Detailed usage of Spring AOP function

Detailed usage of Spring AOP function

TSMYK Java technology programming

Preface

AOP is both familiar and unfamiliar. Anyone who knows Spring knows the concept of AOP, that is, aspect-oriented programming, which can be used to manage some peripheral services that have nothing to do with the main business, such as logging, transaction management, etc.; unfamiliar because it is basically at work I haven't used it before, and the related concepts of AOP are still in the cloud; I was looking at Spring's related source code recently, so let's first explore 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 of Spring AOP, please refer to the official website https://docs.spring.io/spring/docs/2.5.x/reference/aop.html

  1. Join point: a connection point, which represents a point during program execution. In Spring AOP, it represents a method, that is, a method can be regarded as a Join point

  2. pointcut: The point of cut is the predicate that matches the connection point. What does it mean? The point of cut that needs to execute Advice is the point of cut

  3. Advice: enhancement, operations performed at the connection point, divided into five types: pre-, post-, abnormal, final, and surround enhancement

  4. Aspect: An aspect, composed of pointcut and advice, you can simply think that the @Aspect annotated class is an aspect

  5. Target object: target object, that is, the target object woven into advice

  6. AOP proxy: proxy class, in Spring AOP, an AOP proxy is a JDK dynamic proxy object or CGLIB proxy object

  7. Weaving : Weaving, applying Aspect to the target object

Note: Among the above concepts, it is easier to confuse Join point and pointcut. It can be understood in this way. In Spring AOP, all executable methods are Join points, and all Join points can be implanted in Advice; and pointcut It can be regarded as a kind of descriptive information, which modifies the Join point, which is used to confirm the execution of Advice on which Join point,

chestnut

After understanding the concept of AOP, let's take a look at how to use Spring Aop

  1. To use Spring AOP, you must first configure the following tags in the Spring configuration file:

1<aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>

This tag has two attributes, expose-proxy and proxy-target-class, the default value is false;

expose-proxy: Do you need to save the current proxy object using ThreadLocal? What does this mean? 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_2 will not be intercepted for enhancement at this time, because the AOP proxy object is called instead of the current object, and this is used inside the test_1 method to call, so test_2 will not be intercepted and enhanced, so this attribute Expose-proxy is used to solve this problem, namely the acquisition of AOP proxy.

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 proxying only when the class that needs proxying does not implement any interface. If you want to use CGLIB for proxying, you can set this property to true.

2. Define the methods that need to be intercepted by aop to 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=" + user);
 7    }
 8
 9    @Override
10    public User query(String name) {
11        System.out.println("根据name查询用户成功");
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("查询所有用户成功, users = " + users);
22        return users;
23    }
24
25    @Override
26    public void delete(String name) {
27        System.out.println("根据name删除用户成功, name = " + name);
28    }
29
30    @Override
31    public void update(User user) {
32        System.out.println("更新用户成功, user = " + user);
33    }
34}
.

3. Define AOP aspects.
In Spring AOP, the class identified with the @Aspect annotation is an aspect, and then the pointcut and advice are defined in the aspect:

3.1 Pre-enhancement, @Before(), executed before the target method is executed


 1@Component
 2@Aspect
 3public class UserAspectj {
 4
 5    // 在方法执行之前执行
 6    @Before("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
 7    public void before_1(){
 8        System.out.println("log: 在 add 方法之前执行....");
 9    }
10}

The above method before_1() is a pre-enhancement to the add() method of the interface, that is, it is executed before the add() method is executed, and the
test:


 1@RunWith(SpringJUnit4Cla***unner.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// 结果:
15// log: 在 add 方法之前执行....
16// 添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}

If you want to obtain information such as the parameters of the target method execution, we can add the parameter JoinPoint to the pointcut method, through which to 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: 在 add 方法之前执行, 方法参数 = " + user);
9    }

Re-execute the above test code, the results are as follows:


1log: 在 add 方法之前执行, 方法参数 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
2添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}

3.2 Post-enhancement, @After(), executed after the target method is executed, whether it is a normal exit or an exception, it will be executed


1    // 在方法执行之后执行
2    @After("execution(* main.tsmyk.mybeans.inf.IUserService.add(..))")
3    public void after_1(){
4        System.out.println("log: 在 add 方法之后执行....");
5    }

Execute the test code of 3.1, and the results are as follows:


1添加用户成功,user=User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}
2log: ==== 方法执行之后 =====

3.3 Return enhancement, @AfterReturning(), executed after the target method returns normally, it will not be executed if an exception occurs, and the return value can be obtained:


1@AfterReturning(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", returning="object")
2public void after_return(Object object){
3    System.out.println("在 query 方法返回后执行, 返回值= " + object);
4}

test:


1@Test
2public void testQuery() {
3    userServiceImpl.query("zhangsan");
4}
5// 结果:
6// 根据name查询用户成功
7// 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}

When a method is enhanced by both @After() and @AfterReturning(), which one is 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: 在 query 方法返回后执行, 返回值= " + object);
4}
5
6@After("execution(* main.tsmyk.mybeans.inf.IUserService.query(..))")
7public void after_2(){
8    System.out.println("===log: 在 query 方法之后执行....");
9}

