Detailed explanation of Spring AOP pointcut expression

Keywords: Spring Java xml Programming

1. introduction

The biggest advantage of object-oriented programming, also known as OOP (Object Oriented Programming), is that it can encapsulate business modules to achieve the purpose of function reuse. Through object-oriented programming, different templates can be assembled with each other to realize more complex business modules. Its structure can be shown in the following figure:

Object-oriented programming solves the problem of encapsulation and reuse of business modules. However, for some modules, it does not belong to a single business module, but runs through several or all modules according to different situations. For example, for login authentication, only a few interfaces can be opened for users to use without login (generally, login is implemented by interceptors, but the aspect idea is the same); for example, performance statistics needs to record the call of each business module and the call time of the monitor. As you can see, these modules cross each business module. If you use the object-oriented method, you need to add corresponding duplicate code to each module that has been encapsulated. In this case, aspect oriented programming can be used.

Aspect Oriented Programming, also known as AOP (Aspect Oriented Programming), refers to weaving certain aspect logic into specific business modules in a certain way, so as to wrap the calls of these business modules. The structure is as follows:

2. Players of AOP

2.1 main roles of AOP

  • Tangent: expressed by tangent expression, specifies the range size of the business module to be wrapped by the current tangent logic;
  • Advice: that is, the aspect logic, which specifies the logic currently used to wrap the business module specified by the aspect.

2.2 main types of advice

  • @Before: the method marked in this annotation is executed before the code of the business module is executed. It cannot prevent the execution of the business module unless an exception is thrown;
  • @AfterReturning: the annotation method is executed after the business module code is executed;
  • @AfterThrowing: the annotated method is executed after the specified exception is thrown by the business module;
  • @After: the annotated method is executed after all Advice is executed, regardless of whether the business module throws an exception or not, similar to finally;
  • @Around: the annotation function is the most powerful. The annotated method is used to write the code for the package business module execution. It can pass in a ProceedingJoinPoint code to call the business module. Whether it is the pre call logic or the post call logic, it can be written in this method, or even it can block the call of the business module according to certain conditions;
  • @DeclareParents: it is an Introduction type model, which is used on attribute declaration. It is mainly used to add new interface and corresponding implementation for the specified business module.
  • @Aspect: strictly speaking, it does not belong to a kind of Advice. This annotation is mainly used in class declaration to indicate that the current class is a class that organizes section logic. In this annotation, you can specify the instantiation mode of the current class. There are three main types: singleton, perthis and pertarget. The specific usage mode will be explained later.

It should be noted here that @ Before is executed Before the business logic is executed, and it corresponds to @ AfterReturning, not @ After. @ After is executed After all faceted logic is executed, no matter whether an exception is thrown or not.

3. Tangent point expression

3.1 execution

Because the minimum granularity of Spring facet is to reach the method level, and execution expression can be used to explicitly specify method related parts such as method return type, class name, method name and parameter name. In Spring, most business scenarios that need to use AOP only need to reach the method level, so execution expression is the most widely used. The syntax of the execution expression is as follows:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

Here, the question mark indicates whether the current item can have or not. The semantics of each item are as follows:

  • Modifiers pattern: visibility of methods, such as public, protected;
  • RET type pattern: the return value type of the method, such as int, void, etc;
  • Declare type pattern: the full path name of the class where the method is located, such as com.spring.Aspect;
  • Name pattern: method name type, such as businessservice();
  • Param pattern: the parameter type of the method, such as java.lang.String;
  • Throws pattern: the type of exception thrown by the method, such as java.lang.Exception;

Here is an example of using an execution expression:

execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

The above pointcut expression will match the method named businessService in the com.spring.BusinessObject class. The first parameter must be a method of type java.lang.String. In the above example, we use the.. wildcard. There are two types of wildcards:

  • *Wildcard, which is mainly used to match a single word, or a word with a prefix or suffix.

The following example shows a method with a return value of any type in the com.spring.service.BusinessObject class and zero number of parameters:

execution(* com.spring.service.BusinessObject.*())

The following example shows that the return value is of any type. In the com.spring.service package, the class is prefixed with Business, and the number of parameters in the class is zero

