`

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

 
阅读更多

看该文章前,可以参阅之前我的入门教程:http://angie.iteye.com/blog/2328987

 

以下文章转自:http://www.cnblogs.com/wcongcode/p/5482239.html

 

前言

在工作中经常使用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的加载和使用过程,这样就实现了自定义注解,和自定义代理了。

分享到:
评论

相关推荐

    示例代码:自定义注解,使用ImportBeanDefinitionRegistrar自动加载

    综上所述,这个示例展示了如何在Spring Cloud环境中通过自定义注解和`ImportBeanDefinitionRegistrar`实现组件的自动加载,提高了代码的可维护性和模块化。这种做法在大型项目中尤其有用,因为它允许我们更好地组织...

    java(spring boot)自定义注解

    总结来说,这个示例展示了如何在Spring Boot项目中自定义注解,将其应用于Java Bean的方法,以及如何利用AOP来实现全局扫描和自动执行。这样的实践在系统监控、性能分析和故障排查中非常有用,能够帮助开发者更好地...

    丛林探险之Spring自定义注解加载Bean

    在Spring框架中,自定义注解加载Bean是一种高级特性,允许开发者根据自定义注解来动态地注册和管理Bean。这种机制提供了极大的灵活性,能够帮助我们构建更加模块化和可扩展的应用程序。以下是对这个过程的详细解释:...

    springboot工程自定义response注解、自定义规范化返回数据结构

    在Spring Boot中,可以创建一个自定义注解,例如`@CustomResponse`,用于标记控制器方法,指示该方法应返回特定的数据结构。这个注解通常会包含一些元信息,如状态码、消息等,以便在处理过程中填充到返回结果中。...

    Java中的注解及自定义注解使用详解.docx

    在使用自定义注解时,可以使用反射API来读取和处理这些注解。例如,`java.lang.reflect.AnnotatedElement`接口提供了访问注解的方法。 在实际开发中,注解广泛应用于依赖注入、持久化框架、测试框架等领域。例如,...

    Spring 自定义注解注入properties文件的值jar包

    Spring 自定义注解注入properties文件的值jar包,下面为使用方法 在xml配置文件中,这样加载properties文件 ...

    对spring做java注解扩展

    创建自定义注解通常包括定义注解类型(使用`@interface`关键字)和处理逻辑(通过组件扫描或自定义处理器)。 2. **元注解**:Spring支持元注解,这意味着我们可以在自定义注解上使用其他注解。例如,`@Target`、`@...

    Java中自定义注解介绍与使用场景详解

    Java 中的自定义注解是指开发者可以根据需要定义和使用的注解,可以在编译期、加载期和运行期进行处理。自定义注解可以扩展 Java 语言的功能,提供更多的 Metadata 信息,方便开发者在开发过程中使用。 自定义注解...

    Spring Boot 全局懒加载机制.docx

    当我们使用Spring Boot的starter来引入第三方库,如`druid-spring-boot-starter`或`spring-boot-starter-data-redis`,它们内部定义的bean默认并不支持懒加载,我们无法直接修改这些第三方库的源码添加`@Lazy`注解。...

    java自定义注解

    Java自定义注解是Java平台...总的来说,Java自定义注解是强大的工具,它们为我们的代码提供了额外的元数据,使代码更易于维护、扩展和理解。通过合理利用自定义注解,可以提高代码的灵活性和可维护性,同时降低复杂性。

    ssm+自定义标签+自定义注解 实现权限细粒度控制

    4. **配置集成**:在Spring的配置文件中,需要配置AOP的切面和自定义标签库,确保它们在系统启动时能够被正确加载和使用。 5. **权限数据模型**:设计合理的权限数据模型,如角色、权限、用户角色关系等,以便于在...

    spring里@Conditional注解使用示例代码

    在Spring框架中,`@Conditional`注解是一个强大的特性,它允许我们有条件地加载bean,也就是说,只有当特定条件满足时,对应的bean才会被Spring容器实例化并注册。这个注解是Spring Boot的一个核心功能,使得我们...

    Spring 注解 小例子

    通过理解并熟练使用这些注解,开发者可以更高效地构建Spring应用,提高代码的可维护性和可扩展性。在实际项目中,常常会结合使用多种注解来实现复杂的功能,例如结合`@Transactional`进行事务管理,或者使用`@...

    SpringAOP的注解配置

    例如,`@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Loggable`,我们可以创建一个自定义注解`@Loggable`,然后在需要记录日志的方法上使用此注解。在切面中,...

    自定义MVC框架(spring)

    在Spring中,我们通常使用@Controller和@RequestMapping注解标记类和方法。自定义框架时,可以模仿这些注解,定义自己的处理逻辑。 Model部分涉及数据模型的创建和管理。Spring MVC中的ModelAndView或者Model接口...

    Spring@PropertySource 配置文件自定义加密、自定义Yaml文件加载

    1. **配置加载顺序**:Spring加载配置文件时有默认的顺序,如果你同时使用了加密的.properties和YAML配置,需要合理安排加载顺序,以保证依赖关系正确。 2. **自定义`ApplicationContextInitializer`**:如果你需要...

    Spring Boot技术知识点:如何深入理解@Component注解

    9. **元注解和自定义注解** `@Component`是一个元注解,可以用来创建自定义注解,比如`@MyService`,这样可以增加代码的可读性和可维护性。 10. **测试` 在测试中,我们可以使用`@SpringBootTest`注解启动整个...

    [课堂课件讲解]Java微服务实践-Spring Boot 自定义启动器.pptx

    Java微服务实践-Spring Boot自定义启动器 Java微服务实践是当前流行的软件架构...本文通过对Spring Boot自定义启动器的详细介绍,帮助开发者更好地理解和使用Spring Boot框架,提高微服务应用程序的开发效率和质量。

    配置文件详解:自定义属性、随机数、多环境配置等

    总结来说,理解并掌握Spring Boot中的自定义属性、随机数生成以及多环境配置,能够帮助开发者更灵活地管理和适应不同场景的应用配置,提高开发效率和应用的可维护性。通过深入学习和实践,可以更好地利用Spring Boot...

    Spring高级之注解驱动开发视频教程

    n 源码分析-@EnableAspectJAutoproxy注解加载过程分析 n 源码分析-AnnotationAwareAspectJAutoProxyCreator n 技术详解-切入点表达式详解 l Spring JDBC n 基础应用-JdbcTemplate的使用 n 源码分析-自定义...

Global site tag (gtag.js) - Google Analytics