深入Spring 自定义注解加载和使用

前言

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

自定义注解

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

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyComponent {
    String value()default "";
}
@Configuration
public classComponentAnnotationTest{
  publicstaticvoidmain(String[] args){
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(ComponentAnnotationTest.class);
    annotationConfigApplicationContext.refresh();
    InjectClass injectClass = annotationConfigApplicationContext.getBean(InjectClass.class);
        injectClass.print();
  }
  @MyComponent
  public static classInjectClass{
    publicvoidprint(){
        System.out.println("hello world");
    }
  }
}

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

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

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

protected void registerDefaultFilters() {
   this.includeFilters.add(new AnnotationTypeFilter(Component.class));
   ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
   try {
      this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
      logger.debug("JSR-250 ‘javax.annotation.ManagedBean‘ found and supported for component scanning");
   }   catch (ClassNotFoundException ex) {
     // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
   }
   try {
      this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
      logger.debug("JSR-330 ‘javax.inject.Named‘ annotation found and supported for component scanning");
   }
   catch (ClassNotFoundException ex) {
   // JSR-330 API not available - simply skip.
   }
}

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

定制功能

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

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

    protected void detectHandlers() throws BeansException {
         if (logger.isDebugEnabled()) {
             logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
         }
         String[] beanNames = (this.detectHandlersInAncestorContexts ?
                 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                 getApplicationContext().getBeanNamesForType(Object.class));
    
         // Take any bean name that we can determine URLs for.
         for (String beanName : beanNames) {
             String[] urls = determineUrlsForHandler(beanName);
             if (!ObjectUtils.isEmpty(urls)) {
                 // URL paths found: Let‘s consider it a handler.
                 registerHandler(urls, beanName);
             }
             else {
                 if (logger.isDebugEnabled()) {
                     logger.debug("Rejected bean name ‘" + beanName + "‘: no URL paths identified");
                 }
             }
         }
     }
  2. 不依赖@Component,自定义扫描。所以就有了第二个例子。

自定义扫描

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

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

    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CustomizeComponent {
      String value()default "";
    }
  2. 注解修饰的类
    @CustomizeComponent
    public class ScanClass1 {
     publicvoidprint() {
         System.out.println("scanClass1");
     }
    }
  3. BeanScannerConfigurer用于嵌入到Spring的加载过程的中,这里用到了BeanFactoryPostProcessorApplicationContextAware
    Spring提供了一些的接口使程序可以嵌入Spring的加载过程。这个类中的继承ApplicationContextAware接口,Spring会读取ApplicationContextAware类型的的JavaBean,并调用setApplicationContext(ApplicationContext applicationContext)传入Spring的applicationContext
    同样继承BeanFactoryPostProcessor接口,Spring会在BeanFactory的相关处理完成后调用postProcessBeanFactory方法,进行定制的功能。
    @Component
    public static classBeanScannerConfigurerimplementsBeanFactoryPostProcessor, ApplicationContextAware{
     private ApplicationContext applicationContext;
    
     publicvoidsetApplicationContext(ApplicationContext applicationContext)throws BeansException {
       this.applicationContext = applicationContext;
     }
     publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException {
       Scanner scanner = new Scanner((BeanDefinitionRegistry) beanFactory);
       scanner.setResourceLoader(this.applicationContext);
       scanner.scan("org.wcong.test.spring.scan");
     }
    }
  4. Scanner继承的ClassPathBeanDefinitionScanner是Spring内置的Bean定义的扫描器
    includeFilter里定义了类的过滤器,newAnnotationTypeFilter(CustomizeComponent.class)表示只取被CustomizeComponent修饰的类。
    doScan里扫面了包底下的读取道德BeanDefinitionHolder,自定义GenericBeanDefinition相关功能。
    public final static classScannerextendsClassPathBeanDefinitionScanner{
       public Scanner(BeanDefinitionRegistry registry) {
           super(registry);
       }
       public void registerDefaultFilters() {
           this.addIncludeFilter(new AnnotationTypeFilter(CustomizeComponent.class));
       }
       public Set<BeanDefinitionHolder> doScan(String... basePackages) {
           Set<BeanDefinitionHolder> beanDefinitions =   super.doScan(basePackages);
           for (BeanDefinitionHolder holder : beanDefinitions) {
               GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
               definition.getPropertyValues().add("innerClassName", definition.getBeanClassName());
               definition.setBeanClass(FactoryBeanTest.class);
           }
           return beanDefinitions;
       }
       public boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
          return super.isCandidateComponent(beanDefinition) && beanDefinition.getMetadata()
    .hasAnnotation(CustomizeComponent.class.getName());
       }
    }
  5. FactoryBean是Spring中比较重要的一个类。它的描述如下
    Interface to be implemented by objects used within a BeanFactory which are themselves factories.
    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()就通过代理了原始类的方法,自定义类的方法。

    public static classFactoryBeanTest<T> implementsInitializingBean, FactoryBean<T> {
       private String innerClassName;
       publicvoidsetInnerClassName(String innerClassName){
           this.innerClassName = innerClassName;
       }
       public T getObject()throws Exception {
           Class innerClass = Class.forName(innerClassName);
           if (innerClass.isInterface()) {
               return (T) InterfaceProxy.newInstance(innerClass);
           } else {
               Enhancer enhancer = new Enhancer();
               enhancer.setSuperclass(innerClass);
               enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
               enhancer.setCallback(new MethodInterceptorImpl());
               return (T) enhancer.create();
           }
       }
       public Class<?> getObjectType() {
           try {
                 return Class.forName(innerClassName);
           } catch (ClassNotFoundException e) {
                 e.printStackTrace();
           }
           return null;
       }
       publicbooleanisSingleton(){
           return true;
       }
       publicvoidafterPropertiesSet()throws Exception {
       }
    }
    public static classInterfaceProxyimplementsInvocationHandler{
       public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
           System.out.println("ObjectProxy execute:" + method.getName());
           return method.invoke(proxy, args);
       }
       public static <T> T newInstance(Class<T> innerInterface){
           ClassLoader classLoader = innerInterface.getClassLoader();
           Class[] interfaces = new Class[] { innerInterface };
           InterfaceProxy proxy = new InterfaceProxy();
           return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
       }
      }
      public static classMethodInterceptorImplimplementsMethodInterceptor{
           public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {
           System.out.println("MethodInterceptorImpl:" + method.getName());
           return methodProxy.invokeSuper(o, objects);
       }
    }
  6. main函数
    @Configuration
    public class CustomizeScanTest {
     publicstaticvoidmain(String[] args){
         AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
         annotationConfigApplicationContext.register(CustomizeScanTest.class);
         annotationConfigApplicationContext.refresh();
         ScanClass1 injectClass = annotationConfigApplicationContext.getBean(ScanClass1.class);
         injectClass.print();
     }
    }

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

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

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

