繁体   English   中英

如果Spring能够在@Configuration类中成功拦截内部类函数调用,为什么它不能在常规bean中支持它?

[英]If Spring can successfully intercept intra class function calls in a @Configuration class, why does it not support it in a regular bean?

我最近注意到Spring成功拦截了@Configuration类中的类内函数调用,但没有在常规bean中拦截。

像这样的电话

@Repository
public class CustomerDAO {  
    @Transactional(value=TxType.REQUIRED)
    public void saveCustomer() {
        // some DB stuff here...
        saveCustomer2();
    }
    @Transactional(value=TxType.REQUIRES_NEW)
    public void saveCustomer2() {
        // more DB stuff here
    }
}

无法启动新事务,因为当saveCustomer()的代码在CustomerDAO代理中执行时,saveCustomer2()的代码在解包的CustomerDAO类中执行,正如我在调试器中查看'this'所看到的那样,所以Spring没有机会拦截对saveCustomer2的调用。

但是,在下面的示例中,当transactionManager()调用createDataSource()时,它被正确拦截并调用代理的createDataSource(),而不是解包类的调用,如在调试器中查看“this”所证明的那样。

@Configuration
public class PersistenceJPAConfig {
    @Bean
    public DriverManagerDataSource createDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        //dataSource.set ... DB stuff here
        return dataSource;
    }

   @Bean 
       public PlatformTransactionManager transactionManager(   ){
           DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(createDataSource());
           return transactionManager;
       }
}

所以我的问题是,为什么Spring能够在第二个例子中正确地拦截内部类函数调用,而不是在第一个例子中。 它是否使用不同类型的动态代理?

编辑:从这里的答案和其他来源我现在理解以下内容:@Transactional是使用Spring AOP实现的,其中代理模式是通过包装/组合用户类来实现的。 AOP代理是通用的,因此许多方面可以链接在一起,并且可以是CGLib代理或Java动态代理。

在@Configuration类中,Spring还使用CGLib创建一个继承自用户@Configuration类的增强类,并在调用用户/超级函数之前用一些额外的工作覆盖用户的@Bean函数,例如检查是否这样是否是第一次调用函数。 这个班级是代理吗? 这取决于定义。 您可以说它是一个代理,它使用来自真实对象的继承而不是使用组合包装它。

总而言之,从这里给出的答案我理解这些是两种完全不同的机制。 为什么做出这些设计选择是另一个开放的问题。

它是否使用不同类型的动态代理?

几乎完全一样

让我们弄清楚@Configuration类和AOP代理之间的区别是回答以下问题:

  1. 为什么自调用@Transactional方法没有事务语义,即使Spring能够拦截自调用方法?
  2. @Configuration和AOP如何相关?

为什么自调用@Transactional方法没有事务语义?

简短回答:

这就是AOP的制作方式。

