likes
comments
collection
share

业务类无法被AOP代理问题

作者站长头像
站长
· 阅读数 52

背景

在项目里面引入了shiro框架,然而在用户登录的时候始终会出现数据库访问异常,异常信息如下:

org.springframework.jdbc.BadSqlGrammarException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Table 'ems.sys_user_manager' doesn't exist
### The error may exist in com/greenet/platform/common/mapper/UserMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT  account,password,grouptype,account_tmtype,email,notes  FROM sys_user_manager     WHERE account = ?
### Cause: java.sql.SQLSyntaxErrorException: Table 'ems.sys_user_manager' doesn't exist
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Table 'ems.sys_user_manager' doesn't exist
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:234) ~[spring-jdbc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) ~[spring-jdbc-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) ~[mybatis-spring-1.3.2.jar:1.3.2]
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446) ~[mybatis-spring-1.3.2.jar:1.3.2]
	at com.sun.proxy.$Proxy63.selectOne(Unknown Source) ~[na:na]
	at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166) ~[mybatis-spring-1.3.2.jar:1.3.2]
	at com.baomidou.mybatisplus.core.override.PageMapperMethod.execute(PageMapperMethod.java:101) ~[mybatis-plus-core-3.0.7.1.jar:3.0.7.1]
	at com.baomidou.mybatisplus.core.override.PageMapperProxy.invoke(PageMapperProxy.java:64) ~[mybatis-plus-core-3.0.7.1.jar:3.0.7.1]
	at com.sun.proxy.$Proxy64.selectOne(Unknown Source) ~[na:na]
	at com.greenet.platform.common.service.impl.UserServiceImpl.findUserByUsername(UserServiceImpl.java:48) ~[classes/:na]
	at com.greenet.platform.config.UserRealm.doGetAuthenticationInfo(UserRealm.java:47) ~[classes/:na]

从异常日志来看是找错了数据库,因为项目中使用了多数据源技术,通过在service层加上@DS(Database.DATABASE_CA_SYSTEM)注解来标示该service需要使用哪个数据源。这个异常说明注解并没有实现它该有的作用,导致数据源没有切换成功。

多数据源使用的是baomidou开发的框架,相关依赖如下,它的核心是通过AOP技术来切换每个service需要使用的数据源,具体实现这里先不讨论。

<dependency>    
    <groupId>com.baomidou</groupId>    
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId> 		    
</dependency>

上面的异常说明userService这个bean并没有被代理,那么问题出来哪里呢?

问题定位

springboot的启动日志中发现一个值得注意的点:

Warn: Could not find @TableId in Class: com.greenet.platform.common.entity.User. 2019-11-14 15:50:42.487 INFO 17752 --- [ main] trationDelegate业务类无法被AOP代理问题BeanPostProcessorChecker : Bean 'userMapper' of type [com.sun.proxy.业务类无法被AOP代理问题BeanPostProcessorChecker : Bean 'userService' of type [com.greenet.platform.common.service.impl.UserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2019-11-14 15:50:42.555 INFO 17752 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'cacheManager' of type [org.apache.shiro.cache.ehcache.EhCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2019-11-14 15:50:42.556 INFO 17752 --- [ main] o.a.shiro.cache.ehcache.EhCacheManager : Using existing EHCache named [passwordRetryCache

重点在这句话:

trationDelegate$BeanPostProcessorChecker : Bean 'userService' of type [com.greenet.platform.common.service.impl.UserServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

这句话意思是userService由于不符合某种条件,导致不会被自动代理,那么这个条件是什么呢?

查看BeanPostProcessorChecker的源码,打印日志的地方如下:

@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {   
    if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&         this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {      
        if (logger.isInfoEnabled()) {    
            logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +               "] is not eligible for getting processed by all BeanPostProcessors " +               "(for example: not eligible for auto-proxying)");      }   }   
    return bean;
}

日志打印出来的原因是this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount返回了true,也就是说此时注册到beanFactory的后置处理器数量是少于总的后置处理器数量的,也就是说这个时候有其他后置处理器还没准备好,userService就已经被实例化了;

既然这个地方说了userService不会被代理,那么这个bean又是在什么时候被spring实例化的呢。想搞清楚这个问题很简单,可以在AbstractBeanFactory.doGetBean方法里面打一个条件断点,然后看调用栈。

业务类无法被AOP代理问题

得到如下调用栈:

"main@1" prio=5 tid=0x1 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:242)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
	  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
	  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
	  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	  at org.springframework.beans.factory.support.AbstractBeanFactory?Lambda$121.1862994526.getObject(Unknown Source:-1)
	  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	  - locked <0x12b7> (a java.util.concurrent.ConcurrentHashMap)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1247)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
	  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
	  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
	  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getSingletonFactoryBeanForTypeCheck(AbstractAutowireCapableBeanFactory.java:991)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:865)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:574)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:514)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:477)
	  at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:227)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1411)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1210)
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
	  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:857)
	  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:760)
	  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:509)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1305)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1144)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
	  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
	  at org.springframework.beans.factory.support.AbstractBeanFactory?Lambda$121.1862994526.getObject(Unknown Source:-1)
	  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
	  at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:228)
	  at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:721)
	  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:534)
	  - locked <0x12ea> (a java.lang.Object)
	  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
	  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
	  at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	  at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
	  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
	  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
	  at com.greenet.platform.Application.main(Application.java:24)

