码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2015-05-31 16:54:44      阅读:150      评论:0      收藏:0      [点我收藏+]

标签:

编程语言中的反射(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;
      ...
    }

完。

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

标签:

原文地址:http://blog.csdn.net/kevinlynx/article/details/46289117

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!