Take a look at the simplest example of CGLIB, and feel how AOP is achieved?
/** * Created with vernon-test * Description: * User: chenyuan * Date: 16/4/25 * Time: 9:25 am */ public class Target { public String execute() { String message = "----------test()----------"; System.out.println(message); return message; } } /** * Created with vernon-test * Description: * User: chenyuan * Date: 16/4/25 * Time: 9:25 am */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(">>>MethodInterceptor start..."); Object result = proxy.invokeSuper(obj, args); System.out.println(">>>MethodInterceptor ending..."); return "haha"; } } /** * Created with vernon-test * Description: * User: chenyuan * Date: 16/4/25 * Time: 9:28 am */ public class CglibProxyTest { public Object createProxy(Class targetClass) { // The first step Enhancer enhancer = new Enhancer(); // Step two enhancer.setSuperclass(targetClass); // Step 3 enhancer.setCallback(new MyMethodInterceptor()); // Step 4 return enhancer.create(); } public static void main(String rags[]) { CglibProxyTest cglibProxyTest = new CglibProxyTest(); Target proxyTarget = (Target) cglibProxyTest.createProxy(Target.class); String res = proxyTarget.execute(); System.out.println(res); } }
Result display after execution
Connected to the target VM, address: '127.0.0.1:55868', transport: 'socket' >>>MethodInterceptor start... ----------test()---------- >>>MethodInterceptor ending... haha Disconnected from the target VM, address: '127.0.0.1:55868', transport: 'socket'
In fact, before and after executing execute(), they do what they want. In fact, this is a prototype of Spring AOP for simplicity.
@How Transaction works
In Spring, two classes, TransactionInterceptor and PlatformTransactionManager, are the core of the whole transaction module. TransactionInterceptor is responsible for intercepting the execution of methods and judging whether the transaction needs to be committed or rolled back. Platform transaction manager is the transaction management interface in Spring, which really defines how transactions are rolled back and committed. We focus on the source code of these two classes.
There are a lot of codes in the TransactionInterceptor class. Let me simplify the logic to facilitate the description:
//The following code omits some content public Object invoke(MethodInvocation invocation) throws Throwable { //Gets the target method of the transaction call Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); //Execute call with transaction return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
In fact, this is because of the use of dynamic agents. Before and after the transaction rollback, you can do what you want. This is a very important design idea. If we want to write our own framework, this pattern can be your first reference.
Annotation based implementation mechanism
- Call annotation method
- Generate proxy object - CglibAopProxy calls dynam of inner class icAdvisedInterceptor.intercept ()
- Tr ansactionInterceptor.invoke () interceptor, create and join transaction before target method execution
- AbstractPlatformTransactionManager Abstract transaction manager action data sourcedatasource commit or rollback transaction
Let's see how it's modeled on the top code?
Follow the first step of Demo above:
public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); } try { Class<?> rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); Class<?> proxySuperClass = rootClass; if (ClassUtils.isCglibProxyClass(rootClass)) { proxySuperClass = rootClass.getSuperclass(); Class<?>[] additionalInterfaces = rootClass.getInterfaces(); for (Class<?> additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } // Validate the class, writing log messages as necessary. validateClassIfNecessary(proxySuperClass, classLoader); // First point: Configure CGLIB Enhancer Enhancer enhancer = createEnhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); if (classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { enhancer.setUseCache(false); } } // Second point: setSuperclass enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader)); // Third, get the Callback Callback[] callbacks = getCallbacks(rootClass); Class<?>[] types = new Class<?>[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } // fixedInterceptorMap only populated at this point, after getCallbacks call above enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types); // Generate the proxy class and create a proxy instance. return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } }
Follow the third step of Demo above:
private Callback[] getCallbacks(Class<?> rootClass) throws Exception { // Parameters used for optimization choices... boolean exposeProxy = this.advised.isExposeProxy(); boolean isFrozen = this.advised.isFrozen(); boolean isStatic = this.advised.getTargetSource().isStatic(); // Choose an "aop" interceptor (used for AOP calls). Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised); // Choose a "straight to target" interceptor. (used for calls that are // unadvised but can return this). May be required to expose the proxy. Callback targetInterceptor; if (exposeProxy) { targetInterceptor = (isStatic ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource())); } else { targetInterceptor = (isStatic ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedInterceptor(this.advised.getTargetSource())); } // Choose a "direct to target" dispatcher (used for // unadvised calls to static targets that cannot return this). Callback targetDispatcher = (isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp()); Callback[] mainCallbacks = new Callback[] { aopInterceptor, // for normal advice targetInterceptor, // invoke target without considering advice, if optimized new SerializableNoOp(), // no override for methods mapped to this targetDispatcher, this.advisedDispatcher, new EqualsInterceptor(this.advised), new HashCodeInterceptor(this.advised) }; Callback[] callbacks; // If the target is a static one and the advice chain is frozen, // then we can make some optimizations by sending the AOP calls // direct to the target using the fixed chain for that method. if (isStatic && isFrozen) { Method[] methods = rootClass.getMethods(); Callback[] fixedCallbacks = new Callback[methods.length]; this.fixedInterceptorMap = new HashMap<>(methods.length); // TODO: small memory optimization here (can skip creation for methods with no advice) for (int x = 0; x < methods.length; x++) { List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass); fixedCallbacks[x] = new FixedChainStaticTargetInterceptor( chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass()); this.fixedInterceptorMap.put(methods[x].toString(), x); } // Now copy both the callbacks from mainCallbacks // and fixedCallbacks into the callbacks array. callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length]; System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length); System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length); this.fixedInterceptorOffset = mainCallbacks.length; } else { callbacks = mainCallbacks; } return callbacks; }
Follow the fourth step of Demo above:
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { enhancer.setInterceptDuringConstruction(false); enhancer.setCallbacks(callbacks); return (this.constructorArgs != null && this.constructorArgTypes != null ? enhancer.create(this.constructorArgTypes, this.constructorArgs) : enhancer.create()); }
In the above steps, a dynamic agent process is completed, and only the dynamic agent class needs to be executed when the call actually occurs.
What scenarios will things fail?
- 1. Only works with public modifiers
- 2. The default detection exception for @ Transaction is RuntimeException and its subclasses. If there are other exceptions that need to be rolled back, you need to manually configure them, for example: @ transactional (rollbackfor)= Exception.class )
- 3. Make sure that the exception is not tried catch {}, and the catch will not be rolled back later
- 4. Check whether the database supports transactions, such as mysql
- 5. By default, SpringBoot projects already support transactions without configuration; other types of projects need to configure whether to enable transactions in xml
- 6. If a non @ Transaction method call has @ Transaction in the same class, it will not take effect because of the agent problem
Here, in the same class, a non @ Transaction method calls a method with @ Transaction will not take effect. If it's a method call in the same class, it won't be intercepted by the method interceptor, so the Transaction won't work. You have to put the method into another class, and the class is injected through Spring.
Spring uses dynamic proxy (AOP) to manage and slice beans. It generates a proxy object for each class. Only when the proxy objects are called, can the aspect logic be triggered.
In the same class, method B calls method A of the event meta object, not through the proxy object, so Spring can't switch to this call, that is, it can't guarantee transactional through annotation.
Reference address
- https://blog.csdn.net/joker8023joker/article/details/103277571
- https://blog.csdn.net/Dragon_1999/article/details/93059495
If you like my article, you can pay attention to the personal subscription number. Welcome to leave a message and communicate at any time. If you want to join the wechat group and discuss it together, please add the administrator Jianzhan culture - little assistant (lastpass4u), who will pull you into the group.