preface
AOP is both familiar and unfamiliar. Anyone who has known Spring knows the concept of AOP, that is, aspect oriented programming, which can be used to manage some peripheral businesses unrelated to the main business, such as logging, transaction management, etc; It is strange because it has not been used in work, and the related concepts of AOP are also in the clouds; I've been looking at the relevant source code of Spring recently, so let's first look at a usage of AOP in Spring.
Related concepts
Before learning the usage of Spring AOP, let's take a look at the related concepts of AOP,
For a detailed introduction to Spring AOP, please refer to the official website https://docs.spring.io/spring/docs/2.5.x/reference/aop.html
-
Join point : A connection point represents a point during program execution. In spring, AOP represents a method, that is, a method can be regarded as a join point
-
pointcut : The pointcut is the predicate that matches the join point. What does it mean? The join point that needs to execute Advice is the pointcut
-
Advice : Enhancement: the operations performed at the connection point can be divided into five types: pre, post, exception, final and surround enhancement
-
Aspect : Facet, which is composed of pointcut and Advice, can be simply regarded as the class annotated by @ aspect
-
Target object : Target object, that is, the target object woven into advice
-
AOP proxy : Proxy class. In Spring AOP, an AOP proxy is a JDK dynamic proxy object or CGLIB proxy object
-
`Weaving` : Weave in and apply Aspect to the target object
Note: among the above concepts, Join point is easy to be confused And pointcut, it can be understood that in Spring AOP, all executable methods are join points, and all join points can be embedded with Advice; pointcut can be regarded as a kind of descriptive information. It modifies the Join point to confirm which join points to execute Advice on,
Chestnuts
After understanding the concept of AOP, let's take a look at how to use it Spring Aop
-
To use Spring For AOP, first configure the following tags in the Spring configuration file:
1<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>
The tag has two properties, expose-proxy and proxy-target-class , The default values are false;
expose-proxy : Do you want to use the current proxy object ThreadLocal What does it mean to save? For example, Aop needs to intercept all methods under an interface, but some methods call themselves internally, as shown below:
1 public void test_1() 2 { 3 this.test_2(); 4 } 5 public void test_2() 6 { 7 }
Call test_1. Test at this time_ 2 will not be intercepted for enhancement because the AOP proxy object is called instead of the current object, but in test_1 method uses this internally, so test_2 will not be intercepted and enhanced, so the attribute expose proxy It is used to solve this problem, that is, the acquisition of AOP agent.
proxy-target-class : Whether to use CGLIB for proxy, because the underlying technology of Spring AOP uses dynamic proxy, which is divided into JDK proxy and CGLIB proxy. The default value of this attribute is false, which means that the underlying AOP uses JDK proxy by default. CGLIB will be used for proxy only when the class to be proxy does not implement any interface. If you want to use CGLIB for proxy, You can set this property to true.
-
Define the method to be intercepted by aop, and simulate the addition, deletion and modification of a User:
Interface:
1public interface IUserService { 2 void add(User user); 3 User query(String name); 4 List<User> qyertAll(); 5 void delete(String name); 6 void update(User user); 7}
Interface implementation:
1@Service("userServiceImpl") 2public class UserServiceImpl implements IUserService { 3 4 @Override 5 public void add(User user) { 6 System.out.println("User added successfully, user=" + user); 7 } 8 9 @Override 10 public User query(String name) { 11 System.out.println("according to name Query user succeeded"); 12 User user = new User(name, 20, 1, 1000, "java"); 13 return user; 14 } 15 16 @Override 17 public List<User> qyertAll() { 18 List<User> users = new ArrayList<>(2); 19 users.add(new User("zhangsan", 20, 1, 1000, "java")); 20 users.add(new User("lisi", 25, 0, 2000, "Python")); 21 System.out.println("Query all users succeeded, users = " + users); 22 return users; 23 } 24 25 @Override 26 public void delete(String name) { 27 System.out.println("according to name User deleted successfully, name = " + name); 28 } 29 30 @Override 31 public void update(User user) { 32 System.out.println("User updated successfully, user = " + user); 33 } 34}
.
-
Define AOP section
In Spring AOP, use @ Aspect The class identified by the annotation is a facet, and then the pointcut and advice are defined in the facet:
3.1 pre enhancement, @ Before(), is executed before the target method is executed
1@Component 2@Aspect 3public class UserAspectj { 4 5 // Execute before method execution 6 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 7 public void before_1(){ 8 System.out.println("log: stay add Method...."); 9 } 10}
The above method is before_1() is a pre enhancement of the add() method of the interface, that is, it is executed before the add() method is executed,
Test:
1@RunWith(SpringJUnit4ClassRunner.class) 2@ContextConfiguration("/resources/myspring.xml") 3public class TestBean { 4 5 @Autowired 6 private IUserService userServiceImpl; 7 8 @Test 9 public void testAdd() { 10 User user = new User("zhangsan", 20, 1, 1000, "java"); 11 userServiceImpl.add(user); 12 } 13} 14// result: 15// log: stay add Method 16// Successfully added user, user=User{name='zhangsan ', age=20, sex=1, money=1000.0, job='java'}
If you want to get the parameters of the target method, we can add parameters to the tangent method JoinPoint , Through it, you can obtain the relevant information of the target object:
1 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 2 public void before_2(JoinPoint joinPoint){ 3 Object[] args = joinPoint.getArgs(); 4 User user = null; 5 if(args[0].getClass() == User.class){ 6 user = (User) args[0]; 7 } 8 System.out.println("log: stay add Method, Method parameters = " + user); 9 }
Re execute the above test code and the results are as follows:
1log: stay add Method, Method parameters = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} 2 User added successfully, user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
3.2 post enhancement, @ After() is executed after the target method is executed. It will be executed whether it is normal exit or exception throwing
1 // Execute after method execution 2 @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))") 3 public void after_1(){ 4 System.out.println("log: stay add Method...."); 5 }
Execute the test code of 3.1, and the results are as follows:
1 User added successfully, user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'} 2log: ==== After method execution =====
3.3 return enhancement, @ AfterReturning(), is executed after the target method returns normally. If there is an exception, it will not be executed. You can get the return value:
1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") 2public void after_return(Object object){ 3 System.out.println("stay query Method is executed after it returns, Return value= " + object); 4}
Test:
1@Test 2public void testQuery() { 3 userServiceImpl.query("zhangsan"); 4} 5// result: 6// Query user by name succeeded 7// stay query Method is executed after it returns, Return value= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
When a method is enhanced by both @ After() and @ AfterReturning(), which one should be executed first?
1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object") 2public void after_return(Object object){ 3 System.out.println("===log: stay query Method is executed after it returns, Return value= " + object); 4} 5 6@After("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") 7public void after_2(){ 8 System.out.println("===log: stay query Method...."); 9}
Test:
1 according to name Query user succeeded 2===log: stay query Method.... 3===log: stay query Method is executed after it returns, Return value= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
As you can see, even if @ After() is placed in @ After AfterReturning(), it is also executed first, that is, @ After() is executed before @ AfterReturning().
3.4 exception enhancement, @ AfterThrowing, is executed when an exception is thrown. If an exception is not thrown, it is not executed.
1@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex") 2public void after_throw(Exception ex){ 3 System.out.println("stay query Method is executed when an exception is thrown, abnormal= " + ex); 4}
Now modify its enhanced query() method to throw an exception:
1@Override 2public User query(String name) { 3 System.out.println("according to name Query user succeeded"); 4 User user = new User(name, 20, 1, 1000, "java"); 5 int a = 1/0; 6 return user; 7}
Test:
1@Test 2public void testQuery() { 3 userServiceImpl.query("zhangsan"); 4} 5// result: 6// stay query When the method throws an exception, Abnormal= java.lang.ArithmeticException: / by zero 7// java.lang.ArithmeticException: / by zero ...........
3.5 surround enhancement, @ Around, before and after the target method is executed
1@Around("execution(* main.tsmyk.mybeans.inf.IUserService.delete(..))") 2public void test_around(ProceedingJoinPoint joinPoint) throws Throwable { 3 Object[] args = joinPoint.getArgs(); 4 System.out.println("log : delete Before method execution, parameter = " + args[0].toString()); 5 joinPoint.proceed(); 6 System.out.println("log : delete After method execution"); 7}
Test:
1@Test 2public void test5(){ 3 userServiceImpl.delete("zhangsan"); 4} 5 6// result: 7// log : delete Before the method is executed, parameter = zhangsan 8// The user is successfully deleted according to name, name = zhangsan 9// log : delete After method execution
The above are several enhancements of Spring AOP.
In the chestnuts above, the tangent expression above each method needs to be written once, and can now be used @ Pointcut To declare a reusable pointcut expression, and then reference the pointcut expression above each method:
1// statement pointcut 2@Pointcut("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))") 3public void pointcut(){ 4} 5 6@Before("pointcut()") 7public void before_3(){ 8 System.out.println("log: stay query Method"); 9} 10@After("pointcut()") 11public void after_4(){ 12 System.out.println("log: stay query Method...."); 13}
Indicator
In the chestnuts above, the execution Indicator, which is used to match the connection point of method execution. It is also the main indicator used by Spring AOP. Wildcard () is used in pointcut expression And (..), where( ) It can represent any method, any return value, (..) represents any parameter of the method. Next, let's look at other indicators.
1. within
Match all joinpoints (Methods) of all classes under a specific package, including sub packages. Note that all classes, not interfaces, will not take effect if interfaces are written, such as within(main.tsmyk.mybeans.impl.* Will match main.tsmyk.mybeans.impl All of the classes under the package Join point;within(main.tsmyk.mybeans.impl..* The two points will match all of the classes under the package and its sub packages Join point.
Chestnuts:
1@Pointcut("within(main.tsmyk.mybeans.impl.*)") 2public void testWithin(){ 3} 4 5@Before("testWithin()") 6public void test_within(){ 7 System.out.println("test within Execute before method execution....."); 8}
Execute the delete method of class UserServiceImpl under the package. The results are as follows:
1@Test 2public void test5(){ 3 userServiceImpl.delete("zhangsan"); 4} 5 6// result: 7// test within Execute before method execution 8// The user is successfully deleted according to name, name = zhangsan
2. @within
Matches all methods that hold the specified annotation type, such as @ within(Secure), any class method in which the target object holds a Secure annotation; the annotation must be declared on the target object, and the annotation declared on the interface has no effect on it.
3. target
The matching object is a target object, and the target(main.tsmyk.mybeans.inf.IUserService) matches all objects under the interface Join point :
1@Pointcut("target(main.tsmyk.mybeans.inf.IUserService)") 2public void anyMethod(){ 3} 4 5@Before("anyMethod()") 6public void beforeAnyMethod(){ 7 System.out.println("log: ==== Before method execution ====="); 8} 9 10@After("anyMethod()") 11public void afterAnyMethod(){ 12 System.out.println("log: ==== After method execution ====="); 13}
After that, any method under the interface will be enhanced.
4. @target
Match a target object, which must have specific annotations, such as @ target(org.springframework.transaction.annotation.Transactional) Match any @ Transactional Annotation method
5. this
this(service.IPointcutService) is the execution method matching the current AOP proxy object type. The current AOP object implements Any method of the IPointcutService interface
6. arg
Matching parameters,
1 // Match has only one parameter name Method of 2 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)") 3 public void test_arg(){ 4 5 } 6 7 // The first matching parameter is name Method of 8 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)") 9 public void test_arg2(){ 10 11 } 12 13 // Matching the second parameter is name Method of 14 @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)") 15 public void test_arg3(){ 16 17 }
7. @arg
Match parameters. Parameters have specific annotations, @ args(Anno)), and method parameters are marked with Anno annotations.
8. @annotation
Match specific annotation
@annotation(org.springframework.transaction.annotation.Transactional) Match any with @ Transactional Annotation method.
9. bean
Method that matches a specific bean name
1 // matching bean The name of the is userServiceImpl All methods of 2 @Before("bean(userServiceImpl)") 3 public void test_bean(){ 4 System.out.println("==================="); 5 } 6 7 // matching bean Name with ServiceImpl All methods at the end 8 @Before("bean(*ServiceImpl)") 9 public void test_bean2(){ 10 System.out.println("+++++++++++++++++++"); 11 }
Test:
Execute the methods under the bean:
1@Test 2public void test5(){ 3 userServiceImpl.delete("zhangsan"); 4} 5//result: 6// =================== 7// +++++++++++++++++++ 8// The user is successfully deleted according to name, name = zhangsan
The above is how to use all indicators of Spring AOP.
Spring AOP principle
The underlying layer of Spring AOP uses dynamic proxy. There are two ways to implement dynamic proxy: one is the dynamic proxy of JDK and the other is the dynamic proxy of CGLIB. The following two ways are used to implement the above functions, that is, when calling UserServiceImpl class methods, add logs before and after method execution.
JDK dynamic agent
To implement JDK dynamic proxy, you must implement InvocationHandler Interface, and override invoke method:
1public class UserServiceInvocationHandler implements InvocationHandler { 2 3 // Target object of proxy 4 private Object target; 5 6 public UserServiceInvocationHandler(Object target) { 7 this.target = target; 8 } 9 10 @Override 11 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 12 13 System.out.println("log: Before the target method is executed, parameter = " + args); 14 15 // Execution target method 16 Object retVal = method.invoke(target, args); 17 18 System.out.println("log: After the target method is executed....."); 19 20 return retVal; 21 } 22}
Test:
1public static void main(String[] args) throws IOException { 2 3 // Objects requiring proxy 4 IUserService userService = new UserServiceImpl(); 5 InvocationHandler handler = new UserServiceInvocationHandler(userService); 6 ClassLoader classLoader = userService.getClass().getClassLoader(); 7 Class[] interfaces = userService.getClass().getInterfaces(); 8 9 // Proxy object 10 IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler); 11 12 System.out.println("Type of dynamic proxy = " + proxyUserService.getClass().getName()); 13 proxyUserService.query("zhangsan"); 14 15 // Write bytecode to file 16 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserServiceImpl.class}); 17 FileOutputStream fos =new FileOutputStream(new File("D:/$Proxy.class")); 18 fos.write(bytes); 19 fos.flush(); 20 21}
result:
1 Type of dynamic proxy = com.sun.proxy.$Proxy0 2log: Before the target method is executed, parameter = [Ljava.lang.Object;@2ff4acd0 3 according to name Query user succeeded 4log: After the target method is executed.....
You can see that the log has been printed before and after the execution of the target method. Just in the main method above, we wrote the bytecode of the proxy object to the file. Now let's analyze:
Decompile the & proxy.class file as follows:
You can see that it is implemented through the implementation interface.
JDK can only proxy classes that implement interfaces. If a class does not implement interfaces, it cannot create proxies for these classes. At this time, CGLIB can be used for proxy.
CGLIB dynamic proxy
Next, let's look at how CGLIB is implemented.
First, create a new class that needs proxy. It does not implement any interface:
1public class UserServiceImplCglib{ 2 public User query(String name) { 3 System.out.println("according to name Query user succeeded, name = " + name); 4 User user = new User(name, 20, 1, 1000, "java"); 5 return user; 6 } 7}
Now you need to use CGLIB to add logs before and after the execution of method query:
Using CGLIB to implement dynamic proxy also needs to implement interface MethodInterceptor, override intercept method:
1public class CglibMethodInterceptor implements MethodInterceptor { 2 3 @Override 4 public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 5 6 System.out.println("log: Before the target method is executed, parameter = " + args); 7 8 Object retVal = methodProxy.invokeSuper(obj, args); 9 10 System.out.println("log: After the target method is executed, Return value = " + retVal); 11 return retVal; 12 } 13}
Test:
1public static void main(String[] args) { 2 3 // Write proxy class to file 4 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\"); 5 6 Enhancer enhancer = new Enhancer(); 7 enhancer.setSuperclass(UserServiceImplCglib.class); 8 enhancer.setCallback(new CglibMethodInterceptor()); 9 10 // Create proxy object 11 UserServiceImplCglib userService = (UserServiceImplCglib) enhancer.create(); 12 System.out.println("Type of dynamic proxy = " + userService.getClass().getName()); 13 14 userService.query("zhangsan"); 15}
result:
1 Type of dynamic proxy = main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85 2log: Before the target method is executed, parameter = [Ljava.lang.Object;@77556fd 3 according to name Query user succeeded, name = zhangsan 4log: After the target method is executed, Return value = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
You can see that the result is the same as that of using JDK dynamic proxy. In addition, you can see that the type of proxy class is main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$2edd85, which is a subclass of UserServiceImplCglib, that is, CGLIB is implemented through inheritance.
summary
-
JDK's dynamic proxy is implemented through the mechanism of reflection and interceptor. It will generate a proxy class for the proxy interface.
-
The dynamic proxy of CGLIB is implemented by inheritance. The class file of the proxy class is loaded in and processed by modifying its bytecode to generate subclasses.
-
JDK dynamic proxy can only generate proxy for classes that implement interfaces, not for classes.
-
CGLIB implements proxy for classes. It mainly generates a subclass of the specified class and overrides the methods in it. However, because inheritance is adopted, final classes or methods cannot be proxy.
-
In Spring AOP, if the interface is implemented, JDK proxy is used by default, or CGLIB proxy can be forced. If the class to be proxy does not implement any interface, CGLIB proxy will be used, and Spring will switch automatically.
The above chestnuts for implementing Spring AOP are implemented by annotation. In addition, the functions of AOP can also be realized through configuration files. The above is a detailed use process of Spring AOP.