execution(* com.spring.service.Business*.*())
  • ... wildcard, which represents 0 or more items, is mainly used in declaration type pattern and param pattern. If it is used in declaration type pattern, it means matching the current package and its subpackages. If it is used in param pattern, it means matching 0 or more parameters.

The following example shows that the matching return value is of any type, and it is a method with the name of businessService under the com.spring.service package and its subpackages, and the method cannot have any parameters:

execution(* com.spring.service..*.businessService())

It should be noted that.. in package path service..*.businessService() should be understood as continuation of the previous service path, which means to the service path, or continuation of the service path, so as to include its sub package path; the following *. businessService(), where * means to match a word, because it is before the method name, it means to match any Class.

The following example is an example of using.. to represent any number of parameters. It should be noted that when representing parameters, some types of parameters can be specified in parentheses in advance, while the rest parameters are matched by

execution(* com.spring.service.BusinessObject.businessService(java.lang.String,..))

3.2 within

The granularity of within expression is class, and its parameter is the class name of full path (wildcard can be used), indicating that all classes matching the current expression will be surrounded by the current method. The syntax of the within expression is as follows:

within(declaring-type-pattern)

The within expression can only be specified to the class level. The following example indicates that all methods in com.spring.service.BusinessObject are matched:

within(com.spring.service.BusinessObject)

Wildcards can be used to match the path and class name of the within expression. For example, the following expression will match all classes under the com.spring.service package, excluding the classes in the sub package:

within(com.spring.service.*)

The following expression indicates that all classes under the com.spring.service package and its subpackages are matched:

within(com.spring.service..*)

3.3 args

The args expression is used to match a method of a specified parameter type and a specified number of parameters, regardless of its classpath or method name. It should be noted that the args specified parameter must be full path. The syntax of the args expression is as follows:

args(param-pattern)

The following example shows a method that only one parameter is matched and the parameter type is java.lang.String:

args(java.lang.String)

You can also use wildcards, but here they can only use.., not *. The following is an example of using wildcards. The pointcut expression will match the first parameter as java.lang.String and the last parameter as java.lang.Integer. There can be any number and type of parameter methods in the middle:

args(java.lang.String,..,java.lang.Integer)

3.4 this and target

This and target need to be explained together. The main purpose is to distinguish them. Both this and target expressions can only specify classes or interfaces. In the aspect oriented programming specification, this represents the object matching the object method referred to in the current tangent expression, and target represents the object matching the type specified in the tangent expression. For example, there are two classes A and B, and A calls A method of B. if the pointcut expression is this(B), then the instance of A will be matched, that is, it will be surrounded by Advice using the current pointcut expression. If the pointcut expression is target(B), then the instance of B will also be matched, and it will be surrounded by Advice using the current pointcut expression.

Before explaining the use of this and target in Spring, we need to explain a concept: business object (target object) and proxy object. For aspect programming, there is a target object and a proxy object. The target object is the business logic object we declare, and the proxy object is the object generated after wrapping the business logic with aspect logic. If you use the Jdk dynamic proxy, the business object and proxy object will be two objects. When you call the proxy object logic, the logic of the target object will be called in its tangent logic. If you use the Cglib proxy, because the subclass is used for the tangent logic weaving, there is only one object, that is, the subclass object of the business class woven into the proxy logic Business class objects will not be generated.

In Spring, it rewrites the semantics of this, that is, if the proxy object generated by the current object conforms to the type specified by this, then it is woven into the tangent logic. Simply put, this will match the proxy object to a class of the specified type. The semantics of target do not change, that is, it will match the business object to the class of the specified type. Here is a simple example of using this and target expressions:

this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)

As can be seen from the above explanation, the difference between this and target is not big. In most cases, the effect is the same, but there are still some differences. Spring uses two main proxy methods: Jdk proxy and Cglib proxy (for the explanation of these two proxy methods, see my article The implementation of agent mode and the comparison of its advantages and disadvantages ) For these two types of proxy, it is very important to understand the following two aspects about the target object and proxy object:

  • If the method that the target object is represented is the method of an interface it implements, then the proxy object will be generated by using the Jdk proxy. At this time, the proxy object and the target object are two objects, and both of them implement the interface;
  • If the target object is a class, and it does not implement any interface, the Cglib proxy will be used to generate the proxy object, and only one object will be generated, that is, the object of the Cglib proxy class.

