Spring——原理解析-利用反射和注解模拟IoC的自动装配

解析Spring的IoC容器基于注解实现的自动装配(自动注入依赖)的原理


1.本文案例
使用注解和反射机制来模拟Spring中IoC的自动装配功能
定义两个注解:@Component,用来标注组件;@Autowired,用来标记需要被织入的属性。
定义一个@Component注解处理器,用来扫描所有组件。
定义一个bean工厂,用来实例化组件。
测试:有两个组件,一个组件被设置到另一个组件的属性中。

2.定义注解
2.1.定义@Component注解
这个注解表示被标注的就是一个组件,将会被容器自动扫描并创建实例

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    public String id();
}

注解的定义有点类似于接口的定义

注解定义

public @interface Component {
}

接口定义

public interface Component {
}

区别只是在于interface这个标识符前面有没有@符号。

并且注解的定义,还需要使用到几个原生注解:

@Target(ElementType.TYPE)

这个注解表明自定义的注解Component是用来标记谁的,其中ElementType.TYPE表示这个注解使用来标记类型的,也就是可以标记类、接口等。此外还有FIELD、METHOD等,分别表示用来标记字段、方法等。

@Retention(RetentionPolicy.RUNTIME)

表示这个自定义的注解需要保留到什么时候,如只保留到源码中,编译之后就没有了;或者保留到运行时,就是在运行的时候也一直有。这里设置为运行时。

然后这个注解中有这样一行:

public String id();

有点类似于接口中方法的声明,不过在注解中,这个表示注解的一个属性,后面用到的时候可以看看是怎么使用的,就明白了。

2.2.定义 @Autowired注解
这个注解是一个针对成员变量的注解,使用这个注解则表示,这个字段需要由程序来为其赋值的。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {
    public String id();

}

 

3.定义 Beanfactory(也就是注解处理器)
自定义注解完之后,实际上并没有什么用处。要想让注解发挥用处,重点在于注解处理器。
首先来明确下这个处理器干了那些事情,首先根据给定的组件的包名,扫描这个包,找出其中所有的被@Component注解标注的类,将类型的信息保存下来。
然后提供一个getBean()方法,允许根据bean的id来获取bean。
接下来看看这个BeanFactory是如何编写的。

3.1.BeanFactory.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
 
public class BeanFactory {
 
    private HashMap<String, Object> beanPool;
    private HashMap<String, String> components;
    
    public BeanFactory(String packageName) {
        beanPool = new HashMap<>();
        
        scanComponents(packageName);
    }
    
    private void scanComponents(String packageName) {
        components = ComponentScanner
                .getComponentClassName(packageName);
    }
    
    public Object getBean(String id) throws ClassNotFoundException, 
            InstantiationException, IllegalAccessException, 
            NoSuchMethodException, SecurityException, 
            IllegalArgumentException, InvocationTargetException {
        
        if (beanPool.containsKey(id)) {
            return beanPool.get(id);
        }
        
        if (components.containsKey(id)) {
        
            Object bean = Class.forName(components.get(id))
                    .newInstance();
            
            bean = assemblyMember(bean);
            
            beanPool.put(id, bean);
            
            return getBean(id);
        }
        
        throw new ClassNotFoundException();
    }
    
    private Object assemblyMember(Object obj) throws 
            ClassNotFoundException, InstantiationException, 
            IllegalAccessException, NoSuchMethodException, 
            SecurityException, IllegalArgumentException, 
            InvocationTargetException {
        
        Class cl = obj.getClass();
        
        for (Field f : cl.getDeclaredFields()) {
            Autowire at = f.getAnnotation(Autowire.class);
            
            if (at != null) {
 
                Method setMethod = cl.getMethod("set" 
                        + captureName(f.getName()), f.getType());
                        
                setMethod.invoke(obj, getBean(at.id()));
            }
        }
        return obj;
    }
    
    public static String captureName(String name) {
        char[] cs=name.toCharArray();
        cs[0]-=32;
        return String.valueOf(cs);
    }

}

 

 

3.2.ComponentScann.java
这个BeanFactory在构造函数中使用到了一个类,用来扫描出一个包中所有的类的信息。

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
 
public class ComponentScanner {
 
    public static HashMap<String, String> getComponentClassName(
            String packageName) {
        List<String> classes = getClassName(packageName);
        HashMap<String, String> components = new HashMap<String, String>();
        
        try {
            
            for (String cl : classes) {
                cl = cl.replace("workspace_java.LearningJava.bin.", "");
                
                Component comp = Class.forName(cl).getAnnotation(Component.class);
                
                if (comp != null) {
                    components.put(comp.id(), cl);
                }
            }
        
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return components;
    }
    
    public static List<String> getClassName(String packageName) {
        String filePath = ClassLoader.getSystemResource("").getPath() 
                + packageName.replace(".", "\\");  
        List<String> fileNames = getClassName(filePath, null);
        return fileNames;
    }
    
    private static List<String> getClassName(String filePath
            , List<String> className) {  
        List<String> myClassName = new ArrayList<String>();  
        File file = new File(filePath);  
        File[] childFiles = file.listFiles();  
        for (File childFile : childFiles) {  
            if (childFile.isDirectory()) {  
                myClassName.addAll(getClassName(childFile.getPath()
                        , myClassName));  
            } else {  
                String childFilePath = childFile.getPath();  
                childFilePath = childFilePath.substring(childFilePath
                        .indexOf("\\classes") + 9, childFilePath.lastIndexOf("."));  
                childFilePath = childFilePath.replace("\\", ".");  
                myClassName.add(childFilePath);  
            }  
        }  
  
        return myClassName;  
    }
    
    public static void main(String[] args) {
        getComponentClassName("com.oolong.javase.annotation");
    }

}

 

4.测试

定义一个模拟的数据库访问接口

@Component(id = "dataAccessInterface")
public class DataAccessInterface {
 
    public String queryFromTableA() {
        return "query result";
    }
}

这个类使用了Component这个注解,并且注意,这里使用了这个注解的id属性。

定义一个模拟的业务接口

@Component(id="businessObject")
public class BusinessObject {
 
    @Autowire(id="dataAccessInterface")
    private DataAccessInterface dai;
    
    public void print() {
        System.out.println(dai.queryFromTableA());
    }
    
    public void setDai(DataAccessInterface dai) {
        this.dai = dai;
    }
}

这个接口除了使用@Component这个注解标注之外,还有个成员变量,使用了Autowire这个注解标注。使用这个注解标注,表示这个成员变量的初始化将会交给BeanFactory来进行。

测试

public class BeanFactoryTester {
 
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactory("com.oolong.javase.annotation");
        
        BusinessObject obj = (BusinessObject) beanFactory.getBean("businessObject");
        obj.print();
            
    }

}

这里使用BeanFactory创建了一个BusinessObject的对象之后,调用这个对象的print方法,最终打印出来一个结果。

而回到这个类的定义中,可以看到:

public void print() {
    System.out.println(dai.queryFromTableA());
}

这个方法调用的是成员变量dai的queryFromTableA方法。而在这个类中,只有这个成员变量的声明,而没有赋值。

这个赋值又是在哪里进行的呢?

这个就是有我们编写的这个BeanFactory执行的。通过注解和反射机制,自动为类注入依赖。

 

posted @ 2016-12-04 11:12  大肥肥就是我  阅读(4127)  评论(1编辑  收藏  举报