Java中的反射及Bean容器的实现

编程语言中的反射(Refection)指的是可以在程序运行期动态加载一个类。与之相关的是自省(Introspection),这个指的是程序自己可以获取一个类型的描述信息,例如获取一个类的所有接口定义、一个接口的所有形参。当编程语言有了这些语言特性之后,可以在很大程度上解决代码耦合问题,所以在Java的世界里,可以看到很多库/框架使用了反射技术。

类似Spring的Bean容器实现就是大量运用了反射机制。Bean容器维护了一些Bean对象,简单来说就是一些普通对象。Bean容器可以根据配置创建这些对象,创建时如果这些对象依赖了其他对象,Bean容器还会负责将依赖的对象注入到目标对象中,也就是所谓的依赖注入(dependence injection)。放在模块设计中,又衍生出控制反转(IoC, Inverse of Control)概念,用于描述应用程序在使用一个框架时,不是框架来控制/限制应用程序的架构模式,而是由应用程序来控制框架。

本文就简单描述下Bean容器是如何使用反射来实现的,最终代码参考github ioc-sample

类的动态加载

可以简单地使用Class.forName,传入某个class的完整名:

public Class<?> loadClass(String fullName) throws ClassNotFoundException {
      return Class.forName(fullName);
    }

类的加载涉及到class loader,这块内容是可以进一步深化的。加载了类之后就可以创建出类的实例,但还没有完成依赖注入的功能:

Class<?> c = loadClass("com.codemacro.bean.test.Test1");
    Object o = c.newInstance();

通过set接口注入

我们的类可以包含set接口,用于设置某个成员:

public class Test2 {
      public Test1 test1;

      public void setTest1(Test1 t) {
        test1 = t;
      }
    }

那么可以通过setXXX接口将Test1注入到Test2中:

// props指定哪些成员需要注入,例如{"Test1", "test1"},Test1指的是setTest1,test1指的是bean名字
    public Object buildWithSetters(String name, Class<?> c, Map<String, String> props) {
      try {
        // ClassSetMethods 类获取Class<?>中所有setXX这种接口
        ClassSetMethods setMethods = new ClassSetMethods(c);
        Object obj = c.newInstance();
        for (Map.Entry<String, String> entrys : props.entrySet()) {
          String pname = entrys.getKey();
          String beanName = entrys.getValue();
          // 取得setXXX这个Method
          Method m = setMethods.get(pname);
          Object val = getBean(beanName);
          // 调用
          m.invoke(obj, val);
        }
        beans.put(name, obj);
        return obj;
      } catch (Exception e) {
        throw new RuntimeException("build bean failed", e);
      }
    }

ClassSetMethod自省出一个Class中所有的setXXX(xx)接口:

public ClassSetMethods(Class<?> c) {
      Method[] methods = c.getMethods();
      for (Method m : methods) {
        String mname = m.getName();
        Class<?>[] ptypes = m.getParameterTypes();
        if (mname.startsWith("set") && ptypes.length == 1 && m.getReturnType() == Void.TYPE) {
          String name = mname.substring("set".length());
          this.methods.put(name, m);
        }
      }
    }

以上就可以看出Java中的自省能力,例如Class<?>.getMethodsMethod.getReturnTypeMethod.getParameterTypes

通过构造函数注入

类似于Spring中的:

<bean id="exampleBean" class="examples.ExampleBean">
  <constructor-arg type="int" value="2001"/>
  <constructor-arg type="java.lang.String" value="Zara"/>

可以将依赖的Bean通过构造函数参数注入到目标对象中:

List<String> params = new ArrayList<String>();
    params.add("test1");
    bf.buildWithConstructor("test2", Test2.class, params);

其实现:

public Object buildWithConstructor(String name, Class<?> c, List<String> beanNames) {
      try {
        Constructor<?>[] ctors = c.getConstructors(); // 取得Class构造函数列表
        assert ctors.length == 1;
        Constructor<?> cc = ctors[0];
        Class<?>[] ptypes = cc.getParameterTypes(); // 取得构造函数参数类型列表
        assert ptypes.length == beans.size();
        Object[] args = new Object[ptypes.length];
        for (int i = 0; i < beanNames.size(); ++i) {
          args[i] = getBean(beanNames.get(i)); // 构造调用构造函数的实参列表
        }
        Object obj = cc.newInstance(args); // 通过构造函数创建对象
        beans.put(name, obj);
        return obj;
      } catch (Exception e) {
        throw new RuntimeException("build bean failed", e);
      }
    }

这个接口的使用约定beanNames保存的是bean名称,并与构造函数参数一一对应。

通过注解注入

我们可以通过注解标注某个数据成员是需要被自动注入的。我这里简单地获取注解标注的成员类型,找到该类型对应的Bean作为注入对象。当然复杂点还可以指定要注入Bean的名字,或自动查找类型的派生类实现。

一个空的注解即可:

@Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Inject {
    }

实现:

