前言

在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑。特别是开发Web应用时,我们会频繁的定义@Controller@Service等JavaBean组件,通过注解,Spring自动扫描加载了这些组件,并提供相关的服务。
Spring是如何读取注解信息,并注入到bean容器中的,本文就是通过嵌入Spring的Bean加载,来描述Spring的实现方法。完整的例子都在Github上了。

自定义注解

先看一个最简单的例子,在使用SpringWeb应用中的过程中,大家免不了会使用@Controller@Service@Repository等注解来定义JavaBean。那么怎么自己定义一个注解,Spring可以自动加载呢。所以就有了第一个例子。

  1.  
    @Target({ ElementType.TYPE })
  2.  
    @Retention(RetentionPolicy.RUNTIME)
  3.  
    @Documented
  4.  
    @Component
  5.  
    public @interface MyComponent {
  6.  
    String value() default "";
  7.  
    }
  1.  
    @Configuration
  2.  
    public class ComponentAnnotationTest {
  3.  
    public static void main(String[] args) {
  4.  
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
  5.  
    annotationConfigApplicationContext.register(ComponentAnnotationTest.class);
  6.  
    annotationConfigApplicationContext.refresh();
  7.  
    InjectClass injectClass = annotationConfigApplicationContext.getBean(InjectClass.class);
  8.  
    injectClass.print();
  9.  
    }
  10.  
    @MyComponent
  11.  
    public static class InjectClass {
  12.  
    public void print() {
  13.  
    System.out.println("hello world");
  14.  
    }
  15.  
    }
  16.  
    }

运行这个例子,就会发现,@MyComponent 注解的类,也被Spring加载进来了,而且可以当成普通的JavaBean正常的使用。查看Spring的源码会发现,Spring是使用ClassPathScanningCandidateComponentProvider扫描package,这个类有这样的注释

  1.  
    A component provider that scans the classpath from a base package.
  2.  
    It then applies exclude and include filters to the resulting classes to find candidates.

这个类的 registerDefaultFilters 方法有这样几行代码

  1.  
    protected void registerDefaultFilters() {
  2.  
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  3.  
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  4.  
    try {
  5.  
    this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
  6.  
    logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  7.  
    } catch (ClassNotFoundException ex) {
  8.  
    // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  9.  
    }
  10.  
    try {
  11.  
    this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
  12.  
    logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  13.  
    }
  14.  
    catch (ClassNotFoundException ex) {
  15.  
    // JSR-330 API not available - simply skip.
  16.  
    }
  17.  
    }

这里就会发现Spring在扫描类信息的使用只会判断被@Component注解的类,所以任何自定义的注解只要带上@Component(当然还要有String value() default "";的方法,因为Spring的Bean都是有beanName唯一标示的),都可以被Spring扫描到,并注入容器内。

定制功能

但上面的方法太局限了,没办法定制,而且也没有实际的意义。如何用特殊的注解来实现定制的功能呢,一般有两种方式:

  1. 还是用上面的方法,在注入Spring的容器后,再取出来做自己定制的功能,Spring-MVC就是使用这样的方法。AbstractDetectingUrlHandlerMapping 中的detectHandlers方法,这个方法取出了所有的bean,然后循环查找带有Controller的bean,并提取其中的RequestMapping信息

    1.  
      protected void detectHandlers() throws BeansException {
    2.  
      if (logger.isDebugEnabled()) {
    3.  
      logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
    4.  
      }
    5.  
      String[] beanNames = (this.detectHandlersInAncestorContexts ?
    6.  
      BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
    7.  
      getApplicationContext().getBeanNamesForType(Object.class));
    8.  
       
    9.  
      // Take any bean name that we can determine URLs for.
    10.  
      for (String beanName : beanNames) {
    11.  
      String[] urls = determineUrlsForHandler(beanName);
    12.  
      if (!ObjectUtils.isEmpty(urls)) {
    13.  
      // URL paths found: Let's consider it a handler.
    14.  
      registerHandler(urls, beanName);
    15.  
      }
    16.  
      else {
    17.  
      if (logger.isDebugEnabled()) {
    18.  
      logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
    19.  
      }
    20.  
      }
    21.  
      }
    22.  
      }
  2. 不依赖@Component,自定义扫描。所以就有了第二个例子。

自定义扫描

