深入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@Componentpublic @interface MyComponent {String value() default "";}
@Configurationpublic 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();}@MyComponentpublic 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扫描到,并注入容器内。
定制功能
但上面的方法太局限了,没办法定制,而且也没有实际的意义。如何用特殊的注解来实现定制的功能呢,一般有两种方式:
还是用上面的方法,在注入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");}}}}
不依赖@Component,自定义扫描。所以就有了第二个例子。
自定义扫描
结构比较复杂,可以参考完整的例子,这里是关键的几个类
还是定义一个注解,只不过不再需要@Component了
@Target({ ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface CustomizeComponent {String value() default "";}
注解修饰的类
- @CustomizeComponent
- public class ScanClass1 {
- public void print() {
- System.out.println("scanClass1");
- }
- }
BeanScannerConfigurer用于嵌入到Spring的加载过程的中,这里用到了BeanFactoryPostProcessor 和ApplicationContextAware。
Spring提供了一些的接口使程序可以嵌入Spring的加载过程。这个类中的继承ApplicationContextAware接口,Spring会读取ApplicationContextAware类型的的JavaBean,并调用setApplicationContext(ApplicationContext applicationContext)传入Spring的applicationContext。
同样继承BeanFactoryPostProcessor接口,Spring会在BeanFactory的相关处理完成后调用postProcessBeanFactory方法,进行定制的功能。@Componentpublic 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");}}
- 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());
- }
- }
- 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);}}
- main函数
@Configurationpublic 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();}}
至此一个完整的例子就完成了,这里主要用到了BeanFactoryPostProcessor,ApplicationContextAware,FactoryBean等Spring内置的接口,来嵌入Spring的加载和使用过程,这样就实现了自定义注解,和自定义代理了。
原文链接:http://www.jianshu.com/p/7c2948f64b1c
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
深入Spring:自定义注解加载和使用的更多相关文章
- spring 自定义schema 加载异常 White spaces are required between publicId and systemId.
spring 项目启动报错 报错日志如下: Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreExcepti ...
- Spring BeanDefinition的加载
前面提到AbstractRefreshableApplicationContext在刷新BeanFactory时,会调用loadBeanDefinitions方法以加载系统中Bean的定义,下面将讲 ...
- Spring源码加载BeanDefinition过程
本文主要讲解Spring加载xml配置文件的方式,跟踪加载BeanDefinition的全过程. 源码分析 源码的入口 ClassPathXmlApplicationContext构造函数 new C ...
- Bean 注解(Annotation)配置(1)- 通过注解加载Bean
Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...
- 在Spring Boot中加载初始化数据
文章目录 依赖条件 data.sql文件 schema.sql 文件 @sql注解 @SqlConfig 注解 在Spring Boot中加载初始化数据 在Spring Boot中,Spring Bo ...
- 【SpringBoot 基础系列】实现一个自定义配置加载器(应用篇)
[SpringBoot 基础系列]实现一个自定义配置加载器(应用篇) Spring 中提供了@Value注解,用来绑定配置,可以实现从配置文件中,读取对应的配置并赋值给成员变量:某些时候,我们的配置可 ...
- Spring Boot 配置加载顺序详解
使用 Spring Boot 会涉及到各种各样的配置,如开发.测试.线上就至少 3 套配置信息了.Spring Boot 可以轻松的帮助我们使用相同的代码就能使开发.测试.线上环境使用不同的配置. 在 ...
- Spring源码加载过程图解(一)
最近看了一下Spring源码加载的简装版本,为了更好的理解,所以在绘图的基础上,进行了一些总结.(图画是为了理解和便于记忆Spring架构) Spring的核心是IOC(控制反转)和AOP(面向切面编 ...
- 【Spring】Junit加载Spring容器作单元测试(整理)
[Spring]Junit加载Spring容器作单元测试 阅读目录 >引入相关Jar包 > 配置文件加载方式 > 原始的用法 > 常见的用法 > 引入相关Jar包 一.均 ...
随机推荐
- phpcs,phpmd,phan安装部署,phpstorm配置phpunit
git参考地址:https://github.com/YunhanTech/overview/blob/master/php/learn-road.md phpcs 安装 composer globa ...
- Android OpenCV图像转换
1.Mat存储到本地: public void saveMatData(Mat mat) { File fileDir = new File(Environment.getExternalStorag ...
- ionic + cordova 环境搭建
1.安装nodejs:官网下载安装包,双击安装即可.成功后在控制台输入node -v 显示版本号即成功. 2.安装Java,配置环境变量,下载安卓sdk ,配置环境变量 ANDROID_HOME 为s ...
- c++ 继承,组合
.什么是继承 A继承B,说明A是B的一种,并且B的所有行为对A都有意义 eg:A=WOMAN B=HUMAN A=鸵鸟 B=鸟 (不行),因为鸟会飞,但是鸵鸟不会. .什么是组合 若在逻辑上A是B的“ ...
- The Best Hacking Tools
The Best Hacking Tools Hacking Tools : List of security tools specifically aimed toward security pro ...
- 构造函数强制使用new
function Car(model, year, miles) { if (!(this instanceof Car)) { return new Car(model, year, miles); ...
- Lucene索引数计算
Elasticsearch默认在创建索引结束时得到5个分片及1个副本: 分片是有0-n个副本,“5个分片及1个副本”即“5个分片及5个相应分片副本”:共10个Lucene索引 副本数:指的是“单个分片 ...
- JLable设置复制粘贴
final JLabel keyLable = new JLabel(key); keyLable.addMouseListener(new MouseAdapter() { @Override pu ...
- 不同格式的ip 统一转成ip列表
支持以下格式的ip地址: 192.168.1.0/24 192.168.1.1-23 192.168.1.123 代码如下: package finder; import java.net.InetA ...
- 内网IP&外网IP/NAT
内网的计算机以NAT(网络地址转换)协议,通过一个公共的网关访问Internet.内网的计算机可向Internet上的其他 计算机发送连接请求,但Internet上其他的计算机无法向内网的计算机发送连 ...