简体   繁体   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?

I have recently noticed that Spring successfully intercepts intra class function calls in a @Configuration class but not in a regular bean. 我最近注意到Spring成功拦截了@Configuration类中的类内函数调用,但没有在常规bean中拦截。

A call like this 像这样的电话

@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
    }
}

fails to start a new transaction because while the code of saveCustomer() executes in the CustomerDAO proxy, the code of saveCustomer2() gets executed in the unwrapped CustomerDAO class, as I can see by looking at 'this' in the debugger, and so Spring has no chance to intercept the call to saveCustomer2. 无法启动新事务,因为当saveCustomer()的代码在CustomerDAO代理中执行时,saveCustomer2()的代码在解包的CustomerDAO类中执行,正如我在调试器中查看'this'所看到的那样,所以Spring没有机会拦截对saveCustomer2的调用。

However, in the following example, when transactionManager() calls createDataSource() it is correctly intercepted and calls createDataSource() of the proxy, not of the unwrapped class, as evidenced by looking at 'this' in the debugger. 但是,在下面的示例中,当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;
       }
}

So my question is, why can Spring correctly intercept the intra class function calls in the second example, but not in the first. 所以我的问题是,为什么Spring能够在第二个例子中正确地拦截内部类函数调用,而不是在第一个例子中。 Is it using different types of dynamic proxies? 它是否使用不同类型的动态代理?

Edit: From the answers here and other sources I now understand the following: @Transactional is implemented using Spring AOP, where the proxy pattern is carried out by wrapping/composition of the user class. 编辑:从这里的答案和其他来源我现在理解以下内容:@Transactional是使用Spring AOP实现的,其中代理模式是通过包装/组合用户类来实现的。 The AOP proxy is generic enough so that many Aspects can be chained together, and may be a CGLib proxy or a Java Dynamic Proxy. AOP代理是通用的,因此许多方面可以链接在一起,并且可以是CGLib代理或Java动态代理。

In the @Configuration class, Spring also uses CGLib to create an enhanced class which inherits from the user @Configuration class, and overrides the user's @Bean functions with ones that do some extra work before calling the user's/super function such as check if this is the first invocation of the function or not. 在@Configuration类中,Spring还使用CGLib创建一个继承自用户@Configuration类的增强类,并在调用用户/超级函数之前用一些额外的工作覆盖用户的@Bean函数,例如检查是否这样是否是第一次调用函数。 Is this class a proxy? 这个班级是代理吗? It depends on the definition. 这取决于定义。 You may say that it is a proxy which uses inheritance from the real object instead of wrapping it using composition. 您可以说它是一个代理,它使用来自真实对象的继承而不是使用组合包装它。

To sum up, from the answers given here I understand these are two entirely different mechanisms. 总而言之,从这里给出的答案我理解这些是两种完全不同的机制。 Why these design choices were made is another, open question. 为什么做出这些设计选择是另一个开放的问题。

Is it using different types of dynamic proxies? 它是否使用不同类型的动态代理?

Almost exactly 几乎完全一样

Let's figure out what's the difference between @Configuration classes and AOP proxies answering the following questions: 让我们弄清楚@Configuration类和AOP代理之间的区别是回答以下问题:

  1. Why self-invoked @Transactional method has no transactional semantics even though Spring is capable of intercepting self-invoked methods? 为什么自调用@Transactional方法没有事务语义,即使Spring能够拦截自调用方法?
  2. How @Configuration and AOP are related? @Configuration和AOP如何相关?

Why self-invoked @Transactional method has no transactional semantics? 为什么自调用@Transactional方法没有事务语义?

Short answer: 简短回答:

This is how AOP made. 这就是AOP的制作方式。

Long answer: 答案很长:

  1. Declarative transaction management relies on AOP (for the majority of Spring applications on Spring AOP ) 声明式事务管理依赖于AOP (对于Spring AOP上的大多数Spring应用程序

The Spring Framework's declarative transaction management is made possible with Spring aspect-oriented programming (AOP) 使用Spring面向方面编程(AOP)实现Spring Framework的声明式事务管理

  1. It is proxy-based ( §5.8.1. Understanding AOP Proxies ) 它是基于代理的( §5.8.1。理解AOP代理

Spring AOP is proxy-based. Spring AOP是基于代理的。

From the same paragraph SimplePojo.java : 从同一段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...
    }
}