结构比较复杂,可以参考完整的例子,这里是关键的几个类

  1. 还是定义一个注解,只不过不再需要@Component

    1.  
      @Target({ ElementType.TYPE })
    2.  
      @Retention(RetentionPolicy.RUNTIME)
    3.  
      @Documented
    4.  
      public @interface CustomizeComponent {
    5.  
      String value() default "";
    6.  
      }
  2. 注解修饰的类

    1.  
      @CustomizeComponent
    2.  
      public class ScanClass1 {
    3.  
      public void print() {
    4.  
      System.out.println("scanClass1");
    5.  
      }
    6.  
      }
  3. BeanScannerConfigurer用于嵌入到Spring的加载过程的中,这里用到了BeanFactoryPostProcessor 和ApplicationContextAware
    Spring提供了一些的接口使程序可以嵌入Spring的加载过程。这个类中的继承ApplicationContextAware接口,Spring会读取ApplicationContextAware类型的的JavaBean,并调用setApplicationContext(ApplicationContext applicationContext)传入Spring的applicationContext
    同样继承BeanFactoryPostProcessor接口,Spring会在BeanFactory的相关处理完成后调用postProcessBeanFactory方法,进行定制的功能。

    1.  
      @Component
    2.  
      public static class BeanScannerConfigurer implements BeanFactoryPostProcessor, ApplicationContextAware {
    3.  
      private ApplicationContext applicationContext;
    4.  
       
    5.  
      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    6.  
      this.applicationContext = applicationContext;
    7.  
      }
    8.  
      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    9.  
      Scanner scanner = new Scanner((BeanDefinitionRegistry) beanFactory);
    10.  
      scanner.setResourceLoader(this.applicationContext);
    11.  
      scanner.scan("org.wcong.test.spring.scan");
    12.  
      }
    13.  
      }
  4. Scanner继承的ClassPathBeanDefinitionScanner是Spring内置的Bean定义的扫描器。
    includeFilter里定义了类的过滤器,newAnnotationTypeFilter(CustomizeComponent.class)表示只取被CustomizeComponent修饰的类。
    doScan里扫面了包底下的读取道德BeanDefinitionHolder,自定义GenericBeanDefinition相关功能。
    1.  
      public final static class Scanner extends ClassPathBeanDefinitionScanner {
    2.  
      public Scanner(BeanDefinitionRegistry registry) {
    3.  
      super(registry);
    4.  
      }
    5.  
      public void registerDefaultFilters() {
    6.  
      this.addIncludeFilter(new AnnotationTypeFilter(CustomizeComponent.class));
    7.  
      }
    8.  
      public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    9.  
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    10.  
      for (BeanDefinitionHolder holder : beanDefinitions) {
    11.  
      GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
    12.  
      definition.getPropertyValues().add("innerClassName", definition.getBeanClassName());
    13.  
      definition.setBeanClass(FactoryBeanTest.class);
    14.  
      }
    15.  
      return beanDefinitions;
    16.  
      }
    17.  
      public boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    18.  
      return super.isCandidateComponent(beanDefinition) && beanDefinition.getMetadata()
    19.  
      .hasAnnotation(CustomizeComponent.class.getName());
    20.  
      }
    21.  
      }
  5. FactoryBean是Spring中比较重要的一个类。它的描述如下
    1.  
      Interface to be implemented by objects used within a BeanFactory which are themselves factories.
    2.  
      If a bean implements this interface, it is used as a factory for an object to expose, not directly as a bean* instance that will be exposed itself

    普通的JavaBean是直接使用类的实例,但是如果一个Bean继承了这个借口,就可以通过getObject()方法来自定义实例的内容,在FactoryBeanTest的getObject()就通过代理了原始类的方法,自定义类的方法。

    1.  
      public static class FactoryBeanTest<T> implements InitializingBean, FactoryBean<T> {
    2.  
      private String innerClassName;
    3.  
      public void setInnerClassName(String innerClassName) {
    4.  
      this.innerClassName = innerClassName;
    5.  
      }
    6.  
      public T getObject() throws Exception {
    7.  
      Class innerClass = Class.forName(innerClassName);
    8.  
      if (innerClass.isInterface()) {
    9.  
      return (T) InterfaceProxy.newInstance(innerClass);
    10.  
      } else {
    11.  
      Enhancer enhancer = new Enhancer();
    12.  
      enhancer.setSuperclass(innerClass);
    13.  
      enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    14.  
      enhancer.setCallback(new MethodInterceptorImpl());
    15.  
      return (T) enhancer.create();
    16.  
      }
    17.  
      }
    18.  
      public Class<?> getObjectType() {
    19.  
      try {
    20.  
      return Class.forName(innerClassName);
    21.  
      } catch (ClassNotFoundException e) {
    22.  
      e.printStackTrace();
    23.  
      }
    24.  
      return null;
    25.  
      }
    26.  
      public boolean isSingleton() {
    27.  
      return true;
    28.  
      }
    29.  
      public void afterPropertiesSet() throws Exception {
    30.  
      }
    31.  
      }
    32.  
      public static class InterfaceProxy implements InvocationHandler {
    33.  
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    34.  
      System.out.println("ObjectProxy execute:" + method.getName());
    35.  
      return method.invoke(proxy, args);
    36.  
      }
    37.  
      public static <T> T newInstance(Class<T> innerInterface) {
    38.  
      ClassLoader classLoader = innerInterface.getClassLoader();
    39.  
      Class[] interfaces = new Class[] { innerInterface };
    40.  
      InterfaceProxy proxy = new InterfaceProxy();
    41.  
      return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
    42.  
      }
    43.  
      }
    44.  
      public static class MethodInterceptorImpl implements MethodInterceptor {
    45.  
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    46.  
      System.out.println("MethodInterceptorImpl:" + method.getName());
    47.  
      return methodProxy.invokeSuper(o, objects);
    48.  
      }
    49.  
      }
  6. main函数
    1.  
      @Configuration
    2.  
      public class CustomizeScanTest {
    3.  
      public static void main(String[] args) {
    4.  
      AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
    5.  
      annotationConfigApplicationContext.register(CustomizeScanTest.class);
    6.  
      annotationConfigApplicationContext.refresh();
    7.  
      ScanClass1 injectClass = annotationConfigApplicationContext.getBean(ScanClass1.class);
    8.  
      injectClass.print();
    9.  
      }
    10.  
      }