public Object buildWithInject(String name, Class<?> c) {
      try {
        Object obj = c.newInstance();
        Field[] fields = c.getDeclaredFields(); // 获取该类所有定义的成员
        for (Field f :fields) {
          Inject inject = f.getAnnotation(Inject.class); // 获取数据成员的注解
          if (inject != null) { // 如果被Inject注解标注
            Object bean = getBeanByType(f.getType()); // 根据成员的类型找到对应的Bean
            f.set(obj, bean); // 注入
          } else {
            throw new RuntimeException("not found bean " + f.getName());
          }
        }
        beans.put(name, obj);
        return obj;
      } catch (Exception e) {
        throw new RuntimeException("build bean failed", e);
      }
    }

getBeanByType就是根据Class匹配所有的Bean。使用时:

public class Test2 {
      @Inject
      public Test1 test1;
      ...
    }

完。

原文地址: http://codemacro.com/2015/05/31/java-refect-ioc/

written by Kevin Lynx  posted at
http://codemacro.com

时间: 2024-10-25 03:14:30

Java中的反射及Bean容器的实现的相关文章

第二篇 java中的反射

java中的反射 一.反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性: 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.要想解剖一个类,必须先要获取到该类的字节码文件对象. 而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象. 1.创建实体类 1 public class User { 2 private int id; 3 pr

java中利用反射机制绕开编译器对泛型的类型限制

首先看下面这个例子 public static void main(String[] args) { ArrayList<Integer> al1 = new ArrayList<Integer>(); al1.add(1); ArrayList<String> al2 = new ArrayList<String>(); al2.add("hello"); //int型链表和string型链表,结果为true System.out.pr

Java中的反射——(1)什么是反射

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class. public class ReflectTest { public static void main(String[] args) throws ClassNotFoundException { String str1 = "abc"; Class cls1 = String.class; Class cls2 = str1.getClass(); Class cls3 = Class.forNa

浅说Java中的反射机制(二)

写过一篇Java中的反射机制,不算是写,应该是抄了,因为那是别人写的,这一篇也是别人写的,摘抄如下: 引自于Java基础--反射机制的知识点梳理,作者醉眼识朦胧.(()为我手记) 什么是反射? 正常编译执行java文件时,会生成一个.class文件,反射就是一个反编译的过程,它可以通过.class文件得到一个java对象.一个类会有很多组成部分,比如成员变量.成员方法.构造方法等,反射可以通过加载类(加载类是个什么东西?一直搞不清楚),解剖出类的各个组成部分. 为什么要用反射? 我们需要访问一个

JAVA中的反射只获取属性的get方法

JAVA中的反射只获取属性的get方法 在开发过程中,我们经常需要获取类中某些属性的get方法,此时我们需要使用到反射,例如,我们在获得一个对象后,需要知道该对象的哪些属性有值,哪些没有值,方便我们后面的处理. 譬如在我们拼SQL语句时,就需要知道哪些字段为空或为NULL,此时我们在拼语句的时候需要剔除掉,若是我们采用一般的判断的办法,则会很复杂(需要处理好SQL中的AND关键字的有无 ),当然,我们也有另外的解决办法(例如将非空的键和值存入map中,再将map存入list集合中,然后循环集合做

关于JAVA中的反射机制的总结

JAVA中的反射机制是一种能够大大增强程序扩展性的技术.当一个程序封装完成后(生成exe文件),如果想要对该程序进行功能扩展,不用进行代码的从新修改(也不可能拿到源码),只要修改配置文件(一般是XML)就可以完成. 这样的程序为外部提供了一个接口,只要按照该接口的规则定义一个对象(功能),即可以为应用程序扩展该功能. 可以这样比喻,你买了一块电脑主板(封装好的程序),此时你想要添加一块声卡(要扩展的功能),此时只能够通过一个pci接口(应用程序提供的接口)来进行扩展.声卡的接口必须符合PCI的规

Java 中的反射机制

一.什么是Java中的反射: Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法.Java 的这一能力在实际应用中用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性.例如,Pascal.C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息.Reflection 是 Java 被视为动态(或准动态)语言的关键,允许程序于执行期 Reflection APIs 取得任何已知名称之

java中使用反射获取pojo(实体)类的全部字段值

说起反射.不得不说它实在是太强大了,通过反射就能够轻轻松松拿到各种东东,假设你想在项目中解除对某个类的依赖,能够考虑用反射. 今天跟大家分享的是通过java中的反射,获取pojo类的全部字段值. 为什么要做这个操作的呢?主要是为了重写实体类的toString方法.有人会说.直接重写toString方法.返回字段.toString()的字符串不就可以了. 这么做的确能够.可是假设你的pojo类有上百个,上千个,你还要一个一个改吗?所以我们须要从新的方向去解决. 由于我们全部的pojo类.都继承一个

java中的反射机制在Android开发中的用处

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.      然而在android中Google很多的类的某些方法不让第三方应用去调用,通过java反射机制能把这些隐藏方法获取出来并调用,三方应用上我们就很方便的去用这些方法.      例如我们需要安全的杀死某个应用的服务和进程调用ActivityManager.forceStopPack