Spring AOP对编程注册的NamedParameterJdbcTemplate Bean不生效如何解决
问题原因
- 你手动调用
registerSingleton()注册NamedParameterJdbcTemplate实例时,只是把裸对象直接存入Spring容器,没有经过Spring Bean生命周期的BeanPostProcessor处理链,而AOP切面的代理逻辑正是由负责AOP的后置处理器(比如AnnotationAwareAspectJAutoProxyCreator)在Bean初始化阶段完成代理生成的,所以这些手动注册的实例没有被切面包裹,自然无法被拦截。 - 你原来单个Bean的时候是通过
@Bean注解声明的,Spring会完整走完Bean的初始化、后置处理流程,所以会自动生成代理对象,切面生效。
修复方案
两种方案任选其一即可:
方案1:手动对实例应用后置处理器再注册
在注册单例之前,先调用initializeBean方法让Spring走完完整的Bean生命周期,再注册进容器,修改配置代码如下:
@Bean public Map<String, NamedParameterJdbcTemplate> namedParameterJdbcTemplates() { ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); Map<String, NamedParameterJdbcTemplate> templates = new HashedMap<>(); dataSources().forEach((key, value) -> { // 1. 创建原始实例 NamedParameterJdbcTemplate rawTemplate = new NamedParameterJdbcTemplate(value); // 2. 应用所有Bean后置处理器,完成AOP代理生成 NamedParameterJdbcTemplate proxiedTemplate = (NamedParameterJdbcTemplate) beanFactory.initializeBean(rawTemplate, key); // 3. 注册代理后的实例为单例 beanFactory.registerSingleton(key, proxiedTemplate); // 4. 存入Map的是代理对象 templates.put(key, proxiedTemplate); }); return templates; }
方案2:注册BeanDefinition交由Spring管理生命周期
这种方式更符合Spring规范,不需要手动处理代理逻辑,只需要给每个数据源注册对应的Bean定义,由Spring自行完成实例化、代理增强流程:
@Bean public Map<String, NamedParameterJdbcTemplate> namedParameterJdbcTemplates(ApplicationContext applicationContext) { DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); Map<String, DataSource> dataSourceMap = dataSources(); Map<String, NamedParameterJdbcTemplate> templates = new HashedMap<>(); dataSourceMap.forEach((key, dataSource) -> { // 构造Bean定义 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(NamedParameterJdbcTemplate.class); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(dataSource); // 注册Bean定义 beanFactory.registerBeanDefinition(key, beanDefinition); // 从容器获取的就是已经完成代理增强的实例 templates.put(key, applicationContext.getBean(key, NamedParameterJdbcTemplate.class)); }); return templates; }
验证说明
修改完成后,你可以通过断点查看Map中存储的NamedParameterJdbcTemplate实例,如果类名带有CGLIB或者JDK动态代理标识,说明代理已经生成成功,切面即可正常拦截方法调用。
内容的提问来源于stack exchange,提问作者Frankie Drake
相关产品推荐
相关产品推荐