从调用栈可以看到两个重点信息:

  • 这个栈里面出现了三次AbstractBeanFactory.doGetBean,说明userService是在实例化其他bean的时候由于依赖注入而实例化的;
  • 栈的入口是 AbstractApplicationContext.registerBeanPostProcessors

这两点说明userService不是在正常的AbstractApplicationContext.finishBeanFactoryInitialization阶段通过实例化,而是在注册后置处理器的时候就实例化了。

为什么会提前实例化呢?

MethodValidationPostProcessor

从上面打的断点回头追溯到registerBeanPostProcessors的地方,可以看到此时注册的后置处理器MethodValidationPostProcessor,调试截图如下:

业务类无法被AOP代理问题

是对应的代码如下:

	for (String ppName : orderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}

也就是说userServiceMethodValidationPostProcessor这个后置处理器注册前的实例化的过程中,因为某种原因被提前实例化了。

那么到底是为啥实例化MethodValidationPostProcessor的时候需要实例化userService

通过一波调试发现,问题出来我的shiro配置里面

  @Bean("securityManager")
    public SecurityManager securityManager(UserRealm userRealm,
                                           SessionManager sessionManager,
                                           EhCacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRememberMeManager(null);
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }


    /**
     * shiro权限验证
     *
     * @param securityManager 安全管理器
     * @return 安全管理factorybean
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl("/login");

        Map<String, String> filterMap = new LinkedHashMap<>();

        filterMap.put("/static/**", "anon");
        filterMap.put("/", "anon");
        filterMap.put("/login/getVerifyCodeImage", "anon");

        filterMap.put("/**", "authc");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter;
    }

罪魁祸首是这个ShiroFilterFactoryBean,这个东西的定义如下:

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {

它实现了FactoryBean, BeanPostProcessor这两个特殊的接口,所有的FactoryBean会在后置处理器MethodValidationPostProcessor实例化过程中被实例化,为啥呢,这跟这个后置处理器的定义的地方有关系,这个后置处理器在ValidationAutoConfiguration类中定义,具体方法代码如下:

	@Bean
	@ConditionalOnMissingBean
	public static MethodValidationPostProcessor methodValidationPostProcessor(
			Environment environment, @Lazy Validator validator) {
		MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
		boolean proxyTargetClass = environment
				.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
		processor.setProxyTargetClass(proxyTargetClass);
		processor.setValidator(validator);
		return processor;
	}

想要实例化这个类,需要知道这个方法的参数,问题就出在这个Environment里面,为了找到类型为Environment的参数,spring会遍历整个beanDefinitionNames,然后去找到符合Environment这个类型的bean,找的过程中发现有些beanfactoryBean,那不好意思,需要把这个factoryBean给实例化出来,再看上面ShiroFilterFactoryBean定义的方法可以看到,它依赖了securityManager,而securityManager又依赖了userRealm,而userRealm又依赖了userService:

@Component
public class UserRealm extends AuthorizingRealm {

    private static final String ACCOUNT_TYPE_WEB = "2";
    private static final String ACCOUNT_TYPE_ALL = "255";

    @Autowired
    UserService userService;

就这样一步步把userService给实例化了,这个时候还没到后置处理器处理bean的时候,那负责做AOP代理的后置处理器自然无法再去处理userService了。

解决方案

那么如何去解决这个问题呢,思路是不要在factoryBean里面去注入业务类。一种比较简单的办法是去注入ApplicationContext,然后通过getBean方法拿到业务的service

  private UserService getUserService() {
        return (UserService) applicationContext.getBean("userService");
    }
转载自:https://juejin.cn/post/6844903997774495752
评论
请登录