Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 282|回复: 0

SpringBoot+Shiro引起事务失效、错误原因、解决方法

[复制链接]
  • TA的每日心情
    奋斗
    3 天前
  • 签到天数: 789 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722638
    发表于 2021-6-5 06:53:59 | 显示全部楼层 |阅读模式

    一、问题
    今天发现用户注册的Service的事务并没有起到作用,再抛出一个RuntimeException后,并没有发生回滚,下面是调试步骤:

    1、检查数据库的引擎是否是innoDB

    2、启动类上是否加入@EnableTransactionManagement注解

    3、是否在方法上加入@Transactional注解或Service的类上是否有@Transactional注解

    4、方法是否为public

    5、是否是因为抛出了Exception等checked异常

    然而事务失效都不是这些原因引起的,并且发现其他Service的事务都可以正常使用。在查看打印的异常调用链的时候,发现这个Service是没有被AOP代理过的,所以推测可能是因为其他整合Spring的框架提前引用了这个Service。

    为了验证,新建了一个Service,并且把代码copy到新建的类中,测试其事务,发现事务可以正常使用,下面是打印的异常信息:

     

    从上面可以明显看到,Spring为这个service生成了代理类,证明事务是可以正常使用的,并且原service的失效应该是其他的整合Spring的框架提前引用造成的。

    因为项目还使用了Shiro作为权限管理,并且在编写Shiro的自定义验证器Realm中引用了该UserService,后来把Realm中的Service换成了Mapper后,该Service的事务可以正常使用了。

     

    错误原因:
    Spring中事务是通过AOP创建代理对象来完成的,有BeanFactoryTransactionAttributeSourceAdvisor完成对需要事务的方法织入对事务的处理。完成创建AOP代理对象的功能由一个特殊的BeanPostProcessor完成--AnnotationAwareAspectJAutoProxyCreator。该类实现了BeanPostProcessor接口,在bean创建完成并将属性设置好之后,拦截bean,并创建代理对象,在原对象的方法功能上添加增强器中增强方法的处理。对于事务增强器BeanFactoryTransactionAttributeSourceAdvisor而言,也就是在原有方法上加入事务的功能。

    但是,在ApplicationContext刷新上下文过程(refresh)中,上下文会调用registerBeanPostProcessors方法将BeanFactory中的所有BeanPostProcessor后处理器注册到BeanFactory中,使其后面流程中创建bean的时候生效。下面是其实现源码:

    public static void registerBeanPostProcessors(
    ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

    // Register BeanPostProcessorChecker that logs an info message when
    // a bean is created during BeanPostProcessor instantiation, i.e. when
    // a bean is not eligible for getting processed by all BeanPostProcessors.
    int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

    // Separate between BeanPostProcessors that implement PriorityOrdered,
    // Ordered, and the rest.
    List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    List<String> orderedPostProcessorNames = new ArrayList<>();
    List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    for (String ppName : postProcessorNames) {
    if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    priorityOrderedPostProcessors.add(pp);
    if (pp instanceof MergedBeanDefinitionPostProcessor) {
    internalPostProcessors.add(pp);
    }
    }
    else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    orderedPostProcessorNames.add(ppName);
    }
    else {
    nonOrderedPostProcessorNames.add(ppName);
    }
    }

    // First, register the BeanPostProcessors that implement PriorityOrdered.
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

    // AnnotationAwareAspectJAutoProxyCreator实现了Ordered接口,所以会在这里排序
    // 但是,实现Ordered接口的BeanPostProcessor中,有一个是MethodValidationPostProcessor
    List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
    for (String ppName : orderedPostProcessorNames) {
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    orderedPostProcessors.add(pp);
    if (pp instanceof MergedBeanDefinitionPostProcessor) {
    internalPostProcessors.add(pp);
    }
    }
    sortPostProcessors(orderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, orderedPostProcessors);

    // Now, register all regular BeanPostProcessors.
    List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
    for (String ppName : nonOrderedPostProcessorNames) {
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    nonOrderedPostProcessors.add(pp);
    if (pp instanceof MergedBeanDefinitionPostProcessor) {
    internalPostProcessors.add(pp);
    }
    }
    registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

    // Finally, re-register all internal BeanPostProcessors.
    sortPostProcessors(internalPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, internalPostProcessors);

    // Re-register post-processor for detecting inner beans as ApplicationListeners,
    // moving it to the end of the processor chain (for picking up proxies etc).
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    }
    上面流程为:

    实例化所有实现了PriorityOrdered接口的BeanPostProcessor,排序后注册到BeanFactory
    实例化所有实现了Ordered接口的BeanPostProcessor,排序后注册到BeanFactory
    实例化剩余的BeanPostProcessor,注册到BeanFactory
    注册内部使用的BeanPostProcessor
    添加一个特殊的后处理器--ApplicationListenerDetector
    由于AnnotationAwareAspectJAutoProxyCreator实现了Ordered接口,所以会在第2步中注册到BeanFactory,然后生效,可以拦截bean创建并生成代理对象。但是,在其注册前,有一个同样实现了Ordered接口的MethodValidationPostProcessor。在该类的实例化过程中,会由ValidationAutoConfiguration通过工厂方法来创建,创建过程中,需要传入Environment对象作为参数,然后Spring会从BeanFactory中查找所有符合Environment类型的bean,下面是查询过程:

    //type为需要的参数,类型为Environment
    private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
    List<String> result = new ArrayList<>();

    //遍历BeanFactory中保存的所有beanName
    for (String beanName : this.beanDefinitionNames) {
    if (!isAlias(beanName)) {
    try {
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    // 检查是否是合格的bean
    if (!mbd.isAbstract() && (allowEagerInit ||
    (mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
    !requiresEagerInitForType(mbd.getFactoryBeanName()))) {
    // 是否是FactoryBean.
    boolean isFactoryBean = isFactoryBean(beanName, mbd);
    BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
    boolean matchFound =
    (allowEagerInit || !isFactoryBean ||
    (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
    (includeNonSingletons ||
    (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
    isTypeMatch(beanName, type);
    if (!matchFound && isFactoryBean) {
    beanName = FACTORY_BEAN_PREFIX + beanName;
    // 如果是FactoryBean,在比较类型是,会实例化FactoryBean对象,用作比对
    // isTypeMatch会比较FactoryBean对应的实际类型是否符合,所以会实例化FactoryBean
    matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
    }
    if (matchFound) {
    result.add(beanName);
    }
    }
    }
    //catch...略
    }
    }

    // Check manually registered singletons too.
    //省略直接注册的单例检查

    return StringUtils.toStringArray(result);
    }
    如果存在FactoryBean的话,在比对过程中会实例化FactoryBean(isTypeMatch中实现)

    在isTypeMatch中有这么一段代码:

    if (FactoryBean.class.isAssignableFrom(beanType)) {
    if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
    // 如果是FactoryBean类型,需要对其实例化后才能知道到底它创建的bean是什么类型
    beanType = getTypeForFactoryBean(beanName, mbd);
    if (beanType == null) {
    return false;
    }
    }
    }
    下面是getTypeForFactoryBean中的创建FactoryBean实例部分的代码:

    FactoryBean<?> fb = (mbd.isSingleton() ?
    getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
    getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
    下面是getSingletonFactoryBeanForTypeCheck实现:

    private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
    synchronized (getSingletonMutex()) {
    //先尝试从缓存中获取
    BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
    if (bw != null) {
    return (FactoryBean<?>) bw.getWrappedInstance();
    }
    //从单例缓存中获取
    Object beanInstance = getSingleton(beanName, false);
    if (beanInstance instanceof FactoryBean) {
    return (FactoryBean<?>) beanInstance;
    }
    if (isSingletonCurrentlyInCreation(beanName) ||
    (mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
    return null;
    }

    //创建对象
    Object instance;
    try {
    beforeSingletonCreation(beanName);
    instance = resolveBeforeInstantiation(beanName, mbd);
    if (instance == null) {
    //实例化
    bw = createBeanInstance(beanName, mbd, null);
    instance = bw.getWrappedInstance();
    }
    }
    finally {
    afterSingletonCreation(beanName);
    }

    FactoryBean<?> fb = getFactoryBean(beanName, instance);
    if (bw != null) {
    this.factoryBeanInstanceCache.put(beanName, bw);
    }
    return fb;
    }
    }
    从上面代码看以看到,Spring会实例化FactoryBean,以确定其创建的bean的类型

    由于ShiroFilterFactoryBean实现了FactoryBean接口,所以它会提前被初始化。又因为SecurityManager,SecurityManager依赖于Realm实现类、Realm实现类又依赖于UserService,所以引发所有相关的bean提前初始化。

    ShiroFilterFactoryBean -> SecurityManager -> Realm实现类 -> UserService

    但是此时还只是ApplicationContext中registerBeanPostProcessors注册BeanPostProcessor处理器的阶段,此时AnnotationAwareAspectJAutoProxyCreator还没有注册到BeanFactory中,UserService无法享受到事务处理!

    三、解决办法
    在Realm实现中使用Mapper,而不是直接使用Service对象。缺点:直接和数据库交互,并且也没有Service中的逻辑交互以及缓存
    在Realm中Service声明上加入@Lazy注解,延迟Realm实现中Service对象的初始化时间,这样就可以保证Service实际初始化的时候会被BeanPostProcessor拦截,创建具有事务功能的代理对象


    原文:https://blog.csdn.net/finalcola/article/details/81197584

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-9 05:09 , Processed in 0.091151 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表