test:


1根据name查询用户成功
2===log: 在 query 方法之后执行....
3===log: 在 query 方法返回后执行, 返回值= User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}

As you can see, even if @After() is placed after @AfterReturning(), it is executed first, that is, @After() is executed before @AfterReturning().

3.4 Exception enhancement, @AfterThrowing, executes when an exception is thrown, and does not execute without throwing an exception .


1@AfterThrowing(pointcut="execution(* main.tsmyk.mybeans.inf.IUserService.query(..))", throwing = "ex")
2public void after_throw(Exception ex){
3    System.out.println("在 query 方法抛异常时执行, 异常= " + ex);
4}

Now let's modify its enhanced query() method to make it throw an exception:


1@Override
2public User query(String name) {
3    System.out.println("根据name查询用户成功");
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// 结果:
6// 在 query 方法抛异常时执行, 异常= java.lang.ArithmeticException: / by zero
7// java.lang.ArithmeticException: / by zero ...........

3.5 Surround enhancement, @Around, executed before and after target method execution


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 方法执行之前, 参数 = " + args[0].toString());
5    joinPoint.proceed();
6    System.out.println("log : delete 方法执行之后");
7}

test:


1@Test
2public void test5(){
3    userServiceImpl.delete("zhangsan");
4}
5
6// 结果:
7// log : delete 方法执行之前, 参数 = zhangsan
8// 根据name删除用户成功, name = zhangsan
9// log : delete 方法执行之后

The above are several enhancements of Spring AOP.

In the above chestnut, the pointcut expression above each method needs to be written again. Now you can use @Pointcut to declare a reusable pointcut expression, and then quote the pointcut expression above each method. can:


 1// 声明 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: 在 query 方法之前执行");
 9}
10@After("pointcut()")
11public void after_4(){
12    System.out.println("log: 在 query 方法之后执行....");
13}

indicator

In the above chestnut, the execution indicator is used, which is used to match the connection point of method execution. It is also the main indicator used by Spring AOP. Wildcards () and (..) are used in the pointcut expression. Among them, () Can represent any method, any return value, (..) represents any parameter of the method, let's look at other indicators.

1. within

All in all class matches Joinpoint (Method) under the particular packet, including the sub-packet, noted that all classes, rather than interfaces, if the interface is written, does not take effect, such as within (main.tsmyk.mybeans.impl. The Will match all Join points of all classes in the main.tsmyk.mybeans.impl package; within(main.tsmyk.mybeans.impl.. Two points will match all Join points of all classes in the package and its sub-packages.
chestnut:


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 在方法执行之前执行.....");
8}

Execute the delete method of the class UserServiceImpl under the package, and the results are as follows:


1@Test
2public void test5(){
3    userServiceImpl.delete("zhangsan");
4}
5
6// 结果:
7// test within 在方法执行之前执行.....
8// 根据name删除用户成功, name = zhangsan

2. @within

Match all methods that hold the specified annotation type, such as @within(Secure), any target object holds the Secure annotation class method; this annotation must be declared on the target object, and the declaration on the interface has no effect on it.

3. target

What matches is a target object, target (main.tsmyk.mybeans.inf.IUserService) matches all Join points under this interface:


 1@Pointcut("target(main.tsmyk.mybeans.inf.IUserService)")
 2public void anyMethod(){
 3}
 4
 5@Before("anyMethod()")
 6public void beforeAnyMethod(){
 7    System.out.println("log: ==== 方法执行之前 =====");
 8}
 9
10@After("anyMethod()")
11public void afterAnyMethod(){
12    System.out.println("log: ==== 方法执行之后 =====");
13}

After that, execute any method under this interface, it will be enhanced.

4. @target

To match a target object, this object must have a specific annotation, such as @target(org.springframework.transaction.annotation.Transactional) matches any method annotated with @Transactional

5. this

Match the execution method of the current AOP proxy object type, this(service.IPointcutService), the current AOP object implements any method of the IPointcutService interface

6. arg

Matching parameters,


 1    // 匹配只有一个参数 name 的方法
 2    @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name)")
 3    public void test_arg(){
 4
 5    }
 6
 7    // 匹配第一个参数为 name 的方法
 8    @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(name, ..)")
 9    public void test_arg2(){
10
11    }
12
13    // 匹配第二个参数为 name 的方法
14    @Before("execution(* main.tsmyk.mybeans.inf.IUserService.query(String)) && args(*, name, ..)")
15    public void test_arg3(){
16
17    }

7. @arg

Matching parameters, the parameters have specific annotations, @args(Anno)), and the method parameters are marked with Anno annotations.

8. @annotation