And a snippet proxying it: 并代表它的代码片段:

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();
    }
}

The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy . 这里要理解的关键是Main类的main(..)方法中的客户端代码具有对代理的引用。

This means that method calls on that object reference are calls on the proxy . 这意味着对该对象引用的方法调用是对代理的调用

As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call. 因此,代理可以委托给与该特定方法调用相关的所有拦截器(通知)。

However, once the call has finally reached the target object (the SimplePojo , reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo() , are going to be invoked against the this reference, and not the proxy . 但是,一旦调用最终到达目标对象(在这种情况下为SimplePojo ,引用),将调用它可能对其自身进行的任何方法调用,例如this.bar()this.foo() 。反对this参考,而不是代理

This has important implications. 这具有重要意义。 It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute. 这意味着自我调用不会导致与方法调用相关的建议有机会执行。

( Key parts are emphasized. ) 强调关键部分。

You may think that aop works as follows: 您可能认为aop的工作原理如下:

Imagine we have a Foo class which we want to proxy: 想象一下,我们有一个我们要代理的Foo类:

Foo.java : Foo.java

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

There is nothing special. 没什么特别的。 Just getInt method returning 42 只是getInt方法返回42

An interceptor: 拦截器:

Interceptor.java : Interceptor.java

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

LogInterceptor.java (for demonstration): 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 : 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");
    }
  }
}

Finally InterceptingFoo.java : 最后是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--;
    }
  }
}

Wiring everything together: 将所有东西连接起来:

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();
}

Will print: 将打印:

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

Now let's take a look at ReflectiveMethodInvocation . 现在让我们来看看ReflectiveMethodInvocation

Here is a part of its proceed method: 以下是其proceed方法的一部分:

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

++this.currentInterceptorIndex should look familiar now ++this.currentInterceptorIndex现在应该看起来很熟悉了

You may try introducing several aspects into your application and see the stack growing at the proceed method when advised method is invoked 您可以尝试在应用程序中引入几个方面,并在调用建议方法时查看在proceed方法中的堆栈增长

Finally everything ends up at MethodProxy . 最后一切都以MethodProxy结束。

From its invoke method javadoc: 从它的invoke方法javadoc:

Invoke the original method, on a different object of the same type. 在相同类型的不同对象上调用原始方法。

And as I mentioned previously documentation: 正如我之前提到的文档:

once the call has finally reached the target object any method calls that it may make on itself are going to be invoked against the this reference, and not the proxy 一旦调用最终到达target对象,任何方法调用它可能会this引用进行调用,而不是代理调用

I hope now, more or less, it's clear why. 我希望现在,或多或少,很明显为什么。

How @Configuration and AOP are related? @Configuration和AOP如何相关?

The answer is they are not related . 答案是他们没有关系

So Spring here is free to do whatever it wants. 所以Spring在这里可以自由地做任何想做的事。 Here it is not tied to the proxy AOP semantics. 这里它与代理AOP语义无关。

It enhances such classes using ConfigurationClassEnhancer . 它使用ConfigurationClassEnhancer增强了这些类。

Take a look at: 看一眼:

Returning to the question 回到问题

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中支持它?

I hope from technical point of view it is clear why. 希望从技术角度来看很清楚为什么。

Now my thoughts from non-technical side: 现在我的想法来自非技术方面:

I think it is not done because Spring AOP is here long enough ... 我认为它没有完成,因为Spring AOP已经足够长了 ......

Since Spring Framework 5 the Spring WebFlux framework has been introduced. 自Spring Framework 5以来,引入了Spring WebFlux框架。

Currently Spring Team is working hard towards enhancing reactive programming model 目前, Spring Team正在努力提升反应式编程模型

See some notable recent blog posts : 查看一些值得注意的近期 博文