Combined with the above two points, it is relatively simple to understand the similarities and differences between this and target. There are three situations to explain:

  • this(SomeInterface) or target(SomeInterface): in this case, no matter for the Jdk agent or Cglib agent, both the target object and the proxy object implement the SomeInterface interface (the subclass of the target object generated by Cglib also implements the SomeInterface interface interface), so this and target semantics are consistent, and the effect of these two expressions is the same;
  • this(SomeObject) or target(SomeObject), where SomeObject does not implement any interface: in this case, Spring will use Cglib proxy to generate the proxy class object of SomeObject. Because the proxy class is a subclass of SomeObject, and the object of the subclass also conforms to the type of SomeObject, this will be matched. For target, because the target object itself is of the type of SomeObject, because The two expressions have the same effect;
  • this(SomeObject) or target(SomeObject), where SomeObject implements an interface: in this case, although a specific object type is specified in the expression, Spring uses the Jdk proxy to generate the proxy object for it by default because it implements an interface. The proxy object generated by the Jdk proxy and the target object implement the same interface, but the proxy pair The object is different from the target object. Because the proxy object is not of SomeObject type, it does not conform to this semantics at this time. Because the target object is of SomeObject type, the target semantics are consistent. At this time, the effect of this and target is different. If Spring is forced to use Cglib proxy, the proxy objects generated are all of SomeObject subclasses Object of type SomeObject, so the semantics of this and target are consistent, and the effect is consistent.

As for the similarities and differences between this and target, we use the following example for a simple demonstration:

// Target class
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// Section class
@Aspect
public class MyAspect {
  @Around("this(com.business.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
<!-- bean Declaration document -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>
// Driver class
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Apple fruit = (Apple) context.getBean("apple");
    fruit.eat();
  }
}

Execute the main method in the driver class, and the result is as follows:

this is before around advice
Apple.eat method invoked.
this is after around advice

In the above example, Apple does not implement any interfaces, so it uses Cglib proxy, and this expression will match the Apple object. If you change the tangent expression to target or execute the above code, you will find that the result is the same:

target(com.business.Apple)

If we modify Apple's declaration to implement an interface, the execution difference between this and target will be shown here:

public class Apple implements IApple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = (Fruit) context.getBean("apple");
    fruit.eat();
  }
}

We still execute the above code. For this expression, the execution result is as follows:

Apple.eat method invoked.

For the target expression, the execution result is as follows:

this is before around advice
Apple.eat method invoked.
this is after around advice

As you can see, in this case, the execution results of this and target expressions are not the same, which is just in line with the third case we explained earlier.

3.5 @within

Earlier, we explained that the semantic representation of within matches the class instance of the specified type. Here, @ within represents matching the class with the specified annotation. Its usage syntax is as follows:

@within(annotation-type)

The following example shows matching classes annotated with com.spring.annotation.BusinessAspect annotation:

@within(com.spring.annotation.BusinessAspect)

Here we use an example to demonstrate the usage of @ within (here the configuration of driver class and xml file is consistent with that used in Section 3.4, omitted here):