至此一个完整的例子就完成了,这里主要用到了BeanFactoryPostProcessorApplicationContextAwareFactoryBean等Spring内置的接口,来嵌入Spring的加载和使用过程,这样就实现了自定义注解,和自定义代理了。

文/wcong(简书作者)
原文链接:http://www.jianshu.com/p/7c2948f64b1c
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

深入Spring:自定义注解加载和使用的更多相关文章

  1. spring 自定义schema 加载异常 White spaces are required between publicId and systemId.

    spring 项目启动报错 报错日志如下: Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreExcepti ...

  2. Spring BeanDefinition的加载

     前面提到AbstractRefreshableApplicationContext在刷新BeanFactory时,会调用loadBeanDefinitions方法以加载系统中Bean的定义,下面将讲 ...

  3. Spring源码加载BeanDefinition过程

    本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...

  4. Bean 注解(Annotation)配置(1)- 通过注解加载Bean

    Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...

  5. 在Spring Boot中加载初始化数据

    文章目录 依赖条件 data.sql文件 schema.sql 文件 @sql注解 @SqlConfig 注解 在Spring Boot中加载初始化数据 在Spring Boot中,Spring Bo ...

  6. 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)

    [SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...

  7. Spring Boot 配置加载顺序详解

    使用 Spring Boot 会涉及到各种各样的配置,如开发.测试.线上就至少 3 套配置信息了.Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发.测试.线上环境使用不同的配置. 在 ...

  8. Spring源码加载过程图解(一)

    最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...

  9. 【Spring】Junit加载Spring容器作单元测试(整理)

    [Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...

随机推荐

  1. day6笔记

    一.上节回顾 list:li = [1,2,3,5,'a']增加:append:末尾加入==追加 insert:插入,在任意位置,insert(index,'内容') extend:迭代着加入,'as ...

  2. CentOS系统bash: groupadd: command not found问题

    如果我们需要在CentOS执行新建用户组命令的时候,需要进入到ROOT权限,如果你用以下命令: 1 su2 su root 进入到ROOT账户,那么会出现上述的错误信息:“bash: groupadd ...

  3. No image!使用border-color属性来制作小三角形

    border属性在项目中使用的还是蛮频繁的.例如页签.按钮这样的. border简写属性是按照如下属性设置的: border:border-width/border-style/border-colo ...

  4. 1280 前缀后缀集合(map)

    1280 前缀后缀集合 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 一个数组包含N个正整数,其中有些是重复的.一个前缀后缀集是满足 ...

  5. 05、(通过nat123软件) 实现用自己电脑搭建一个网站

    (通过nat123软件) 实现用自己电脑搭建一个网站 准备: Tomcat:这个是web容器,其实有了这个就已经让电脑成为服务器了,在自己电脑上可以通过 localhost:8080/xxx 来访问自 ...

  6. 获取JDBC响应做接口关联

    1:从sql表中将需要取的数据查出来 2:我们需要把这个id为4451的数据从sql里面取出来,传到下一个sql里面,执行删除 3:写一个接口的传参有些不同,变成了var_id_1.var_id是之前 ...

  7. windows下redis的安装和启动

    Rides: //cmd管理员进入 // 运行 : redis-cli.exe //报错  :Redis (error) NOAUTH Authentication required.解决方法 // ...

  8. Ubuntu系统vi编辑器上下左右键变ABCD的解决方法(转)

    首先卸载旧版本的vi编辑器: $sudo apt-get remove vim-common 然后安装新版vi即可: $sudo apt-get install vim Ubuntu自带有几种版本的v ...

  9. 剑指offer 面试36题

    面试36题: 题:二叉搜索树与双向链表 题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. 解题思路一:由于输入的一个二叉搜索树, ...

  10. python常用模块——time模块

    参考博客:http://blog.csdn.net/SeeTheWorld518/article/details/48314501 http://www.jb51.net/article/49325. ...