More and more features towards less-proxying approach of building Spring applications are introduced. 介绍了构建Spring应用程序的代理方法越来越少的特性。 (see this commit for example) (例如,参见此提交

So I think that even though it might be possible to do what you've described it is far from Spring Team's #1 priority for now 所以我认为尽管可能会做你所描述的,但它远远不是Spring Team现在的首要任务

Because AOP proxies and @Configuration class serve a different purpose, and are implemented in a significantly different ways (even though both involve using proxies). 因为AOP代理和@Configuration类服务于不同的目的,并且以显着不同的方式实现(即使两者都涉及使用代理)。 Basically, AOP uses composition while @Configuration uses inheritance . 基本上,AOP使用组合,而@Configuration使用继承

AOP proxies AOP代理

The way these work is basically that they create proxies that do the relevant advice logic before/after delegating the call to the original (proxied) object. 这些工作的方式基本上是他们创建代理,在委托调用原始(代理)对象之前/之后执行相关的建议逻辑。 The container registers this proxy instead of the proxied object itself, so all dependencies are set to this proxy and all calls from one bean to another go through this proxy. 容器注册此代理而不是代理对象本身,因此所有依赖项都设置为此代理,并且从一个bean到另一个bean的所有调用都通过此代理。 However, the proxied object itself has no pointer to the proxy (it doesn't know it's proxied, only the proxy has a pointer to the target object). 但是,代理对象本身没有指向代理的指针(它不知道代理,只有代理有指向目标对象的指针)。 So any calls within that object to other methods don't go through the proxy. 因此,该对象中对其他方法的任何调用都不会通过代理。

(I'm only adding this here for contrast with @Configuration, since you seem to have correct understanding of this part.) (我只是在这里添加它与@Configuration形成对比,因为你似乎对这部分有了正确的理解。)

@Configuration @组态

Now while the objects that you usually apply the AOP proxy to are a standard part of your application, the @Configuration class is different - for one, you probably never intend to create any instances of that class directly yourself. 现在虽然您通常应用AOP代理的对象是应用程序的标准部分,但@Configuration类是不同的 - 对于一个,您可能永远不打算直接自己创建该类的任何实例。 This class truly is just a way to write configuration of the bean container, has no meaning outside Spring and you know that it will be used by Spring in a special way and that it has some special semantics outside of just plain Java code - eg that @Bean -annotated methods actually define Spring beans. 这个类真的只是编写bean容器配置的一种方式,在Spring之外没有任何意义,你知道它将以一种特殊的方式被Spring使用,并且它只有普通的Java代码之外还有一些特殊的语义 - 例如@Bean -annotated方法实际上定义了Spring bean。

Because of this, Spring can do much more radical things to this class without worrying that it will break something in your code (remember, you know that you only provide this class for Spring, and you aren't going to ever create or use its instance directly). 因此,Spring可以为这个类做更激进的事情,而不用担心它会破坏你的代码中的某些东西(记住,你知道你只为Spring提供这个类,你不会创建或使用它实例直接)。

What it actually does is it creates a proxy that's subclass of the @Configuration class . 它实际上做的是创建一个代理,它@Configuration子类 This way, it can intercept invocation of every (non- final non- private ) method of the @Configuration class, even within the same object (because the methods are effectively all overriden by the proxy, and Java has all the methods virtual). 通过这种方式,它可以拦截@Configuration类的每个(非finalprivate )方法的调用,即使在同一个对象中也是如此(因为这些方法实际上都被代理覆盖,并且Java将所有方法都设置为虚拟)。 The proxy does exactly this to redirect any method calls that it recognizes to be (semantically) references to Spring beans to the actual bean instances instead of invoking the superclass method. 代理正是这样做,将它识别为(语义上)对Spring bean的引用的任何方法调用重定向到实际的bean实例,而不是调用超类方法。

read a bit spring source code. 读一点弹簧源代码。 I try to answer it. 我试着回答它。

the point is how spring deal with the @Configuration and @bean . 关键是spring如何处理@Configuration@bean in the ConfigurationClassPostProcessor which is a BeanFactoryPostProcessor, it will enhance all ConfigurationClasses and creat a Enhancer as a subClass. 在作为BeanFactoryPostProcessor的ConfigurationClassPostProcessor中,它将增强所有ConfigurationClasses并创建一个Enhancer作为Enhancer this Enhancer register two CALLBACKS(BeanMethodInterceptor,BeanFactoryAwareMethodInterceptor). 这个Enhancer注册了两个CALLBACKS(BeanMethodInterceptor,BeanFactoryAwareMethodInterceptor)。 you call PersistenceJPAConfig method will go through the CALLBACKS. 你调用PersistenceJPAConfig方法将通过CALLBACKS。 in BeanMethodInterceptor,it will get bean from spring container. 在BeanMethodInterceptor中,它将从spring容器中获取bean。

it may be not clearly. 可能不清楚。 you can see the source code in ConfigurationClassEnhancer.java BeanMethodInterceptor . 您可以在ConfigurationClassEnhancer.java BeanMethodInterceptor看到源代码。 ConfigurationClassPostProcessor.java enhanceConfigurationClasses

You can't call @Transactional method in same class 不能在同一个类中调用@Transactional方法

It's a limitation of Spring AOP (dynamic objects and cglib). 这是Spring AOP(动态对象和cglib)的限制。

If you configure Spring to use AspectJ to handle the transactions, your code will work. 如果您将Spring配置为使用AspectJ来处理事务,那么您的代码将起作用。

The simple and probably best alternative is to refactor your code. 简单且可能最好的替代方法是重构代码。 For example one class that handles users and one that process each user. 例如,一个处理用户的类和一个处理每个用户的类。 Then default transaction handling with Spring AOP will work. 然后使用Spring AOP进行默认事务处理将起作用。

Also @Transactional should be on Service layer and not on @Repository @Transactional也应位于服务层而不是@Repository

transactions belong on the Service layer. 事务属于服务层。 It's the one that knows about units of work and use cases. 这是了解工作单位和用例的人。 It's the right answer if you have several DAOs injected into a Service that need to work together in a single transaction. 如果您将几个DAO注入到需要在单个事务中协同工作的服务中,那么这是正确的答案。

So you need to rethink your transaction approach, so your methods can be reuse in a flow including several other DAO operations that are roll-able 因此,您需要重新考虑您的事务方法,因此您的方法可以在流中重用,包括其他几个可滚动的 DAO操作

Spring uses proxying for method invocation and when you use this... it bypasses that proxy. Spring使用代理进行方法调用,当你使用它时...它会绕过该代理。 For @Bean annotations Spring uses reflection to find them. 对于@Bean注释,Spring使用反射来查找它们。

暂无
暂无

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

相关问题 为什么在 Spring 配置类中的 @Bean 方法上不需要 @Autowired? - Why do I not need @Autowired on @Bean methods in a Spring configuration class? 在spring配置类中使用在配置类本身中初始化过的bean - Use a bean in spring configuration class which initialized in the configuration class itself 在春季如何将xml bean配置文件导入到@configuration类? - How can I import a xml bean configuration file to a @configuration class in spring? 配置 class 中的 Spring bean 未在.xml 文件中定义的 bean 中自动装配 - Spring bean in Configuration class not being autowired in a bean defined in .xml file Spring Boot希望@Component类成为@Configuration类中的@Bean - Spring Boot wants @Component class to be a @Bean in @Configuration class 如何在spring配置类中创建RestControllerAdvice类型的spring @bean - How to create spring @bean of type RestControllerAdvice in spring configuration class 在@Configuration类中使用Spring XML配置的bean,并将该bean作为另一个bean的factory-bean - Use Spring XML configured bean inside @Configuration Class and Use the bean as factory-bean for another bean 如何使用现有应用程序的spring配置类仅测试一个bean? - How can I test only one bean using existing application's spring configuration class? Spring @Configuration类包含@Bean方法,但是如何首先执行init()? - Spring @Configuration class contains @Bean methods but how can I get execute init() first? 如何拦截内部非Spring类的方法执行,该类在Spring托管Bean中定义? - How to intercept method execution of inner non-spring class, which is defined in Spring managed bean?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM