Java的注解机制——Spring自动装配的实现原理

  JDK1.5 加入了对注解机制的支持,实际上我学习 Java 的时候就已经使用 JDK1.6 了,而且除了 @Override 和 @SuppressWarnings(后者还是 IDE 给生成的……) 之外没接触过其他的。

  进入公司前的面试,技术人员就问了我关于注解的问题,我就说可以生成 chm 手册……现在想起来真囧,注释和注解被我搞得完全一样了。

  

  使用注解主要是在需要使用 Spring 框架的时候,特别是使用 SpringMVC。因为这时我们会发现它的强大之处:预处理。

  注解实际上相当于一种标记,它允许你在运行时 (源码、文档、类文件我们就不讨论了) 动态地对拥有该标记的成员进行操作。

  实现注解需要三个条件 (我们讨论的是类似于 Spring 自动装配的高级应用):注解声明、使用注解的元素、操作使用注解元素的代码

  

  首先是注解声明,注解也是一种类型,我们要定义的话也需要编写代码,如下:

 1 package annotation;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * 自定义注解,用来配置方法
10  * 
11  * @author Johness
12  *
13  */
14 @Retention(RetentionPolicy.RUNTIME) // 表示注解在运行时依然存在
15 @Target(ElementType.METHOD) // 表示注解可以被使用于方法上
16 public @interface SayHiAnnotation {
17     String paramValue() default "johness"; // 表示我的注解需要一个参数 名为 "paramValue" 默认值为 "johness"
18 }

 

  然后是使用我们注解的元素:

 1 package element;
 2 
 3 import annotation.SayHiAnnotation;
 4 
 5 /**
 6  * 要使用 SayHiAnnotation 的元素所在类
 7  * 由于我们定义了只有方法才能使用我们的注解,我们就使用多个方法来进行测试
 8  * 
 9  * @author Johness
10  *
11  */
12 public class SayHiEmlement {
13 
14     // 普通的方法
15     public void SayHiDefault(String name){
16         System.out.println("Hi," + name);
17     }
18     
19     // 使用注解并传入参数的方法
20     @SayHiAnnotation(paramValue="Jack")
21     public void SayHiAnnotation(String name){
22         System.out.println("Hi," + name);
23     }
24     
25     // 使用注解并使用默认参数的方法
26     @SayHiAnnotation
27     public void SayHiAnnotationDefault(String name){
28         System.out.println("Hi," + name);
29     }
30 }

  最后,是我们的操作方法 (值得一提的是虽然有一定的规范,但您大可不必去浪费精力,您只需要保证您的操作代码在您希望的时候执行即可):

 1 package Main;
 2 
 3 import java.lang.reflect.InvocationTargetException;
 4 import java.lang.reflect.Method;
 5 
 6 import element.SayHiEmlement;
 7 import annotation.SayHiAnnotation;
 8 
 9 public class AnnotionOperator {
10     public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
11         SayHiEmlement element = new SayHiEmlement(); // 初始化一个实例,用于方法调用
12         Method[] methods = SayHiEmlement.class.getDeclaredMethods(); // 获得所有方法
13         
14         for (Method method : methods) {
15             SayHiAnnotation annotationTmp = null;
16             if((annotationTmp = method.getAnnotation(SayHiAnnotation.class))!=null) // 检测是否使用了我们的注解
17                 method.invoke(element,annotationTmp.paramValue()); // 如果使用了我们的注解,我们就把注解里的 "paramValue" 参数值作为方法参数来调用方法
18             else
19                 method.invoke(element, "Rose"); // 如果没有使用我们的注解,我们就需要使用普通的方式来调用方法了
20         }
21     }
22 }

  结果为:Hi, Jack
      Hi, johness
      Hi, Rose

  可以看到,注解是进行预处理的很好方式 (这里的预处理和编译原理有区别)!

 

  接下来我们看看 Spring 是如何使用注解机制完成自动装配的:

  

    首先是为了让 Spring 为我们自动装配要进行的操作,无外乎两种:继承 org.springframework.web.context.support.SpringBeanAutowiringSupport 类或者添加 @Component/@Controller 等注解并 (只是使用注解方式需要)在 Spring 配置文件里声明 context:component-scan 元素。

    我说说继承方式是如何实现自动装配的,我们打开 Spring 源代码查看 SpringBeanAutowiringSupport 类。我们会发现以下语句:

1 public SpringBeanAutowiringSupport() {
2         processInjectionBasedOnCurrentContext(this);
3     }

 

    众所周知,Java 实例构造时会调用默认父类无参构造方法,Spring 正是利用了这一点,让 "操作元素的代码" 得以执行!(我看到第一眼就震惊了!真是奇思妙想啊。果然,高手都要善于用 Java 来用 Java)

    后面的我就不就不多说了,不过还是要纠正一些人的观点:说使用注解的自动装配来完成注入也需要 setter。这明显是错误的嘛!我们看 Spring 注解装配 (继承方式) 的方法调用顺序: org.springframework.web.context.support.SpringBeanAutowiringSupport#SpringBeanAutowiringSupport=>

        org.springframework.web.context.support.SpringBeanAutowiringSupport#processInjectionBasedOnCurrentContext=>

      org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#processInjection=>

               org.springframework.beans.factory.annotation.InjectionMetadata#Injection(继承,方法重写)。最后看看 Injection 方法的方法体:

 1 /**
 2          * Either this or {@link #getResourceToInject} needs to be overridden.
 3          */
 4         protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
 5             if (this.isField) {
 6                 Field field = (Field) this.member;
 7                 ReflectionUtils.makeAccessible(field);
 8                 field.set(target, getResourceToInject(target, requestingBeanName));
 9             }
10             else {
11                 if (checkPropertySkipping(pvs)) {
12                     return;
13                 }
14                 try {
15                     Method method = (Method) this.member;
16                     ReflectionUtils.makeAccessible(method);
17                     method.invoke(target, getResourceToInject(target, requestingBeanName));
18                 }
19                 catch (InvocationTargetException ex) {
20                     throw ex.getTargetException();
21                 }
22             }
23         }

      虽然不完全,但可以基本判定此种自动装配是使用了 java 放射机制。

 

 欢迎您移步我们的交流群,无聊的时候大家一起打发时间:Programmer Union

 或者通过 QQ 与我联系:点击这里给我发消息

 (最后编辑时间 2013-04-19 09:52:27)