深入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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM