Spring中自定义注解的解析过程-学习

2023-05-16

Spring中自定义注解的解析过程-学习

在学习spring源码的过程中,最好奇的一件事就是Sprint的注解是怎么被读取到的,又是怎么进行解析的。然后又是怎么将注解的内容注入到spring容器中的。带着这个好奇心,写了一个小的Spring的自定义注解的demo,记录一下,怕自己忘记!

  1. 在spring中,对于注解的解析,有一个非常重要的类就是ConfigurationClassPostProcessor,它是BeanDefinitionRegistryPostProcessor的其中一个实现类。关于后置处理器的暂时不记录,只要知道它是一个后置处理器,暂时就可以了。他是在容器启动时会被调用的一个后置处理器,调用的方法是postProcessBeanDefinitionRegistry,在这个方法中调用了processConfigBeanDefinitions方法。
@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		int registryId = System.identityHashCode(registry);
		if (this.registriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
		}
		if (this.factoriesPostProcessed.contains(registryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + registry);
		}
		this.registriesPostProcessed.add(registryId);
		// 重要的方法在这个地方,调用这个方法,然后进行处理
		processConfigBeanDefinitions(registry);
	}
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		// ..... 省略一部分暂时不需要关心的代码
		// Parse each @Configuration class
		// 注解解析入口 解析每一个@Configuration标注的配置类
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			// 最终调用的是这个方法,在这个方法里面进行注解的解析以及相应的处理
			parser.parse(candidates);
	// ...... 省略
}
  1. 知道注解的解析,然后开始自己定义一个自定义注解TanxiiValue
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TanxiiValue {
	String value() default "";
}
  1. 不采用上面的解析,我想自己写一个后置处理器来处理这个注解相关的逻辑,因此需要准备一个ApplicationContext、BeanFactoryPostProcessor以及一个BeanPostProcessor
public class TanxiiApplicationContext extends AnnotationConfigApplicationContext {
	public TanxiiApplicationContext(Class<MyConfig> myConfigClass) {
		super(myConfigClass);
	}

	@Override
	protected void initPropertySources() {
		// abc 属性必填 通过系统参数的形式获取
		this.getEnvironment().setRequiredProperties("abc");
	}

	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		System.out.println("Application的postProcessBeanFactory");
	}

	@Override
	public void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor) {
		Assert.notNull(postProcessor,"postProcessor is must not null");
		super.addBeanFactoryPostProcessor(postProcessor);
	}
}
//BeanFactoryPostProcessor 这里实现了一个Aware接口 目的是为了在容器启动的时候为当前类设置一个ApplicationContext,我需要通过这个context来获取一些必要属性,具体每一行代码干什么用的,都写好了注释,可以参考一下
@Component
public class TanxiiBeanFactoryPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {

	private BeanDefinitionRegistry registry;

	private ApplicationContext applicationContext;

