1. 程式人生 > >深入Spring:自定義註解載入和使用 144 作者 wcong 關注 2016.03.23 13:41* 字數 1573 閱讀 7651評論 7喜歡 22 前言 在工作中經常使用Spring的相

深入Spring:自定義註解載入和使用 144 作者 wcong 關注 2016.03.23 13:41* 字數 1573 閱讀 7651評論 7喜歡 22 前言 在工作中經常使用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 class ComponentAnnotationTest {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(ComponentAnnotationTest.class);
    annotationConfigApplicationContext.refresh();
    InjectClass injectClass = annotationConfigApplicationContext.getBean(InjectClass.class);
        injectClass.print();
  }
  @MyComponent
  public static class InjectClass {
    public void print() {
        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 {
     public void print() {
         System.out.println("scanClass1");
     }
    }
  3. BeanScannerConfigurer用於嵌入到Spring的載入過程的中,這裡用到了BeanFactoryPostProcessor 和 ApplicationContextAware
    Spring提供了一些的介面使程式可以嵌入Spring的載入過程。這個類中的繼承ApplicationContextAware介面,Spring會讀取ApplicationContextAware型別的的JavaBean,並呼叫setApplicationContext(ApplicationContext applicationContext)傳入Spring的applicationContext
    同樣繼承BeanFactoryPostProcessor介面,Spring會在BeanFactory的相關處理完成後呼叫postProcessBeanFactory方法,進行定製的功能。

    @Component
    public static class BeanScannerConfigurer implements  BeanFactoryPostProcessor, ApplicationContextAware {
     private ApplicationContext applicationContext;
    
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
     }
     public void postProcessBeanFactory(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 class Scanner extends ClassPathBeanDefinitionScanner {
       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 class FactoryBeanTest<T> implements InitializingBean, FactoryBean<T> {
       private String innerClassName;
       public void setInnerClassName(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;
       }
       public boolean isSingleton() {
           return true;
       }
       public void afterPropertiesSet() throws Exception {
       }
    }
    public static class InterfaceProxy implements InvocationHandler {
       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 class MethodInterceptorImpl implements MethodInterceptor {
           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 {
     public static void main(String[] args) {
         AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();                
         annotationConfigApplicationContext.register(CustomizeScanTest.class);
         annotationConfigApplicationContext.refresh();
         ScanClass1 injectClass = annotationConfigApplicationContext.getBean(ScanClass1.class);
         injectClass.print();
     }
    }

至此一個完整的例子就完成了,這裡主要用到了BeanFactoryPostProcessorApplicationContextAwareFactoryBean等Spring內建的介面,來嵌入Spring的載入和使用過程,這樣就實現了自定義註解,和自定義代理了。