Exploration of things in the Controller layer

The exploration of Transaction in the Controller layer

Generally, transactions in development require us to be placed in the Service layer, but in some cases, we may be required to be placed in the Controller layer. Have you ever encountered such a requirement? So will the transaction in the Controller layer take effect? What will be the problem? Let's take a look


I. Seeing the essence through the phenomenon

first case

Controller layer code is as follows

@RestController
@RequestMapping("/city")
public class CityControllerImpl implements CityController {
    @Autowired   
    private CityService cityService;
        
    @Override   
    @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)     
    @Transcational   
    public BaseResult<City> getCity(@RequestParam("id") Integer id) {       
        City one = cityService.getOne(id);       
        BaseResult <City> baseResult = new BaseResult <> ();       
        baseResult.setData(one);       
        return baseResult;   
    }
}

operation result

Yes, you read that right, a 404 exception occurs when Transactional loads the Controller layer

second case

Controller layer code is as follows

@RestController
@RequestMapping("/city")
public class CityControllerImpl  {
   @Autowired   private CityService cityService;    
   @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE)   
   @Transactional   
   public BaseResult<City> getCity(@RequestParam("id") Integer id) {       
       City one = cityService.getOne(id);       
       BaseResult <City> baseResult = new BaseResult <> ();       
       baseResult.setData(one);       
       return baseResult;   
   }
}

The difference from the above is that the CityController interface is not implemented, so if we run it, what will be the result?

The results are as follows:

{ data: null, message: null, status: 0 }

There is no problem in the second case, so is Transactional rolled back normally? I will tell you the answer here. Even if it is replaced with an interface with data changes, our transaction will take effect.

third case

The author tested and used the Resteasy test that supports ==JAX-RS 2.0==, and found that there is no such problem. You can test whether this problem exists in Jersey. It is inferred that there should be no such problem.

II. Familiarity with the phenomenon of essential solutions

1. Difference

It can be seen that the difference between our two Controllers is that one has an implemented interface and the other has no implementation. Why is the difference so big?

2. The nature of the transaction

We know that transactions are implemented based on proxies. Currently, there are two proxies in Spring: JDK dynamic proxy and CGLIB proxy. Does it have anything to do with the proxy selected by Spring? Let's take a look at the source code of which proxy Spring chooses to use when proxying a class. as follows:

@Override     
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {        
     if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {             
         Class<?> targetClass = config.getTargetClass();             
         if (targetClass == null) {                 
             throw new AopConfigException("TargetSource cannot determine target class: " +                         
                    "Either an interface or a target is required for proxy creation.");             
         }             
         if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {                 
             return new JdkDynamicAopProxy(config);             
         }             
         return new ObjenesisCglibAopProxy(config);         
     } else {             
         return new JdkDynamicAopProxy(config);         
     }     
 }

This is the core piece of code for Spring to create a proxy. In the class DefaultAopProxyFactory, whether or not an interface is added, Spring will register our Controller as a proxy object when it sees the @Transactional annotation. Note: Spring does not create proxy classes for all Controllers. If our Controller does not expose any aspects, Spring will not create a proxy class. You may find it strange here. We will put a TAG here and explain it at the end of the article.

Continuing the topic just now, in the first case, since our Controller has an interface, we use the JDK proxy. On the contrary, the second way uses the Cglib proxy. OK, our CityControllerImpl is now a proxy class. So why does the 404 exception occur?

3. The principle of SpringMvc

Why does a 404 exception occur after the Controller becomes a proxy? It must be related to our SpringMVC. Let's take a look at the core class of SpringMVC, AbstractHandlerMethodMapping. This class can bind the URL and which method of the processor needs to be executed. This abstract class implements the initializingBean interface. In fact, the main registration URL operation is called through the afterPropertiesSet() interface method of this interface. Then call the initHandlerMethods method to bind the URL. The method is detailed as follows:

protected void initHandlerMethods() {
         if (logger.isDebugEnabled()) {             
             logger.debug("Looking for request mappings in application context: " + getApplicationContext());         
         }         
         String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?                 
             BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :                 
             getApplicationContext().getBeanNamesForType(Object.class));          
         for (String beanName: beanNames) {             
             if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {                 
                 Class<?> beanType = null;                 
             try {                     
                 beanType = getApplicationContext (). getType (beanName);                 
             } catch (Throwable ex) {                     
                 // An unresolvable bean type, probably from a lazy bean - let's ignore it.                     
                if (logger.isDebugEnabled()) {                         
                    logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);                     
                }                 
            }                 
                if (beanType != null && isHandler(beanType)) {                     
                detectHandlerMethods(beanName);                 
                }             
            }         
        }         
        handlerMethodsInitialized(getHandlerMethods());     
}

