Today I am going to share with you AOP (aspect-oriented Programming), the name is only one letter from OOP, in fact, it is a complement to OOP Programming, not a replacement. It translates as “aspect-oriented programming,” but I prefer to translate as “section-oriented programming.” It sounds a little mysterious. Why? By the time you finish reading this article, you will know that the important work we did was to write this “section”. So what is a “slice”?
That’s right! You use a knife to cut a lump of noodles. Notice that, relative to the surface, we must be cutting it horizontally, which is called “crosscutting” for short. You can think of a piece of code as a piece of dough. You can also use a knife to cut across it. Here’s how to do it!
To be clear, this concept was not invented by Rod Johnson. AspectJ is the best-known and most powerful Java open source project, but its predecessor is AspectWerkz (the project was discontinued in 2005), which is the ancestor of AOP. Lao Luo (a bald genius who was my dad’s equivalent) wrote a framework called Spring, which became an instant hit and became the father of Spring. After implementing an AOP framework on top of his IOC, he seemed to find himself falling further and further into the abyss. When someone suggested that he integrate AspectJ, he reluctantly accepted. As a result, Spring + AspectJ is probably the AOP framework we use most today.
So what exactly is AOP? How to use it? This article will take you step by step into the world of AOP and make you feel better than ever!
But before I dive into AOP, I think it’s worth recalling this code:
1. Write dead code
Let’s start with an interface:
public interface Greeting {
void sayHello(String name);
}Copy the code
There is also an implementation class:
public class GreetingImpl implements Greeting {
@Override
public void sayHello(String name) {
before();
System.out.println("Hello! " + name);
after();
}
private void before(a) {
System.out.println("Before");
}
private void after(a) {
System.out.println("After"); }}Copy the code
The before() and after() methods are dead in the body of the sayHello() method, which is pretty bad code. If someone writes a lot of this code, your architect will scold you.
For example, if we want to count the execution time of each method in order to evaluate its performance, do we need to fiddle with each method from end to end?
For example, if we want to write a JDBC application, we should also connect to the database at the beginning of the method, and close the database at the end of the method.
Such code will only work the programmer to death, the architect to death!
Make sure you find a way to refactor the code above, starting with three solutions:
2. Static proxy
The simplest solution is to use static proxy mode. We write a separate proxy class for the GreetingImpl class:
public class GreetingProxy implements Greeting {
private GreetingImpl greetingImpl;
public GreetingProxy(GreetingImpl greetingImpl) {
this.greetingImpl = greetingImpl;
}
@Override
public void sayHello(String name) {
before();
greetingImpl.sayHello(name);
after();
}
private void before(a) {
System.out.println("Before");
}
private void after(a) {
System.out.println("After"); }}Copy the code
GreetingImpl: GreetingImpl: GreetingImpl: GreetingImpl: GreetingImpl: GreetingImpl
public class Client {
public static void main(String[] args) {
Greeting greetingProxy = new GreetingProxy(new GreetingImpl());
greetingProxy.sayHello("Jack"); }}Copy the code
This is correct, but there is a problem. There are more and more classes like XxxProxy. How can we reduce the number of proxy classes as much as possible? Ideally, only one proxy class.
This is where we need to use the dynamic proxy provided by the JDK.
3. JDK dynamic proxy
public class JDKDynamicProxy implements InvocationHandler {
private Object target;
public JDKDynamicProxy(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(a) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before(a) {
System.out.println("Before");
}
private void after(a) {
System.out.println("After"); }}Copy the code
The client is called like this:
public class Client {
public static void main(String[] args) {
Greeting greeting = new JDKDynamicProxy(new GreetingImpl()).getProxy();
greeting.sayHello("Jack"); }}Copy the code
This has merged all the proxy classes into dynamic proxy classes, but there is still a problem: the JDK gives us dynamic proxies that can only proxy interfaces, not classes without them. What can be done about it?
4. CGLib dynamic proxy
We use the open source CGLib class library to proxy classes without interfaces, thus compensating for the JDK’s shortcomings. The CGLib dynamic proxy class plays like this:
public class CGLibDynamicProxy implements MethodInterceptor {
private static CGLibDynamicProxy instance = new CGLibDynamicProxy();
private CGLibDynamicProxy(a) {}public static CGLibDynamicProxy getInstance(a) {
return instance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(target, args);
after();
return result;
}
private void before(a) {
System.out.println("Before");
}
private void after(a) {
System.out.println("After"); }}Copy the code
With the Singleton pattern in the above code, the client calls are also easier:
public class Client {
public static void main(String[] args) {
Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);
greeting.sayHello("Jack"); }}Copy the code
At this point, we have done all we can and the problem seems to have been solved. But things will never be perfect, and we must strive for perfection!
Lao Luo has come up with an AOP framework, can it be perfect and elegant? Please continue to read!
Spring AOP: Pre -, post -, surround – (programmatic)
In the world of Spring AOP, there are so many aOP-related terms that we often get in the way of each book or technical document that we start by instilling them in the reader one by one. I think it’s totally intimidating. It’s not that complicated. Relax.
The before() method we mentioned in the previous example is called before Advice in Spring AOP. Some people literally translate Advice as “notification”, which I think is inappropriate because it doesn’t mean “notification” at all, but rather an “enhancement” to the functionality of the original code. Furthermore, CGLib also has an Enhancer class, which is an Enhancer class.
Also, a method like after() is called after Advice because it comes after to enhance the functionality of the code.
If you could combine before() with after(), it would be called Around Advice, like a hamburger with a ham in the middle.
Are these three concepts easily understood? If so, move on!
The next step is to implement these so-called “enhanced classes” so that they are crosscut into the code rather than dead in it.
Let’s start with a pre-enhanced class:
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before"); }}Copy the code
Note: this class implements the org. Springframework. Aop. MethodBeforeAdvice interface, we would need to strengthen the code into it.
Here’s another post-enhancer class:
public class GreetingAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After"); }}Copy the code
Similarly, this class implements the org. Springframework. Aop) AfterReturningAdvice interface.
Finally, use a client to integrate them and see how to call:
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(); // Create a proxy factory
proxyFactory.setTarget(new GreetingImpl()); // Shoots the target class object
proxyFactory.addAdvice(new GreetingBeforeAdvice()); // Add a pre-enhancement
proxyFactory.addAdvice(new GreetingAfterAdvice()); // Add post-enhancement
Greeting greeting = (Greeting) proxyFactory.getProxy(); // Get the agent from the agent factory
greeting.sayHello("Jack"); // Call the proxy's method}}Copy the code
Take a look at the above code and its comments, and you’ll see that Spring AOP is pretty simple, right?
Of course, we could have just defined an enhanced class that implements both MethodBeforeAdvice and AfterReturningAdvice, as follows:
public class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice.AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before");
}
@Override
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After"); }}Copy the code
This allows us to add pre and post enhancements with just one line of code:
proxyFactory.addAdvice(new GreetingBeforeAndAfterAdvice());Copy the code
The “surround enhancement” mentioned earlier is actually a combination of “front enhancement” and “back enhancement” without having to implement both interfaces simultaneously.
public class GreetingAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before();
Object result = invocation.proceed();
after();
return result;
}
private void before(a) {
System.out.println("Before");
}
private void after(a) {
System.out.println("After"); }}Copy the code
Surrounding the enhancement class needs to implement org. Aopalliance. Intercept. MethodInterceptor interfaces. Note that this interface is not provided by Spring; it was written by the AOP Consortium (a very cool consortium), and Spring just borrows it.
We also need to add objects of this enhanced class to the proxy factory on the client side:
proxyFactory.addAdvice(new GreetingAroundAdvice());Copy the code
Well, that’s the basic use of Spring AOP, but that’s just “programming.” Spring AOP would be silly if it were just that, since it once advocated defining Bean objects in Spring configuration files, leaving all new operations out of the code.
Spring AOP: Pre -, post -, surround – (declarative)
Let’s start with how the Spring configuration file is written:
<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<! Scan the specified package (automatically define @Component annotated classes as Spring beans) -->
<context:component-scan base-package="aop.demo"/>
<! -- Configure a proxy -->
<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="aop.Greeting"/> <! -- Need proxy interface -->
<property name="target" ref="greetingImpl"/> <! Interface implementation class -->
<property name="interceptorNames"> <! Interceptor name (i.e., enhanced class name, Spring Bean ID) -->
<list>
<value>greetingAroundAdvice</value>
</list>
</property>
</bean>
</beans>Copy the code
Be sure to read the comments for the code above. You can use ProxyFactoryBean instead of ProxyFactory. I think interceptorNames should be renamed adviceNames to make it easier to understand why. Add an enhanced class to this property.
In addition, if there is only one enhanced class, this can be simplified using the following methods:
.<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="aop.Greeting"/>
<property name="target" ref="greetingImpl"/>
<property name="interceptorNames" value="greetingAroundAdvice"/> <! -- Notice the configuration of this line -->
</bean>.Copy the code
Also note that we use the Spring 2.5+ feature “Bean scanning” here, so we don’t have to constantly define < Bean ID =” XXX “class=” XXX “/> in our Spring configuration files, thus freeing our hands.
See how easy it is:
@Component
public class GreetingImpl implements Greeting {... }Copy the code
@Component
public class GreetingAroundAdvice implements MethodInterceptor {... }Copy the code
Finally, take a look at the client side:
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml"); // Get Spring Context
Greeting greeting = (Greeting) context.getBean("greetingProxy"); // Get the Bean object from the Context by its ID (actually a proxy)
greeting.sayHello("Jack"); // Call the proxy's method}}Copy the code
The amount of code is really low, so we put the configuration code in the configuration file, which also helps later maintenance. More importantly, the code only focuses on the business logic and puts the configuration into a file. This is a best practice!
In addition to the three enhancements mentioned above, there are actually two other types of enhancements that you need to know about when it comes to getting your hands on them.
Spring AOP: Throw enhancements
An exception is thrown. The general practice is to print it to the console or log file. There are a lot of places to deal with it. Throws Advice, it is really strong, do not believe you continue reading:
@Component
public class GreetingImpl implements Greeting {
@Override
public void sayHello(String name) {
System.out.println("Hello! " + name);
throw new RuntimeException("Error"); // Intentionally throw an exception to see if the exception message can be intercepted}}Copy the code
Here is the code that throws the enhanced class:
@Component
public class GreetingThrowAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
System.out.println("---------- Throw Exception ----------");
System.out.println("Target Class: " + target.getClass().getName());
System.out.println("Method Name: " + method.getName());
System.out.println("Exception Message: " + e.getMessage());
System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); }}Copy the code
Throw enhancement class needs to implement org. Springframework. Aop. ThrowsAdvice interface, available methods in the interface methods, parameters, the information such as target, the exception object. We can write this information uniformly to the log or persist it to the database.
This feature is really great! But there is an even more dramatic enhancement. If A class implements interface A, but not interface B, can that class call the methods of interface B? If you haven’t read this, you won’t believe it works!
Spring AOP: Introducing enhancements
All of the above are enhancements to methods, but what about class enhancements? In AOP lingo, enhancements to methods are called Weaving and enhancements to classes are called Introduction. Introduction Advice, however, is a feature enhancement to a class, and it is the last of the enhancements Spring AOP provides. It is recommended that you do not read Spring Reference in the first place, or you will regret it. Because when you look at the following code examples, it becomes clear exactly what an introduction enhancement is.
Defines a new interface Apology:
public interface Apology {
void saySorry(String name);
}Copy the code
But I don’t want GreetingImpl to implement this interface directly in my code. I want to implement it dynamically while the program is running. Because if I implemented this interface, I would definitely have to rewrite the GreetingImpl class, but the point is I don’t want to change it, and maybe in a real world scenario, this class would be 10,000 lines of code, and I wouldn’t dare touch it. So, I need to take advantage of Spring’s introduction of enhancements. This is getting interesting!
@Component
public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return super.invoke(invocation);
}
@Override
public void saySorry(String name) {
System.out.println("Sorry! "+ name); }}Copy the code
Above defines a introduction enhancement class, expanded the org. Springframework. Aop. Support. DelegatingIntroductionInterceptor class, also has realized the credo of the definition of new interfaces. The invoke() method of the parent class is first overridden in the class, and then the methods of the Apology interface are implemented. I want to use this enhancement class to enrich the GreetingImpl class, so that the GreetingImpl class can call the METHODS of the Apology interface when the application runs without directly implementing the Apology interface. It’s amazing!
Take a look at how it is configured:
<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="aop.demo"/>
<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="aop.demo.Apology"/> <! -- Interface that needs dynamic implementation -->
<property name="target" ref="greetingImpl"/> <! -- Target class -->
<property name="interceptorNames" value="greetingIntroAdvice"/> <! -- Introducing enhancements -->
<property name="proxyTargetClass" value="true"/> <! -- Proxy target class (default false, proxy interface) -->
</bean>
</beans>Copy the code
Note the proxyTargetClass attribute, which indicates whether the target class is proxied. The default is false, which is the proxied interface. In this case, Spring uses JDK dynamic proxies. If true, Spring uses CGLib dynamic proxies. This is so convenient! Spring encapsulates all of this and lets programmers care less about the details. We would like to pay tribute to Lao Luo comrade, you are our hearts forever idol!
All of this will be clear when you finish looking at the following client code:
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
GreetingImpl greetingImpl = (GreetingImpl) context.getBean("greetingProxy"); // Note: Transition to the target class, not its Greeting interface
greetingImpl.sayHello("Jack");
Apology apology = (Apology) greetingImpl; // Force the target class up to the Apology interface (this is a feature introduced to us by the introduction enhancement, namely the "interface dynamic implementation" feature)
apology.saySorry("Jack"); }}Copy the code
The saySorry() method turns out to be called directly by the greetingImpl object, simply by casting it to that interface.
Once again, we thank Spring AOP and Luo for providing us with such powerful features!
In fact, there is a lot more to Spring AOP, and the next article will cover more valuable AOP technologies to get you even more out of it.
To be continued…
Follow wechat official account [Programmer’s Dream, focusing on Java, SpringBoot, SpringCloud, microservices, Docker, and full-stack technologies such as backend separation.