// Annotation
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FruitAspect {
}
// Target class
@FruitAspect
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// Section class
@Aspect
public class MyAspect {
  @Around("@within(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

The above facet indicates that the class matching with the annotation of fruit aspect is used by apple, so the call of Apple class method will be surrounded by facet. The following results can be obtained by running the driver class, indicating that Apple.eat() method is indeed surrounded:

this is before around advice
Apple.eat method invoked.
this is after around advice

3.6 @annotation

The usage of @ annotation is similar to @ within, indicating that the matching method of using @ annotation to specify annotation annotation will be surrounded, and its usage syntax is as follows:

@annotation(annotation-type)

The following example shows how to match annotation using com.spring.annotation.BusinessAspect:

@annotation(com.spring.annotation.BusinessAspect)

Here we will continue to use the examples in section 3.5 to explain the usage of @ annotation. But here we need to modify the way Apple and MyAspect use and specify annotations. The reason why FruitAspect doesn't need to be modified is that when declaring this annotation, it has specified that it can be used on classes, methods and parameters:

// Target class, which moves friendaspect to a method
public class Apple {
  @FruitAspect
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
@Aspect
public class MyAspect {
  @Around("@annotation(com.business.annotation.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

Here, the Apple.eat() method is annotated with the fruit aspect annotation, so the execution of this method will be surrounded by facets, and the execution results are as follows:

this is before around advice
Apple.eat method invoked.
this is after around advice

3.7 @args

@ within and @ annotation respectively indicate that the matching class and method annotated with the specified annotation will be matched, @ args indicates that the method will be matched when the class annotated with the specified annotation is used as the parameter of a method. The syntax of the @ args annotation is as follows:

@args(annotation-type)

The following example shows how to match a class annotated with com.spring.annotation.friendaspect annotation as a parameter:

@args(com.spring.annotation.FruitAspect)

Here we use the following example to explain the usage of @ args:

<!-- xml configuration file -->
<bean id="bucket" class="chapter7.eg1.FruitBucket"/>
<bean id="aspect" class="chapter7.eg6.MyAspect"/>
<aop:aspectj-autoproxy/>
// Parameter classes annotated with annotations
@FruitAspect
public class Apple {}
// Target class using Apple parameters
public class FruitBucket {
  public void putIntoBucket(Apple apple) {
    System.out.println("put apple into bucket.");
  }
}
@Aspect
public class MyAspect {
  @Around("@args(chapter7.eg6.FruitAspect)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
// Driver class
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    FruitBucket bucket = (FruitBucket) context.getBean("bucket");
    bucket.putIntoBucket(new Apple());
  }
}

Here, the parameter apple of the friendbucket.putintobucket (Apple) method is annotated with the friendaspect specified by the @ args annotation, so the call of this method will be surrounded. Execute the driver class, and the result is as follows:

this is before around advice
put apple into bucket.
this is after around advice

3.8 @DeclareParents

DeclareParents, also known as Introduction, refers to the Introduction of new properties and methods for the specified target class. The principle of @ DeclareParents is easy to understand, because whether it's a Jdk agent or a Cglib agent, if you want to introduce a new method, you only need to weave the newly declared method into the proxy class in a certain way, because the proxy class is a newly generated class, so the weaving process is more convenient. The following is the usage syntax of @ DeclareParents:

@DeclareParents(value = "TargetType", defaultImpl = WeaverType.class)
private WeaverInterface attribute;

Here, the TargetType represents the target type to be woven (with full path), the method to be added is declared in the WeaverInterface, and the specific implementation of the method to be woven is declared in the weaver type. The following example shows the method of weaving the IDescriber interface declaration in the Apple class:

@DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
private IDescriber describer;

Here we use the following example to explain the usage of @ DeclareParents. The configuration file is consistent with Section 3.4, which is omitted here:

// Target class of weaving method
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// Interface to weave in
public interface IDescriber {
  void desc();
}
// Default implementation of the interface to weave
public class DescriberImpl implements IDescriber {
  @Override
  public void desc() {
    System.out.println("this is an introduction describer.");
  }
}
// Section example
@Aspect
public class MyAspect {
  @DeclareParents(value = "com.spring.service.Apple", defaultImpl = DescriberImpl.class)
  private IDescriber describer;
}
// Driver class
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    IDescriber describer = (IDescriber) context.getBean("apple");
    describer.desc();
  }
}

In MyAspect, it is declared that we need to weave the method of IDescriber into the apple instance. In the driver class, we can see that we get the apple instance, but the bean we get can be strongly converted to the type of IDescriber, which indicates that our weaving operation is successful.

3.9 perthis and pertarget

In Spring AOP, there is only one instance of the Aspect class, such as the MyAspect class we have been using before. Suppose that the Aspect class we used needs to have a certain state to apply to some special situations, such as multi-threaded environment, then the Aspect class of the single instance does not meet our requirements. In Spring AOP, the facet class is single instance by default, but it also supports the facets of two other multi instance facet instances, namely, perthis and pertarget. Note that both perthis and pertarget are used in the @ Aspect annotation of the facet class. Here, both the perthis and pertarget expressions specify a tangent expression, whose semantics are very similar to those described earlier. Perthis means that if the proxy class of a class conforms to its specified tangent expression, a tangent instance will be declared for each qualified target class; pertarget means that if a target class conforms to its specified tangent expression , a tangent instance will be declared for each qualified class. From the above semantics, we can see that the meaning of perthis and pertarget are very similar. The following is the usage syntax of perthis and pertarget:

perthis(pointcut-expression)
pertarget(pointcut-expression)

Because the use effect of perthis and pertarget is consistent in most cases, we mainly discuss the difference between perthis and pertarget here. One thing to note about the use of perthis and pertarget is that since both perthis and pertarget declare a tangent instance for each qualified class, the tangent class must add prototype to the declaration in the configuration file, otherwise Spring startup will report an error. Here is an example we used:

<!-- xml configuration file -->
<bean id="apple" class="chapter7.eg1.Apple"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>
// Interface implemented by target class
public interface Fruit {
  void eat();
}
// Business class
public class Apple implements Fruit {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// Section class
@Aspect("perthis(this(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("this(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
// Driver class
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

Here we use the faceted expression syntax of perthis(this(com.spring.service.Apple)), where this means that the matching proxy class is an apple type class, and perthis means that a faceted class will be created for each instance of these classes. Because Apple implements the Fruit interface, spring uses the Jdk dynamic proxy to generate the proxy class for it, that is, both the proxy class and apple implement the Fruit interface, but the proxy class is not Apple type, so the aspect declared here will not match the apple class. Execute the above driver class, and the result is as follows:

Apple.eat method invoked.

The results show that the Apple class is indeed not surrounded. If we talk about changing perthis and this in the tangent class to pertarget and target, what's the effect:

@Aspect("pertarget(target(com.spring.service.Apple))")
public class MyAspect {

  public MyAspect() {
    System.out.println("create MyAspect instance, address: " + toString());
  }

  @Around("target(com.spring.service.Apple)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}

The results are as follows:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice

As you can see, the apple class is surrounded by facets. Here target indicates that the target class is of Apple type. Although Spring uses Jdk dynamic proxy to realize the aspect wrapping, the proxy class is not of Apple type, but the target class is of Apple type, which conforms to the semantics of target. And pertarget will create a proxy class instance for each class instance of qualified expression, so Apple will be surrounded here.

Because the difference between the proxy class and the target class is very small, the difference between per this and pertarget is also very small, and the use effect is the same in most cases. For the creation of faceted multi instance, the demonstration is relatively simple. We can modify the Apple instance in the xml file to prototype type, and obtain the Apple instance in the driver class many times:

<!-- xml configuration file -->
<bean id="apple" class="chapter7.eg1.Apple" scope="prototype"/>
<bean id="aspect" class="chapter7.eg6.MyAspect" scope="prototype"/>
<aop:aspectj-autoproxy/>
public class AspectApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Fruit fruit = context.getBean(Fruit.class);
    fruit.eat();
    fruit = context.getBean(Fruit.class);
    fruit.eat();
  }
}

The results are as follows:

create MyAspect instance, address: chapter7.eg6.MyAspect@48fa0f47
this is before around advice
Apple.eat method invoked.
this is after around advice
create MyAspect instance, address: chapter7.eg6.MyAspect@56528192
this is before around advice
Apple.eat method invoked.
this is after around advice

The create MyAspect instance printed twice in the execution result indicates that the current slice instance has been created twice, which is also in line with our two attempts to get the Apple instance.

4. summary

In this paper, AOP is first introduced, then the roles in facet are introduced, and finally the syntax of different types of expression in facet expression is introduced in detail.

133 original articles published, praised 126, visited 490000+
His message board follow

Posted by blackhorse on Thu, 27 Feb 2020 19:20:45 -0800