Spring源码组合注解/合并注解的问题

编程

我想,每个开发人员都应该有过这样的经历:在编写某个类或接口的时候,需要声明Spring本身的注解(@Controller、@Service,@Dao),又需要声明自己公司编写的注解来完成公司的独特业务,然后就悲剧了,一个类上边声明了五六个注解,茫茫然不知所云。注解本身是好的,它可以替我们完成一些事情。但和XML一样,过度使用就编程了一种灾难。

     于是,一种新的替代方案出现了,那就是组合注解。比较经典的组合注解就是SpringBoot的@SpringBootApplication注解。我们看看它的源码: 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
 
 
    Class<?>[] exclude() default {};
 
 
    String[] excludeName() default {};
 
 
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
 
 
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
 
}
      我们可以大致看看它包含哪些功能:

          第一点,它本身是个注解,提供了exclude()和excludeName()两个注解属性;

          第二点,它声明了@ComponentScan注解,同时是@ComponentScan注解的容器。我们发现scanBasePackages和scanBasePackageClasses两个注解属性上面同样声明了@AliasFor注解,分别指向了@ComponentScan注解的basePackages注解属性和basePackageClasses属性。

          第三点,它声明了@Configuration注解,表明声明了它的类本身也是个配置类。

          第四点,它声明了@EnableAutoConfiguration注解,表明声明了它的类本身会默认开启自动配置

          第五点,它声明了@Inherited注解,表明声明了它的类的子类是可以继承它的。

       以上,就是@SpringBootApplication注解的全部含义了。那么,注解为什么可以被继承呢?  想象我们是如何定义一个注解的?它与我们定义的普通类和接口有什么区别?这是一个最原始的注解:

public @Interface TestAnno{
 
}
      可以看到,它在创建时就被声明为@Interface类型,而不是class类型。声明为@Interface类型的类在编译时会自动继承Annotation接口,那什么类型的类可以继承接口呢?自然是接口类型了。换言之,对编译器而言,注解其实就是一个接口。所以,我们在某个类上声明注解本质上等价于继承注解代表的接口。因为注解本质上是接口,所以就具有了相互继承的能力了。

     那问题来了,既然注解本质上是接口,那我们在注解里声明的注解方法呢?我们给注解属性赋值又是个什么行为?这里啊,我们需要借鉴Mybatis了,想象,Mybatis为何可以只声明接口就具有了访问数据库的能力?对了,就是JDK动态代理!编译器把我们的类翻译为实现了注解接口的类,然后用JDK动态代理的方式拦截所有该类方法的调用,如果是自定义方法就放行;如果是注解方法就拦截下来执行某些逻辑。至于我们给注解属性赋值,会以JDK动态代理类的常量的方式保存,需要时使用就可以了。我们可以看看代理注解的类是怎样的:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    
    //实例化本类时会将注解方法存到memberValues中
    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        //获取参数接口上声明的所有注解接口
        Class<?>[] superInterfaces = type.getInterfaces();
        /*
         *参数接口本身不能是Annotation接口,
         *参数接口不能只拥有一个接口(或注解接口)父类
         *参数接口不能直接继承Annotation接口(说明参数接口是注解,不能给注解生成代理类)
         */
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }
    
    //拦截所有方法的执行
    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();
 
        // 拦截Object类和Annotation接口的方法
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");
 
        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }
 
        // 拦截真正注解类方法的执行
        Object result = memberValues.get(member);
 
        if (result == null)
            throw new IncompleteAnnotationException(type, member);
 
        if (result instanceof ExceptionProxy)
            throw ((ExceptionProxy) result).generateException();
 
        if (result.getClass().isArray() && Array.getLength(result) != 0)
            result = cloneArray(result);
 
        return result;
    }
}
    最后安利一个知识点,很重要。我们在合并注解的时候,比如A中有属性A(),B中有属性B(),它们的组合注解这么写就可以了:

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@A
@B
public @Interface C{
  
   String A();
 
   String B();
 
}
     
————————————————
版权声明:本文为CSDN博主「小雨的光」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28802119/article/details/83573950

以上是 Spring源码组合注解/合并注解的问题 的全部内容, 来源链接: utcz.com/z/514604.html

回到顶部