答案很长:

  1. 声明式事务管理依赖于AOP (对于Spring AOP上的大多数Spring应用程序

使用Spring面向方面编程(AOP)实现Spring Framework的声明式事务管理

  1. 它是基于代理的( §5.8.1。理解AOP代理

Spring AOP是基于代理的。

从同一段SimplePojo.java

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

并代表它的代码片段:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

这里要理解的关键是Main类的main(..)方法中的客户端代码具有对代理的引用。

这意味着对该对象引用的方法调用是对代理的调用

因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)。

但是,一旦调用最终到达目标对象(在这种情况下为SimplePojo ,引用),将调用它可能对其自身进行的任何方法调用,例如this.bar()this.foo() 。反对this参考,而不是代理

这具有重要意义。 这意味着自我调用不会导致与方法调用相关的建议有机会执行。

强调关键部分。

您可能认为aop的工作原理如下:

想象一下,我们有一个我们要代理的Foo类:

Foo.java

public class Foo {
  public int getInt() {
    return 42;
  }
}

没什么特别的。 只是getInt方法返回42

拦截器:

Interceptor.java

public interface Interceptor {
  Object invoke(InterceptingFoo interceptingFoo);
}

LogInterceptor.java (用于演示):

public class LogInterceptor implements Interceptor {
  @Override
  public Object invoke(InterceptingFoo interceptingFoo) {
    System.out.println("log. before");
    try {
      return interceptingFoo.getInt();
    } finally {
      System.out.println("log. after");
    }
  }
}

InvokeTargetInterceptor.java

public class InvokeTargetInterceptor implements Interceptor {
  @Override
  public Object invoke(InterceptingFoo interceptingFoo) {
    try {
      System.out.println("Invoking target");
      Object targetRetVal = interceptingFoo.method.invoke(interceptingFoo.target);
      System.out.println("Target returned " + targetRetVal);
      return targetRetVal;
    } catch (Throwable t) {
      throw new RuntimeException(t);
    } finally {
      System.out.println("Invoked target");
    }
  }
}

最后是InterceptingFoo.java

public class InterceptingFoo extends Foo {
  public Foo target;
  public List<Interceptor> interceptors = new ArrayList<>();
  public int index = 0;
  public Method method;

  @Override
  public int getInt() {
    try {
      Interceptor interceptor = interceptors.get(index++);
      return (Integer) interceptor.invoke(this);
    } finally {
      index--;
    }
  }
}

将所有东西连接起来:

public static void main(String[] args) throws Throwable {
  Foo target = new Foo();
  InterceptingFoo interceptingFoo = new InterceptingFoo();
  interceptingFoo.method = Foo.class.getDeclaredMethod("getInt");
  interceptingFoo.target = target;
  interceptingFoo.interceptors.add(new LogInterceptor());
  interceptingFoo.interceptors.add(new InvokeTargetInterceptor());

  interceptingFoo.getInt();
  interceptingFoo.getInt();
}

将打印:

log. before
Invoking target
Target returned 42
Invoked target
log. after
log. before
Invoking target
Target returned 42
Invoked target
log. after

现在让我们来看看ReflectiveMethodInvocation

以下是其proceed方法的一部分:

Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

++this.currentInterceptorIndex现在应该看起来很熟悉了

您可以尝试在应用程序中引入几个方面,并在调用建议方法时查看在proceed方法中的堆栈增长

最后一切都以MethodProxy结束。

从它的invoke方法javadoc:

在相同类型的不同对象上调用原始方法。

正如我之前提到的文档:

一旦调用最终到达target对象,任何方法调用它可能会this引用进行调用,而不是代理调用

我希望现在,或多或少,很明显为什么。

@Configuration和AOP如何相关?

答案是他们没有关系

所以Spring在这里可以自由地做任何想做的事。 这里它与代理AOP语义无关。

它使用ConfigurationClassEnhancer增强了这些类。

看一眼:

回到问题

如果Spring能够在@Configuration类中成功拦截内部类函数调用,为什么它不能在常规bean中支持它?

希望从技术角度来看很清楚为什么。

现在我的想法来自非技术方面:

我认为它没有完成,因为Spring AOP已经足够长了 ......

自Spring Framework 5以来,引入了Spring WebFlux框架。

目前, Spring Team正在努力提升反应式编程模型

查看一些值得注意的近期 博文

介绍了构建Spring应用程序的代理方法越来越少的特性。 (例如,参见此提交

所以我认为尽管可能会做你所描述的,但它远远不是Spring Team现在的首要任务

因为AOP代理和@Configuration类服务于不同的目的,并且以显着不同的方式实现(即使两者都涉及使用代理)。 基本上,AOP使用组合,而@Configuration使用继承

AOP代理

这些工作的方式基本上是他们创建代理,在委托调用原始(代理)对象之前/之后执行相关的建议逻辑。 容器注册此代理而不是代理对象本身,因此所有依赖项都设置为此代理,并且从一个bean到另一个bean的所有调用都通过此代理。 但是,代理对象本身没有指向代理的指针(它不知道代理,只有代理有指向目标对象的指针)。 因此,该对象中对其他方法的任何调用都不会通过代理。

(我只是在这里添加它与@Configuration形成对比,因为你似乎对这部分有了正确的理解。)

@组态

现在虽然您通常应用AOP代理的对象是应用程序的标准部分,但@Configuration类是不同的 - 对于一个,您可能永远不打算直接自己创建该类的任何实例。 这个类真的只是编写bean容器配置的一种方式,在Spring之外没有任何意义,你知道它将以一种特殊的方式被Spring使用,并且它只有普通的Java代码之外还有一些特殊的语义 - 例如@Bean -annotated方法实际上定义了Spring bean。

因此,Spring可以为这个类做更激进的事情,而不用担心它会破坏你的代码中的某些东西(记住,你知道你只为Spring提供这个类,你不会创建或使用它实例直接)。

它实际上做的是创建一个代理,它@Configuration子类 通过这种方式,它可以拦截@Configuration类的每个(非finalprivate )方法的调用,即使在同一个对象中也是如此(因为这些方法实际上都被代理覆盖,并且Java将所有方法都设置为虚拟)。 代理正是这样做,将它识别为(语义上)对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。

读一点弹簧源代码。 我试着回答它。

关键是spring如何处理@Configuration@bean 在作为BeanFactoryPostProcessor的ConfigurationClassPostProcessor中,它将增强所有ConfigurationClasses并创建一个Enhancer作为Enhancer 这个Enhancer注册了两个CALLBACKS(BeanMethodInterceptor,BeanFactoryAwareMethodInterceptor)。 你调用PersistenceJPAConfig方法将通过CALLBACKS。 在BeanMethodInterceptor中,它将从spring容器中获取bean。

可能不清楚。 您可以在ConfigurationClassEnhancer.java BeanMethodInterceptor看到源代码。 ConfigurationClassPostProcessor.java enhanceConfigurationClasses

不能在同一个类中调用@Transactional方法

这是Spring AOP(动态对象和cglib)的限制。

如果您将Spring配置为使用AspectJ来处理事务,那么您的代码将起作用。

简单且可能最好的替代方法是重构代码。 例如,一个处理用户的类和一个处理每个用户的类。 然后使用Spring AOP进行默认事务处理将起作用。

@Transactional也应位于服务层而不是@Repository

事务属于服务层。 这是了解工作单位和用例的人。 如果您将几个DAO注入到需要在单个事务中协同工作的服务中,那么这是正确的答案。

因此,您需要重新考虑您的事务方法,因此您的方法可以在流中重用,包括其他几个可滚动的 DAO操作

Spring使用代理进行方法调用,当你使用它时...它会绕过该代理。 对于@Bean注释,Spring使用反射来查找它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM