zoukankan      html  css  js  c++  java
  • 深入Spring:自定义注解加载和使用

    前言

    在工作中经常使用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
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    如何得到需要下载文件的链接(路径)?
    Python之内存泄漏和内存溢出
    IO多路复用
    python-socket和进程线程协程(代码展示)
    Xshell(远程)连接不上linux服务器(防火墙介绍)
    Shell--变量
    Python读写配置文件模块--Configobj
    python文件处理之fileinput
    python之commands和subprocess入门介绍(可执行shell命令的模块)
    linux下 > /dev/null 2 > &1 的意思和如何在后台启动进程
  • 原文地址:https://www.cnblogs.com/zhoading/p/10243025.html
Copyright © 2011-2022 走看看