原文地址:https://www.cnblogs.com/skinchqqhah/p/10350631.html

时间: 2024-08-06 14:53:20

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

spring源码阅读(二) Bean加载之自定义标签加载

紧接着上一篇关于spring默认标签加载,这一篇来看下自定义标签的加载 继续从 DefaultBeanDefinitionDocumentReader来看 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for(

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

Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of Control – IOC) 理解依赖注入(DI – Dependency Injection) Bean XML 配置(1)- 通过XML配置加载Bean Bean XML 配置(2)- Bean作用域与生命周期回调方法配置 Bean XML 配置(3)- 依赖注入配置 Bean XML 配置(

Spring BeanDefinition的加载

?前面提到AbstractRefreshableApplicationContext在刷新BeanFactory时,会调用loadBeanDefinitions方法以加载系统中Bean的定义,下面将讲解Bean定义的加载过程. 一.XML定义 ?XML配置的加载由AbstractXmlApplicationContext实现,方法实现如下: ?主要是实例化了一个XmlBeanDefinitionReader对象,对其设置了Environment对象(具体过程可以上一篇)等后,调用XmlBeanD

spring 如何动态加载properties文件的内容

1. 在xml中配置你的properties路径: <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <!-- 指定资源文件基名称 jdbc为文件名,不包含扩展名 --&g

Spring Boot 配置加载顺序详解

使用 Spring Boot 会涉及到各种各样的配置,如开发.测试.线上就至少 3 套配置信息了.Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发.测试.线上环境使用不同的配置. 在 Spring Boot 里面,可以使用以下几种方式来加载配置.本章内容基于 Spring Boot 2.0 进行详解. 1.properties文件: 2.YAML文件: 3.系统环境变量: 4.命令行参数: 等等-- 我们可以在 Spring Beans 里面直接使用这些配置文件中加载的值,如:

监听器如何获取Spring配置文件(加载生成Spring容器)

Spring容器是生成Bean的工厂,我们在做项目的时候,会用到监听器去获取spring的配置文件,然后从中拿出我们需要的bean出来,比如做网站首页,假设商品的后台业务逻辑都做好了,我们需要创建一个监听器,在项目启动时将首页的数据查询出来放到application里,即在监听器里调用后台商品业务逻辑的方法,也就是说我们需要在监听器里获取Spring中配置的相应的bean.先把监听器创建出来: 1. 创建InitDataListener 创建一个监听器InitDataListener继承Serv

spring不断重新加载所有类文件

第一次写博文,可能有点兴奋和啰嗦,先说说使用的环境:resin+spring+struts,服务器是测试用的,由运维同事维护,里面有几个项目,仅供开发的同事修改. 事情从昨天下午开始, 查看测试服务器log时,发现spring不断的加载类文件,但却没有任何错误信息提示,初步判断应该是某开发的同事修改了配置文件导致的问题,但看了一遍没发现什么问题,难道服务器作了什么动作?于是问了一名运维同事,说没作什么修改,一听心血来潮,因为这个问题可以加入我的问题库里面. 因为配置正常,怀疑是某些类加载不正常导

iOS多线程自定义operation加载图片 不重复下载图片

摘要:1:ios通过抽象类NSOperation封装了gcd,让ios的多线程变得更为简单易用:   2:耗时的操作交给子线程来完成,主线程负责ui的处理,提示用户的体验   2:自定义operation继承自NSOperation,在子线程中下载图片: 3:保证图片只下载一次,还有保证下载任务不重复 ------------------------------------------------------------------------------------ 实现原理:1:图片缓存:用

Spring mvc应用 加载静态资源的几种方式

总结几种Spring mvc应用加载静态资源的方式 1.使用服务器的默认Servlet处理 对于不同的服务器,处理静态资源的servlet-name不一样,需要去看服务器具体的配置文件 比如resin-3.1.12,通过查看app-default.xml可以看到默认处理jsp的servlet-name为>resin-jsp 所以可以通过在web.xml中添加静态资源的访问 <servlet-mapping>        <servlet-name>resin-jsp<