Spring LTW 切面 Configurable autowire null 自动注入失败的原因及解决方案
本文主要介绍使用spring+aspectj使用动态织入方法实现Aop,以及遇到的问题及解决方案。
基于jdk1.8版本、spring-5.2.5版本、aspectj-1.9.5版本、bytebuddy-1.10.9版本
动态织入的实现方式有两种
1. 在jvm启动参数中加入-agent xxx;不再详述,自行网上搜索;缺点:开发环境配置繁琐
2. 在项目启动的Main方法中,利用bytebuddy的实现agent代理
net.bytebuddy byte-buddy-agent 1.10.9
在Main方法的开头插入以下代码,即可在程序中实现agent功能
Instrumentation instrumentation = ByteBuddyAgent.install();Agent.agentmain("", instrumentation);InstrumentationSavingAgent.agentmain("", instrumentation);
启用Aspectj作为aop实现(优点:可以为任意方法设置切面),直接上代码
基于全注解方式实现
配置类
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
@EnableSpringConfigured
@ComponentScan(basePackages = "zhibo")
public class AppConfig extends WebMvcConfigurationSupport {@Beanpublic DemoProcessor demoProcessor() {return new DemoProcessor();}
}
切面类:开启LTW后,@Aspect标记的会被spring创建但不能被管理,所以不能通过@Autowire注入依赖,要想使用该功能,必须同时加上@Configurable注解
@Aspect
@Configurable
public class DemoAspectj {private Bean2 bean2;public DemoAspectj() {System.out.println();}@Autowiredpublic void setBean2(Bean2 bean2) {this.bean2 = bean2;}@Around("@annotation(demoAnn)")public void aop(ProceedingJoinPoint joinPoint,DemoAnn demoAnn) throws Throwable {System.out.println("参数:"+Arrays.toString(joinPoint.getArgs()));bean2.say(String.valueOf(joinPoint.getArgs()[0]));}
}
配置aop:META-INF/aop.xml
正常来说,通过以上的配置,即可实现spring LTW 方法的AOP了,并且Bean2可以被注入;
但是,这里有个巨坑,有时会出现DemoAspectj中的bean2没有被注入,一旦执行aop方法就会报空指针异常。
经过排查,发现是由于spring 加载bean的顺序不确定导致的(有4个spring 的bean会最先加,其余的spring bean或者用户自定义的bean的加载顺序完全不确定,其中就包括了用于动态织入的bean:org.springframework.context.config.internalBeanConfigurerAspect)。
也就是说,如果org.springframework.context.config.internalBeanConfigurerAspect这个bean还没有被spring加载的情况下,已经被加载的bean一旦调用了被aop的方法(比如在初始化方法中调用到被aop的方法),那么必定会报空指针异常。
找到问题的原因了,那么解决方案就出来咯:优先加载所有的spring bean,最后加载我们自己定义的bean
public class DemoProcessor implements BeanPostProcessor,BeanDefinitionRegistryPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();Stream.of(beanDefinitionNames).forEach(System.err::println);}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {String[] beanDefinitionNames = registry.getBeanDefinitionNames();Map zhiboBeans = new LinkedHashMap<>();//1. 删除我们自己定义的bean,目的是为让spring相关bean靠前Stream.of(beanDefinitionNames).forEach(beanName->{BeanDefinition bd = registry.getBeanDefinition(beanName);//这里的[zhibo.]需要替换为实际的包名if(bd.getBeanClassName()!=null && bd.getBeanClassName().startsWith("zhibo.")) {registry.removeBeanDefinition(beanName);zhiboBeans.put(beanName, bd);}});//2. 重新将我们自定义的bean注册到spring容器中zhiboBeans.forEach((k,v)->registry.registerBeanDefinition(k, v));}
}
以下是我用于排查和验证猜测的代码,如果大家把DemoProcessor中的postProcessBeanDefinitionRegistry方法体注释掉之后再执行,必定会报空指针异常;
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
@EnableSpringConfigured
@ComponentScan(basePackages = "zhibo")
public class AppConfig extends WebMvcConfigurationSupport {@Beanpublic DemoProcessor demoProcessor() {return new DemoProcessor();}
}@Component
public class Bean1 {public void say(String msg) {System.out.println(this.getClass().getSimpleName()+"-->"+msg);}
}@Component
public class Bean2 {public void say(String msg) {System.out.println(this.getClass().getSimpleName()+"-->"+msg);}
}@Component
public class Bean3 {public void say(String msg) {System.out.println(this.getClass().getSimpleName()+"-->"+msg);}
}@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DemoAnn {}@Aspect
@Configurable
public class DemoAspectj {private Bean2 bean2;public DemoAspectj() {System.out.println();}@Autowiredpublic void setBean2(Bean2 bean2) {this.bean2 = bean2;}@Around("@annotation(demoAnn)")public void aop(ProceedingJoinPoint joinPoint,DemoAnn demoAnn) throws Throwable {System.out.println("参数:"+Arrays.toString(joinPoint.getArgs()));bean2.say(String.valueOf(joinPoint.getArgs()[0]));}
}public class DemoProcessor implements BeanPostProcessor,BeanDefinitionRegistryPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();Stream.of(beanDefinitionNames).forEach(System.err::println);}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {String[] beanDefinitionNames = registry.getBeanDefinitionNames();Map zhiboBeans = new LinkedHashMap<>();//1. 删除我们自己定义的bean,目的是为让spring相关bean靠前Stream.of(beanDefinitionNames).forEach(beanName->{BeanDefinition bd = registry.getBeanDefinition(beanName);//这里的[zhibo.]需要替换为实际的包名if(bd.getBeanClassName()!=null && bd.getBeanClassName().startsWith("zhibo.")) {registry.removeBeanDefinition(beanName);zhiboBeans.put(beanName, bd);}});//2. 重新将我们自定义的bean注册到spring容器中zhiboBeans.forEach((k,v)->registry.registerBeanDefinition(k, v));}
}@Component
public class People1 {@DemoAnnpublic void say(String msg) {System.out.println(getClass().getSimpleName()+":"+msg);}
}@Component
public class People2 {//模拟org.springframework.context.config.internalBeanConfigurerAspect未被创建时,就开始执行aop方法,已验证bean2是否被注入@PostConstructpublic void init() {say("初始化");}@DemoAnnpublic void say(String msg) {System.out.println(getClass().getSimpleName()+":"+msg);}
}public class Run {public static AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();public static void main(String[] args) {Instrumentation instrumentation = ByteBuddyAgent.install();Agent.agentmain("", instrumentation);InstrumentationSavingAgent.agentmain("", instrumentation);MockServletConfig config = new MockServletConfig(new MockServletContext(Run.class.getResource("/").getFile(),new FileSystemResourceLoader()),"demo");context.setServletConfig(config);context.register(AppConfig.class);context.refresh();context.getBean(People1.class).say("我去");context.close();}
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
