Take a look at the simplest CGLIB example to see how AOP does this.

/** * Created with Vernon -test * Description: * User: chenyuan * Date: 16/4/25 * Time: 9:25 am */
public class Target {
    public String execute(a) {
        String message = "----------test()----------";
        System.out.println(message);
        returnmessage; }}/** * 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();
       / / the second step
       enhancer.setSuperclass(targetClass);
       / / the third step
       enhancer.setCallback(new MyMethodInterceptor());
       / / step 4
       return enhancer.create();
    }

    public static void main(String rags[]) {
        CglibProxyTest cglibProxyTest = newCglibProxyTest(); Target proxyTarget = (Target) cglibProxyTest.createProxy(Target.class); String res = proxyTarget.execute(); System.out.println(res); }}Copy the code

The execution result is displayed

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'Copy the code

You actually do what you want before and after executing execute(). In fact, this is a prototype of Spring AOP for simplicity.

How @Transaction works

In Spring and TransactionInterceptor PlatformTransactionManager these two classes is the core of the entire transaction module, TransactionInterceptor responsible for intercept method, to determine whether need to commit or rollback transaction. PlatformTransactionManager is the transaction management interface in the Spring, how truly defines transaction rollback and submit. We focus on the source code of these two classes.

The TransactionInterceptor class has a lot of code in it, so I’ll simplify the logic.

// The following code is omitted
public Object invoke(MethodInvocation invocation) throws Throwable {
// Get the target method for the transaction invocationClass<? > targetClass = (invocation.getThis() ! =null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Perform a transaction call
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
Copy the code

In fact, this is because of the use of dynamic proxy. You can do whatever you want before and after the transaction rollback. This is a very important design idea. If we are writing our own framework, this pattern can be your first reference.

Annotation based implementation mechanism

  • Calling annotation methods
  • Generate proxy object –CglibAopProxyCall a method of the inner classDynamicAdvisedInterceptor.intercept()
  • TransactionInterceptor.invoke()Interceptor intercepts, creates and joins a transaction before the target method executes
  • AbstractPlatformTransactionManagerAbstract transaction managers manipulate data sourcesDataSourceCommit or roll back the transaction

Let’s see how it mimics the code at the top, okay?

Follow the first step in the 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);

			// 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));
			// Get CallbackCallback[] callbacks = getCallbacks(rootClass); Class<? >[] types =newClass<? >[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); }}Copy the code

Repeat step 3 of the Demo above:

privateCallback[] 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;
	}
Copy the code

Repeat step 4 from the 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());
	}
Copy the code

The above steps complete the flow of a dynamic proxy, which only needs to be executed when the call actually occurs.

Which scene things fail?

  • 1, only topublicIt’s the modification that matters
  • 2,@TransactionThe default detection exception isRuntimeExceptionAnd its subclasses need to be manually configured if there are other exceptions that need to be rolled back. For example:@Transactional(rollbackFor = Exception.class)
  • 3, ensure that exceptions are nottry-catch{}.catchIt won’t roll back
  • 4. Check whether your database supports transactions, e.gmysqlthemylsam
  • 5,SpringBootThe project already supports transactions by default, so no configuration is required. Other types of projects are required inxmlTo configure whether to enable transactions
  • 6, if in the same class, a not@TransactionThe method calls are@TransactionThe method does not take effect because of proxy issues

In the same class, a non-@ Transaction method call with an @Transaction method does not take effect. If a method call is in the same class, it will not be intercepted by the method interceptor, so the transaction will not work and the method must be put into a different class that is injected through Spring.

Spring uses dynamic proxy (AOP) to manage and slice beans, generating a proxy object for each of our classes and triggering the aspect logic only when the proxy objects are called between them.

In the same class, method B calls A, the method of the object, instead of the proxy object, so Spring can’t cut through this call, that is, it can’t guarantee transactionality through annotations.

Refer to the address

  • Blog.csdn.net/joker8023jo…
  • Blog.csdn.net/Dragon_1999…

If you like my article, you can follow the individual subscription number. Welcome to leave messages and communicate at any time. If you want to join the wechat group to discuss with us, please add the administrator to simplify the stack culture – little Assistant (lastpass4U), he will pull you into the group.