Spring注解的内部工作原理

Spring注解的内部工作原理

注解工作原理

注解本身只是元数据,即描述数据的数据,被描述的数据可以是类、方法、属性、参数、构造器等。注解仅是标记本身没有任何可执行的功能代码,但是我们只要标注了注解,我们就能得到想要的功能,这是怎么做到的呢?我们理解肯定是有某处代码根据我们标注的注解找到我们注解的类、方法、属性、参数等数据本身,并根据注解本身的含义以及注解属性里的内容执行功能代码。

第一种:BeanPostProcessor

BeanPostProcessor实现中有一类名称为*AnnotationBeanPostProcessor都是针对处理注解的,对容器内标注了指定注解的Bean,进行功能处理。如:

  • AutowiredAnnotationBeanPostProcessor:让@Autowired@Value@Inject注解起效;
  • CommonAnnotationBeanPostProcessor:让@PostConstruct@PreDestroy注解起效;
  • AsyncAnnotationBeanPostProcessor:让@Async@Asynchronous注解起效;
  • ScheduledAnnotationBeanPostProcessor:让@Scheduled注解起效;
  • PersistenceAnnotationBeanPostProcessor:让@PersistenceUnit@PersistenceContext注解起效;
  • JmsListenerAnnotationBeanPostProcessor:让@JmsListener注解起效。 它们都会在构造器里指定其能处理的注解类型,并在对应的方法进行功能处理,我们下面演示一个简单的例子帮助大家理解,我们定义注解@InjectLogger向Bean注入org.slf4j.Logger来做系统日志。

我们先定义要使用的注解,只能注解在类上,默认的后缀为“-Bean”

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectLogger {
}

下面我们定义处理注解的 InjectLoggerAnnotationBeanPostPorcessor,我们在上节已经学习过,我们只需要实现BeanPostPorcessor接口即可:

@Component
public class InjectLoggerAnnotationBeanPostPorcessor implements BeanPostProcessor {
    private Class<? extends Annotation> changeAnnotationType; //1

    public InjectLoggerAnnotationBeanPostPorcessor() {
        this.changeAnnotationType = InjectLogger.class; //1
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        ReflectionUtils.doWithFields(bean.getClass(), field -> { //2
            ReflectionUtils.makeAccessible(field); //3
            if(field.isAnnotationPresent(changeAnnotationType)){ //4
                Logger logger = LoggerFactory.getLogger(bean.getClass()); //5
                field.set(bean, logger); //6
            }
        });
        return bean;
    }
}
  1. 指明当前类处理@InjectLogger注解;
  2. 通过反射机制对类的每个属性(Field)进行处理,第一个参数是Bean的Class,第二参数是入参为Field无返回值的函数接口的Lambda实现;
  3. 通过反射机制让当前属性可访问;
  4. 新建Logger的实例logger
  5. 通过反射将logger值设置到bean实例的当前属性(field)上。 我们将注解使用到其他Bean上使用:

    @Component
    public class DemoLoggerService {
     @InjectLogger
     private Logger log;
    
     public void doSomething(){
         log.info("通过自定义InjectLoggerAnnotationBeanPostPorcessor让注解@InjectLogger注入Logger对象");
     }
    }
    

    通过在JavaConfig运行:

    @Bean
    CommandLineRunner changeAnnotationBeanPostProcessorClr(DemoLoggerService demoLoggerService){
     return args -> {
         demoLoggerService.doSomething();
     };
    }
    

image.png

第二种:BeanFactoryPostProcessor

前面我们讲了针对Bean进行处理的BeanPostProcessor,而BeanFactoryPostProcessor是针对Bean的配置元数据(注解等)进行处理操作,而这项工作属于BeanFactory的职责范畴。

  • ConfigurationClassPostProcessor:使@PropertySource@ComponentScan@Component类、@Configuration@Bean@Import@ImportResource注解起效;
  • EventListenerMethodProcessor:使@EventListener注解起效; 我们也通过一个自定义的注解@CustomBean,来自己自动注册Bean,我们的`@CustomBean的作用即是配置元数据。

自定义的注解为:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomBean {
}

使用在Bean上:

@CustomBean
public class CustomBeanService {
    public void doSomething(){
        System.out.println("通过自定义的注解成功注册bean");
    }
}

同样,我们也实现BeanFactoryPostProcessor接口:

@Component
public class CustomBeanDefinitionRegistryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) beanFactory); //1
        scanner.addIncludeFilter(new AnnotationTypeFilter(CustomBean.class)); //2
        scanner.scan("top.wisely.springfundamentals.custom_scan"); //3

    }
}
  1. 定义一个类路径Bean定义扫描器,它的入参是BeanDefinitionRegistry类型,而ConfigurableListableBeanFactory是它的子类,可强制转换使用,当然我们可以让我们的类直接实现BeanDefinitionRegistryPostProcessor接口,它的方法void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)直接提供了BeanDefinitionRegistry的对象;
  2. 为扫描器添加包含的注解@CustomBean的过滤器;
  3. 在包top.wisely.springfundamentals.custom_scan下扫描注解; 在JavaConfig中注入我们自定义的Bean,因为是我们自定义的,IDE不能自动检测而显示红色,但可以正常执行:
    @Bean
    CommandLineRunner customBeanDefinitionRegistryPostProcessorClr(CustomBeanService customBeanService){
     return args -> {
         customBeanService.doSomething();
     };
    }
    

image.png

第三种:AOP

我们还可以通过基于AOP来让注解具备功能,通过拦截标注了指定注解的方法或类,然后再建言执行功能代码。如:

  • AnnotationTransactionAspect:让@Transactional注解起效;
  • AnnotationCacheAspect:让@Cacheable注解起效;

第四种:组合元注解

Spring的大部分的元注解,我们可以使用元注解到其它的注解,即用元注解(元数据)描述注解,从而使其它的注解具备元注解的功能,一般我们认为组合注解是元注解在新的语义下的特例。

  • @Component元注解:@Service@Repository@Controller@Configuration
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component //组合了@Component注解,具备了声明Bean的能力
    public @interface Service {}
    
  • @Import元注解:大量的@Enable*注解:
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AsyncConfigurationSelector.class) //组合了@Import注解,具备了导入配置的能力
    public @interface EnableAsync {}
    
  • @Conditional元注解:@Profile以及Spring Boot的大量条件注解@ConditionalOn*
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnClassCondition.class)
    public @interface ConditionalOnClass {}
    

    总结

    Spring框架的注解的主要工作方式大体罗列在本文,如果大家发现还有其他的Spring注解的工作方式,欢迎补充。