The CityControllerImpl proxy class is taken out of beanType. Please note that in line 21 of the code, there is an isHandler method. This method is used to determine whether this class is a Handler. The code is as follows:

@Override     
protected boolean isHandler(Class<?> beanType) {
      return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||                 
          AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));     
}

Seeing this, I believe everyone already understands it very well. Here is to see if there are Controller annotations and RequestMapping annotations on your class. If there is, establish the relevant mapping relationship (URL->Handler)

Among them, the interface is proxied by JDK, and the generated JDK proxy class is

JDK's dynamic proxy is implemented by polymorphism and reflection. The proxy class it generates needs to implement the interface you pass in, and obtain the method object of the interface through reflection, and pass the method object to the invoke method of the enhanced class. to execute, thus realizing the proxy function.

The proxy class file generated by CityController is as follows:

public final class cityControllerImpl extends Proxy implements Proxy86 {  
  private static Method m1;  
  private static Method m32;  
  private static Method m7;  

  public cityControllerImpl(InvocationHandler var1) throws  {  
      super (var1);  
  }  

  public final TargetSource getTargetSource() throws  {  
      try {  
          return (TargetSource)super.h.invoke(this, m8, (Object[])null);  
      } catch (RuntimeException | Error var2) {  
          throw var2;  
      } catch (Throwable var3) {  
          throw new UndeclaredThrowableException(var3);  
      }  
  }  

  public final void addAdvice(int var1, Advice var2) throws AopConfigException {  
      try {  
          super.h.invoke(this, m21, new Object[]{var1, var2});  
      } catch (RuntimeException | Error var4) {  
          throw var4;  
      } catch (Throwable var5) {  
          throw new UndeclaredThrowableException(var5);  
      }  
  }  

  public final BaseResult getCity(Integer var1) throws  {  
      try {  
          return (BaseResult)super.h.invoke(this, m27, new Object[]{var1});  
      } catch (RuntimeException | Error var3) {  
          throw var3;  
      } catch (Throwable var4) {  
          throw new UndeclaredThrowableException(var4);  
      }  
  }  

}

The class has been streamlined, and we see that the generated proxy class has no @Controller @RequestMapping annotation at all, so the isHandler method fails to execute, so it will not be added to the SpringMvc controller processing method at all. When the URL request comes, The corresponding processor cannot be found, so a 404 error is reported.

No interface is proxied by CGLIB, and CGlib proxy class is generated

CGLib uses a very low-level bytecode technology. The principle is to create a subclass for a class through bytecode technology, and use the method interception technology in the subclass to intercept all parent class method calls, and weave the cross-cutting logic according to the trend. . Both JDK dynamic proxy and CGLib dynamic proxy are the basis for implementing Spring AOP

public class CityControllerImpl$$EnhancerBySpringCGLIB$$8cae5808 extends CityControllerImpl implements SpringProxy, Advised, Factory {
  private boolean CGLIB$BOUND;  public static Object CGLIB$FACTORY_DATA;  
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;  
  private static final Callback[] CGLIB$STATIC_CALLBACKS;  
  final BaseResult CGLIB $ getCity $ 0 (Integer var1) {      
      return super.getCity(var1);
  }  
  public final BaseResult getCity (Integer var1) {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;      
      if (this.CGLIB$CALLBACK_0 == null) {
          CGLIB$BIND_CALLBACKS(this);
          var10000 = this.CGLIB$CALLBACK_0;
      }      
      return var10000 != null ? (BaseResult)var10000.intercept(this, CGLIB$getCity$0$Method, new Object[]{var1}, CGLIB$getCity$0$Proxy) : super.getCity(var1);
  }
}

==In fact, the isHandler method will scan the interface and parent class of the proxy class==, see if you have this annotation, the cityControllerImpl interface and parent class in the JDK proxy have no annotations, and the parent class of the CGlib proxy is the original class CityControllerImpl, so return true

4. Think

If the Controller layer is not annotated with @Transcational, why won't a 404 exception be generated? In fact, if your Controller does not add any woven code (custom aop aspect, etc., if you are interested, you can use AspectJ to try what happens to the Method method woven into the Controller layer), Spring will not generate a proxy for your class. , that is, when AbstractHandlerMethodMapping is bound, this class is not a proxy, so it will match successfully.


Original address:

https://blog.csdn.net/u011410529/article/details/79671309

If you want to know more about java technology, please pay attention to my WeChat public account: the way of learning java technology



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326304373&siteId=291194637