What is AOP
AOP (Aspect-Oriented Programming, aspect-oriented programming) can be said to be a supplement and improvement of OOP (Object-Oriented Programming). OOP introduces concepts such as encapsulation, inheritance, and polymorphism to establish an object hierarchy to simulate a collection of public behaviors. When we need to introduce public behavior to scattered objects, OOP seems powerless. That is, OOP allows you to define relationships from top to bottom, but is not suitable for defining relationships from left to right. For example, logging function. Log code is often scattered horizontally across all object levels without any relation to the core functionality of the object to which it is scattered. The same is true for other types of code, such as security, exception handling, and transparency. This kind of irrelevant code scattered everywhere is called cross-cutting code. In OOP design, it causes a lot of code duplication, which is not conducive to the reuse of each module.
AOP technology, on the contrary, uses a technique called "crosscutting" to dissect the inside of the encapsulated object and encapsulate the common behaviors that affect multiple classes into a reusable module and name it "Aspect", i.e. The so-called "aspect", simply put, encapsulates logic or responsibilities that are not related to the business but are called jointly by the business module, which facilitates reducing the system's duplicate code, reducing the coupling between modules, and promoting future operability and maintainability. AOP represents a horizontal relationship. If the "object" is a hollow cylinder, encapsulating the properties and behavior of the object; then the aspect-oriented programming method is like a sharp blade, cutting these hollow cylinders to obtain internal information. The cut-out section is the so-called "face". Then it restored these cut-out sections with its clever hands without leaving any trace.
Using the "crosscutting" technology, AOP divides the software system into two parts: core concern and cross-cutting concern. The main process of business processing is the core focus, and the part that has little to do with it is the cross-sectional focus. One characteristic of cross-cutting concerns is that they often occur in multiple core concerns, and are basically similar everywhere. For example, permission authentication, logging, and transaction processing. The role of Aop is to separate various concerns in the system and separate core concerns from cross-cutting concerns. As Adam Magee, senior solution architect at Avanade, said, the core idea of AOP is to “separate the business logic in the application from the common services that support it.”
The technology to implement AOP is mainly divided into two categories: one is to use dynamic proxy technology, and use the method of intercepting messages to decorate the message to replace the execution of the original object behavior; the other is to use static weaving method to introduce specific syntax to create "faces", so that the compiler can weave code related to "faces" during compilation.
AOP usage scenarios
AOP is used to encapsulate cross-cutting concerns, which can be used in the following scenarios:
Authentication Permissions
Caching Caching
Context passing content
Error handling
Lazy loading
Debugging
logging, tracing, profiling and monitoring
Performance optimization
Persistence Persistence
Resource pooling
Synchronization
Transactions
AOP related concepts
Aspect: A modularity of a focus that may further cross-cut multiple objects. Transaction management is a good example of cross-cutting concerns in J2EE applications. The aspect is implemented using Spring's Advisor or interceptor.
Joinpoint: A clear point during the execution of the program, such as a method call or a specific exception being thrown.
Advice: Actions performed by the AOP framework at a specific connection point. Various types of notifications include "around", "before" and "throws" notifications. Notification types are discussed below. Many AOP frameworks, including Spring, use interceptors as notification models to maintain an interceptor chain "round" connection points. Four advices are defined in Spring: BeforeAdvice, AfterAdvice, ThrowAdvice and DynamicIntroductionAdvice
Pointcut: Specifies a collection of connection points to which notification will be triggered. The AOP framework must allow developers to specify entry points: for example, using regular expressions. Spring defines the Pointcut interface, which is used to combine MethodMatcher and ClassFilter, which can be understood clearly through the name. MethodMatcher is used to check whether the method of the target class can be used to apply this notification, while ClassFilter is used to check whether Pointcut should be applied to the target class.
Introduction: Add a method or field to the class that is notified. Spring allows the introduction of new interfaces to any object notified. For example, you can simplify caching using an introduction that enables any object to implement the IsModified interface. To use Introduction in Spring, you can use DelegatingIntroductionInterceptor to implement notifications, and use DefaultIntroductionAdvisor to configure the interface to implement Advice and proxy classes.
Target Object: An object containing the connection point. Also known as a notified or proxy object. POJO
AOP Proxy: An object created by the AOP framework, containing notifications. In Spring, the AOP proxy can be a JDK dynamic proxy or a CGLIB proxy.
Weaving: Assemble to create a notified object. This can be done at compile time (for example using the AspectJ compiler) or at runtime. Spring, like other pure Java AOP frameworks, completes weaving at runtime.
Spring AOP Components
The following class diagram lists the main AOP components in Spring
How to use Spring AOP
Spring AOP can be used in configuration files or programming ways.
The configuration can be performed through the xml file, and there are about four ways:
1. Configure ProxyFactoryBean, explicitly set advisors, advice, target, etc.
2. Configure AutoProxyCreator. In this way, the defined bean is still used as before, but what you get from the container is actually a proxy object.
3. Configure through <aop:config>
4. Configure through <aop: aspectj-autoproxy>, and use AspectJ annotations to identify notifications and entry points
You can also use ProxyFactory directly to use Spring AOP programmatically. Through the methods provided by ProxyFactory, you can set target objects, advisors and other related configurations, and finally get the proxy object through the getProxy() method.
Specific examples of use can be Google. omitted here
Generation of Spring AOP proxy object
Spring provides two ways to generate proxy objects: JDKProxy and Cglib. The specific method of generation is determined by AopProxyFactory based on the configuration of AdvisedSupport object. The default policy is to use JDK dynamic proxy technology if the target class is an interface, otherwise use Cglib to generate the proxy. Let's study how Spring uses JDK to generate proxy objects. The specific generation code is placed in the JdkDynamicAopProxy class, and the relevant code is directly added:
/** * <ol> * <li>Get the interface to be implemented by the proxy class. In addition to the configuration in the Advised object, SpringProxy will also be added, Advised(opaque=false) * <li>Check whether there is an interface that defines equals or hashcode in the interface obtained above* <li>Calling Proxy.newProxyInstance to create a proxy object* </ol> */ public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource()); } Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } Then this is actually very clear. I have already written the comments clearly and will not repeat them again.
The following question is, the proxy object is generated, how is the cut surface weaved?
We know that InvocationHandler is the core of JDK dynamic proxy, and the method calls of generated proxy objects will be delegated to the InvocationHandler.invoke() method. Through the signature of JdkDynamicAopProxy, we can see that this class actually implements InvocationHandler. Let’s take a look at how Spring AOP weaves into the section by analyzing the invoke() method implemented in this class.
publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { //eqauls() method, the target object does not implement this method if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){ return (equals(args[0])? Boolean.TRUE : Boolean.FALSE); } //hashCode() method, the target object does not implement this method if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){ return newInteger(hashCode()); } //Advised interface or the method defined in its parent interface, directly reflects the call, and does not use notification if (!this.advised.opaque &&method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations onProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args); } Object retVal = null; if (this.advised.exposeProxy) { // Make invocation available ifnecessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //Get the target object's class target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } //Get the Interceptor list that can be applied to this method List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass); //If there is no notification that can be applied to this method (Interceptor), this direct reflection call method.invoke(target, args) if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //Create MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } // Massage return value if necessary. if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Notethat we can't help if the target sets // a reference to itself inanother returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come fromTargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } The main process can be briefly described as: obtaining the notification chain that can be applied to this method (Interceptor Chain). If there is, apply the notification and execute the joinpoint; if there is no, directly reflect the joinpoint. The key here is how the notification chain is obtained and how it is executed. Let’s analyze it one by one.
First of all, from the above code, we can see that the notification chain is obtained through the Advised.getInterceptorsAndDynamicInterceptionAdvice() method. Let's take a look at the implementation of this method:
public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { MethodCacheKeycacheKey = new MethodCacheKey(method); List<Object>cached = this.methodCache.get(cacheKey); if(cached == null) { cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this, method, targetClass); this.methodCache.put(cacheKey,cached); } returncached; } It can be seen that the actual acquisition work is actually done by the AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice() method, and the obtained results will be cached.
Let’s analyze the implementation of this method below:
/** * Get the advisor list from the provided configuration instance config and traverse these advisors. If it is an IntroductionAdvisor, * Then determine whether this Advisor can be applied to the target class targetClass. If it is a PointcutAdvisor, determine * Whether this Advisor can be applied to the target method method. The Advisor that meets the conditions is converted into an Interceptor list through the AdvisorAdaptor. */ publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) { // This is some tricky... we have to process introductions first, // but we need to preserve order in the ultimate list. List interceptorList = new ArrayList(config.getAdvisors().length); // Check whether the IntroductionAdvisor boolean hasIntroductions = hasMatchingIntroductions(config,targetClass); // In fact, a series of AdvisorAdapters are registered here to convert Advisor into MethodInterceptor AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); Advisor[] advisors = config.getAdvisors(); for (int i = 0; i <advisors.length; i++) { Advisor advisor = advisors[i]; if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor; if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { //TODO: The positions of these two methods can be swapped in this place //Convert Advisor to Interceptor MethodInterceptor[]interceptors = registry.getInterceptors(advisor); //Check whether the pointcut of the current advisor can match the current method MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) { if(mm.isRuntime()) { // Creating a newobject instance in the getInterceptors() method // isn't a problem we normally cache created chains. for (intj = 0; j < interceptors.length; j++) { interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm)); } } else { interceptorList.addAll(Arrays.asList(interceptors)); } } } } else if (advisor instanceof IntroductionAdvisor){ IntroductionAdvisor ia =(IntroductionAdvisor) advisor; if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) { Interceptor[] interceptors= registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } else { Interceptor[] interceptors =registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; } After this method is executed, all Advisors configured in Advised that can be applied to connection points or target classes are converted into MethodInterceptor.
Next, let's take a look at how the obtained interceptor chain works.
if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //Create MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } From this code, we can see that if the obtained interceptor chain is empty, the target method will be called directly reflected. Otherwise, a MethodInvocation will be created, its process method will be called, and the execution of the interceptor chain will be triggered. Let's take a look at the specific code
public Object proceed() throws Throwable { // We start with an index of -1and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) { //If Interceptor is finished executing, execute joinPoint return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //If you want to dynamically match joinPoint if (interceptorOrInterceptionAdvice instance of InterceptorAndDynamicMethodMatcher){ // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice; // Dynamic Match: Whether the runtime parameters meet the matching conditions if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //Execute the current Intercetpor returndm.interceptor.invoke(this); } else { // When dynamic matching fails, skip the current Intercetpor and call the next Interceptor return procedure(); } } else { // It's an interceptor, so we just invoke it: The pointcutwill have // been evaluated statically before this object was constructed. //Execute the current Intercetpor return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } }The code is also relatively simple, so I won't go into details here.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.