Match specific annotation
@annotation(org.springframework.transaction.annotation.Transactional) matches any method annotated with @Transactional.

9. bean

Ways to match specific bean names


 1    // 匹配 bean 的名称为 userServiceImpl 的所有方法
 2    @Before("bean(userServiceImpl)")
 3    public void test_bean(){
 4        System.out.println("===================");
 5    }
 6
 7    // 匹配 bean 名称以 ServiceImpl 结尾的所有方法
 8    @Before("bean(*ServiceImpl)")
 9    public void test_bean2(){
10        System.out.println("+++++++++++++++++++");
11    }

Test:
execute the method under the bean:


1@Test
2public void test5(){
3    userServiceImpl.delete("zhangsan");
4}
5//结果:
6// ===================
7// +++++++++++++++++++
8// 根据name删除用户成功, name = zhangsan

The above is how to use all the indicators of Spring AOP.

Spring AOP principle

The bottom layer of Spring AOP uses dynamic proxy; there are two ways to implement dynamic proxy, one is the dynamic proxy of JDK, the other is the dynamic proxy of CGLIB, the following two methods are used to achieve the above functions, that is, in the call When the UserServiceImpl class method, add logs before and after the method is executed.

JDK dynamic proxy

To implement JDK dynamic proxy, you must implement the InvocationHandler interface and rewrite the invoke method:


 1public class UserServiceInvocationHandler implements InvocationHandler {
 2
 3    // 代理的目标对象
 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: 目标方法执行之前, 参数 = " + args);
14
15        // 执行目标方法
16        Object retVal = method.invoke(target, args);
17
18        System.out.println("log: 目标方法执行之后.....");
19
20        return retVal;
21    }
22}

test:


 1public static void main(String[] args) throws IOException {
 2
 3    // 需要代理的对象
 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    // 代理对象
10    IUserService proxyUserService = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
11
12    System.out.println("动态代理的类型  = " + proxyUserService.getClass().getName());
13    proxyUserService.query("zhangsan");
14
15    // 把字节码写到文件
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动态代理的类型  = com.sun.proxy.$Proxy0
2log: 目标方法执行之前, 参数 = [Ljava.lang.Object;@2ff4acd0
3根据name查询用户成功
4log: 目标方法执行之后.....

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 it:

Decompile &Proxy.class file as follows:
Detailed usage of Spring AOP function

You can see that it is achieved by implementing interfaces.

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 a proxy, it does not implement any interface:


1public class UserServiceImplCglib{
2    public User query(String name) {
3        System.out.println("根据name查询用户成功, 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 method query is executed:

To use CGLIB to implement dynamic proxy, you also need to implement the Interface MethodInterceptor and override the 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: 目标方法执行之前, 参数 = " + args);
 7
 8        Object retVal = methodProxy.invokeSuper(obj, args);
 9
10        System.out.println("log: 目标方法执行之后, 返回值 = " + retVal);
11        return retVal;
12    }
13}

test:


 1public static void main(String[] args) {
 2
 3    // 把代理类写入到文件
 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    // 创建代理对象
11    UserServiceImplCglib userService = (UserServiceImplCglib) enhancer.create();
12    System.out.println("动态代理的类型 = " + userService.getClass().getName());
13
14    userService.query("zhangsan");
15}

result:


1动态代理的类型 = main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85
2log: 目标方法执行之前, 参数 = [Ljava.lang.Object;@77556fd
3根据name查询用户成功, name = zhangsan
4log: 目标方法执行之后, 返回值 = User{name='zhangsan', age=20, sex=1, money=1000.0, job='java'}

As you can see, the result is the same as using the JDK dynamic proxy. In addition, you can see that the type of proxy class is main.tsmyk.mybeans.impl.UserServiceImplCglib$$EnhancerByCGLIB$$772edd85, which is a subclass of UserServiceImplCglib, that is, CGLIB is It is achieved through inheritance.

to sum up

  1. The dynamic proxy of JDK is implemented through the mechanism of reflection and interceptor, which generates a proxy class for the proxy interface.

  2. The dynamic proxy of CGLIB is realized through inheritance, and the class file of the proxy class is loaded in and processed by modifying its bytecode to generate subclasses.

  3. JDK dynamic proxies can only generate proxies for classes that implement interfaces, not classes.

  4. CGLIB implements proxy for classes, mainly generating a subclass for the specified class and overriding the methods in it, but because it uses inheritance, the final class or method cannot be proxied.

  5. In Spring AOP, if the interface is implemented, the JDK proxy is used by default, or CGLIB proxy can be mandatory. If the class to be proxy does not implement any interface, CGLIB will be used for proxy, and Spring will switch automatically.

The above-mentioned implementation of Spring AOP chestnut is realized by the annotation method. In addition, the AOP function can also be realized through the configuration file. The above is a detailed usage process of Spring AOP.

Guess you like

Origin blog.51cto.com/15077536/2608529