	@Override
	public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if(beanFactory instanceof BeanDefinitionRegistry){
			System.out.println("======BeanDefinitionRegistry");
			this.registry = (BeanDefinitionRegistry) beanFactory;
		}
		// 获取扫描的包 这个地方有一个限制,就是加了注解的类必须在applicationContext的同级包或者子包里面 不然遍历不到
		String packageName = this.applicationContext.getClass().getPackageName();
		// 拼接待扫描的包的表达式
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				packageName.replaceAll("\\.","/") + '/' + "**/*.class";
		// 构造一个reader工厂出来,目的是为了方便构造一个beandefinition出来
		CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
		try {
			// 扫描符合packageSearchPath 路径下的所有类
			Resource[] resources = this.applicationContext.getResources(packageSearchPath);
			// 遍历所有类
			for (Resource resource : resources) {
				// 将类变成一个reader 给后面构成beandefinition使用
				MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
				// 构造beandefinition
				ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
				// BeanNameGengrator bean的名称生成器
				AnnotationBeanNameGenerator annotationBeanNameGenerator = new AnnotationBeanNameGenerator();
				// 设置资源
				sbd.setSource(resource);
				// 设置单例
				sbd.setScope(BeanDefinition.SCOPE_SINGLETON);
				// 获取注解
				AnnotationMetadata metadata = sbd.getMetadata();
				// 获取指定注解的方法 主要的地方,在这里获取到了被TanxiiValue修饰的类
				// 因为被TanxiiValue修饰的类没有@Component注解,因此在spring启动的时候不会将该类扫描到SPring容器中,需要手动进行注册。这里就是我的目的,获取被注解的类,将其注入到spring容器中
				if(metadata.isAnnotated(TanxiiValue.class.getName())){
					// 构造一个BeanDefinitionHolder 然后将其注册到spring容器中
					BeanDefinitionHolder holder = new BeanDefinitionHolder(sbd,annotationBeanNameGenerator.generateBeanName(sbd,this.registry));
					BeanDefinitionReaderUtils.registerBeanDefinition(holder,this.registry);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	// 获取spring容器中的applicationContext对象
	@Override
	public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}
// beanPostProcessor 在bean实例化并已经进行属性填充之后,在执行初始化方法的前后进行调用处理的地方,因此,我们自定义的注解在bean初始化前后都可以进行相应的个性化处理 这里只是写了一个测试的方法,勉强够看
@Component
public class TanxiiBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		Annotation[] annotations = bean.getClass().getAnnotations();
		for (Annotation annotation : annotations) {
			if(annotation instanceof TanxiiValue){
				TanxiiValue tanxiiValue = bean.getClass().getAnnotation(TanxiiValue.class);
				System.out.println("获取到注解的值" + tanxiiValue.value());
			}
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("BeanPostProcessor======after " + beanName);
		Annotation[] annotations = bean.getClass().getAnnotations();
		for (Annotation annotation : annotations) {
			if(annotation instanceof TanxiiValue){
				TanxiiValue tanxiiValue = bean.getClass().getAnnotation(TanxiiValue.class);
				System.out.println("获取到注解的值" + tanxiiValue.value());
			}
		}
		return bean;
	}
}
  1. 测试,需要注意的是,在TanxiiBeanFactoryPostProcessor类中,我是获取的applicationcontext的包路径,这个地方就有一个限制,测试的类必须在applicationcontext的同级或者下级包中
@TanxiiValue(value = "正好做一个注解值获取的测试")
public class TestAnnotation {
	@Autowired
	public UserService userService;
	public void printf(){
		System.out.println(userService.orderService.getHello());
	}
}
  1. 启动spring容器
public class MyFirstSpringApplication {
	public static void main(String[] args) {
		ApplicationContext context = new TanxiiApplicationContext(MyConfig.class);
//		OrderService orderService = (OrderService) context.getBean("testAnnotation");
		TestAnnotation testAnnotation = (TestAnnotation) context.getBean("testAnnotation");
		testAnnotation.printf();
		System.out.println(context.getBean("testAnnotation"));
//		System.out.println("====================orderServiceBefore");
//		System.out.println(orderService.getHello());
//		System.out.println("====================orderServiceAfter");
	}
}

可以发现被TanxiiValue注解修饰的类也可以正常从Spring容器中获取。当前注解可以被正常解析。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring中自定义注解的解析过程-学习 的相关文章

随机推荐

  • Tesla T4 在Ubuntu18.04上的安装使用

    Tesla T4 在Ubuntu18 04上的安装使用 在默认情况下 xff0c 安装 tesla T4 的驱动 xff0c 尝试很多版本都无法正确安装 xff0c 安装完之后会出现 xff1a nvidia smi NVIDIA SMI
  • cannot import name ‘gcd’ from ‘fractions’

    cannot import name gcd from fractions 在这里插入图片描述 在3 9
  • 【Java 实战】通过Redis 和 MQ 简单实现秒杀功能

    项目场景 实现一个商品秒杀的功能 能后台自定义秒杀时间段 商品库存等信息 一 设计思路 这里简单分享下思路 1 限流 秒杀时大量用户会在同一时间同时进行抢购 网站瞬时访问流量激增 由于只有少部分用户能够秒杀成功 所以要限制大部分流量 只允许
  • mariadb设置默认字符编码

    mariadb version 10 3 7 mariadb安装目录下找到data文件夹 xff0c 下面有个my ini文件 xff0c 该文件为mariadb服务启动加载文件 由于之前各种原因 xff0c 也搜索过博文 xff0c 但是
  • 多线程编程以及线程池相关记录

    Java 中经常需要用到多线程来处理一些业务 xff0c 非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程 xff0c 那样势必有创建及销毁线程耗费资源 线程上下文切换问题 同时创建过多的线程也可能引发资源耗尽
  • Spring Application Context 注入问题

    熟悉spring bean生命周期的都知道 xff0c 在其生命周期中有个很重要的接口 xff1a Aware 如果要注入application xff0c 可以用如下方式 64 Component public class SpringC
  • 解决我遇到的apt-get ,(Depends: AAA(>= BBB)but it is not going to install)的问题

    因为我直接把ubuntu16的源复制给了18的系统 xff0c 报错 xff0c 网上查说要换源我没懂啥意思 原来每个版本的源不一样 xff0c 这里贴一下清华的16和18的源 修改源文件source list xff08 1 xff09
  • IIS 网页在每天第一次打开慢的原因及解决

    今天碰到一个问题 xff0c 我们的网页在早晨的时候打开总是很慢 xff0c 其它时间有时也比较慢 开始认为是服务器性能的问题 xff0c 监测服务器资源使用后 xff0c 发现服务器的资源没有在高水平运行 xff0c 可以确定不是服务器硬
  • 【考研】操作系统——高速缓存与缓冲区、设备分配的数据结构

    一 磁盘高速缓存 操作系统中使用磁盘高速缓存技术是指利用内存中的存储空间来暂存从磁盘中读出的一系列盘块中的信息 xff0c 用以提高磁盘的 I O 速度 xff0c 对高速缓存复制的访问要比原始数据访问更为高效 二 缓冲区 xff08 一
  • Linux 系统的centos8安装jdk 1.8 教程

    卸载openjdk 查看rpm qa grep jdk 卸载yum y remove java 1 7 0 openjdk 1 7 0 75 2 5 4 2 el7 方法一 安装包手动安装 1 下载并上传 官网下载 我的备份 将我们下载好的
  • 解决Matlab2014A在win10下字体模糊的问题

    右击快捷方式 xff0c 进入属性设置 兼容性 更高高DPI设置 高DPI缩放替代 打钩 xff0c 下拉菜单选择应用程序
  • android工程师开发IOS oc浅析(14)之MRC与ARC

    1 为什么要进行内存管理 管理的是什么 内存管理 管理的是任何继承自NSObject的对象 因为一般的其他基本数据类型的局部变量都是储存在栈区的 当代码块执行结束 代码块中的局部变量出作用于就会被回收 而OC对象则不一样 OC对象类型是程序
  • Github个人主页绑定域名实操

    由于笔者在上传文件到github仓库 xff0c 由于相关操作经常会间歇性遗忘 xff08 还是不够熟悉惹的祸啊 xff09 xff0c 或者部分解决方案里并没有明确说明指令所表示的含义 xff0c 所以在百度了许多许多的相关文章和blog
  • spring 注解作用与解析过程

    64 PostConstruct 作用 xff1a 初始化方法之前执行 xff0c 作用于方法 xff0c 无方法上修饰符限制 解析过程 xff1a CommonAnnotationBeanPostProcessor xff08 BeanF
  • 关于Goland调试不可以使用

    今天在使用Goland时发现Goland断点调试无法使用 如下图所示 在找了半天之后在某论坛找到了原因 结果是360把Go的程序截拦导致无法断点调试 关闭360 重新打开文件即可使用
  • MySQL导入含有触发器的sql脚本报错解决方案

    报错码 ERROR 1419 HY000 You do not have the SUPER Privilege and Binary Logging is Enabled 解决方案 1 Linux下执行sql脚本 我们一般在linux服务
  • maven 本地仓库的配置以及如何修改默认.m2仓库位置

    本人转载于http blog csdn net qq 27093465 article details 52957253 以下为转载内容 xff1a 本地仓库是远程仓库的一个缓冲和子集 xff0c 当你构建Maven项目的时候 xff0c
  • onNewIntent 作用

    当Activity启动模式为singleTask时 如果在栈中已经有该Activity的实例 xff0c 就重用该实例 会调用实例的onNewIntent 不会调用onCreate方法 重用时 xff0c 会让该实例回到栈顶 xff0c 因
  • xml与txt文件格式互换

    当前遇到一个问题 xff0c 需要将txt格式的文件转换为xml格式的文件 xff0c 网上找了挺多的方法 xff0c 也成功了 但用时比较麻烦 xff0c 考虑到后期程序的需要 xff0c 决定开发一个小程序 耗时两个半天 xff0c 终
  • Spring中自定义注解的解析过程-学习

    Spring中自定义注解的解析过程 学习 在学习spring源码的过程中 xff0c 最好奇的一件事就是Sprint的注解是怎么被读取到的 xff0c 又是怎么进行解析的 然后又是怎么将注解的内容注入到spring容器中的 带着这个好奇心