解析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 HashMapbeanPool; private HashMap 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 HashMapgetComponentClassName( String packageName) { List classes = getClassName(packageName); HashMap components = new HashMap (); 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 getClassName(String packageName) { String filePath = ClassLoader.getSystemResource("").getPath() + packageName.replace(".", "\\"); List fileNames = getClassName(filePath, null); return fileNames; } private static List getClassName(String filePath , List className) { List myClassName = new ArrayList (); 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执行的。通过注解和反射机制,自动为类注入依赖。