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


    转自:https://blog.csdn.net/z69183787/article/details/53784845

    前言

    在工作中经常使用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,这个类有这样的注释

    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 方法有这样几行代码

     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扫描到,并注入容器内。

    定制功能

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

    还是用上面的方法,在注入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的加载过程的中,这里用到了BeanFactoryPostProcessorApplicationContextAware
    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中比较重要的一个类。它的描述如下

    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()就通过代理了原始类的方法,自定义类的方法。
     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的加载和使用过程,这样就实现了自定义注解,和自定义代理了。

    分类: spring
  • 相关阅读:
    分时区查询问题解决
    .htaccess伪静态实例分享
    net mvc 小目标
    PHP中钩子函数的实现与认识
    session
    修改net基本三层 动软生产
    格式化问题
    数据字典
    关联数据和formatter问题-easyui+微型持久化工具
    说说JSON和JSONP,也许你会豁然开朗
  • 原文地址:https://www.cnblogs.com/sharpest/p/6064595.html
Copyright © 2020-2023  润新知