切点表达式
上篇文章中,我们一直使用切点表达式来描述切点。
切点表达式常见有两种表达方式:
- execution(......):根据方法的签名来匹配
- @annotation:根据注解匹配
execution表达式
execution()是最常用的切点表达式,用来匹配方法,语法为:
*:匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
- 包名使用*表示任意包(一层包使用一个*)
- 类名使用*表示任意类
- 返回值使用*表示任意返回值类型
- 方法名使用*表示任意方法
- 参数使用*表示一个任意类型的参数
..:匹配多个连续的任意符号,可以统配任意层级的包,或任意类型,任意个数的参数
- 使用..配置包名,标识磁暴以及此包下的所有子包
- 可以使用..配置参数,任意个任意类型的参数
示例:
TestController下的public修饰,返回类型为String方法名为t1,无参方法:
java
execution(public String com.example.demo.controller.TestController.t1())
省略访问修饰符:
java
execution(String com.example.demo.controller.TestController.t1())
匹配所有返回类型:
java
execution(* com.example.demo.controller.TestController.t1())
匹配TestController下的所有无参方法 :
java
execution(* com.example.demo.controller.TestController.*())
匹配TestController下的所有方法 :
java
execution(* com.example.demo.controller.TestController.*(..))
匹配controller包下所有类的所有方法:
java
execution(* com.example.demo.controller.*.*(..))
匹配所有包下的TestController:
java
execution(* com..TestController.*(..))
匹配com.example.demo包下, 子孙包下所有类的所有方法 :
java
execution(* com.example.demo..*(..))
@annotation
execution表达式更适用有规则的方法,如果我们要匹配多个无规则方法就不是很方便了。
比如:TestController中的t1()方法,UsrController中的u1()方法

这时我们可以借助自定义注解的方式以及另一种切点表达式@annotation来描述这一类的切点
实现步骤:
- 编写自定义注解。
- 使用@annotation表达式来描述切点。
- 在连接点的方法上添加自定义注解。
自定义注解@MyAspect
与创建类的方式相同:
注解代码如下:
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
@Target标识了Annotation所修饰的对象范围,即该注解可以用在什么地方。
- ElementType.TYPE:用于描述类、接口(包括注解)或enum声明
- ElementType.METHOD:描述方法
- ElementType.PARAMETER:描述参数
- ElementType.TYPE_USE:可以标注任意类型
@Retention指Annotation被保留的时间长短,标明注解的生命周期。
- RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。
- RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解。
- RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码、字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息,通常用于一些需要在运行时处理的注解。
切面类
使用@annotation切点表达式定义切点,只对@MyAspect生效。
切面类代码如下:
java
@Slf4j
@Aspect
@Configuration
public class MyAspectDemo {
@Around("@annotation(com.example.springaop11111.MyAspect.MyAspect)")
public Object MyAspectDemo(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("do Around Before");
Object proceed = joinPoint.proceed();
log.info("do Around After");
return proceed;
}
}
注意:这里@annotation()括号里要添加上自定义注解的在当前项目中的相对路径!!!
添加自定义注解
在TestController中的t1()和UserController中的u1方法上添加上我们的自定义注解。
java
@Slf4j
@RestController
public class TestController {
@MyAspect
@RequestMapping("/t1")
public void t1(){
log.info("t1方法执行中");
}
/**
* 测试有异常的情况
*/
@RequestMapping("/t2")
public void t2(){
log.info("t2方法执行中");
int a = 10/0;
}
}
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@MyAspect
@RequestMapping("/u1")
public void u1(){
log.info("u1方法执行中");
}
@RequestMapping("/u2")
public void u2(){
log.info("u2方法执行中");
}
}
进行测试:
t1:
u1:
u2:
Spring AOP的实现方式
1、基于注解@Aspect
2、基于自定义注解(@annotation)
3、基于Spring API(通过xml配置的方式,自从SpringBoot广泛使用之后,这种方法几乎看不到了)
4、基于代理来实现(更加远古的一种实现方式,写法笨重,不建议使用)
参考:面试官:谈谈你对IOC和AOP的理解及AOP四种实现方式[通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)
Spring AOP原理
Spring AOP是基于动态代理来实现AOP的。
代理模式
代理模式,也叫委托模式。
定义:为其他对象提供一种代理以控制对这个对象到的访问。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象间起到中介的作用。
使用代理前:

使用代理后:
举个例子:
代理模式的主要角色
1、Subject:业务接口类。可以是抽象类或者接口(不一定有)(房客)
2、RealSubject:业务实现类。具体业务执行,也就是被代理对象(房东)
3、Proxy:代理类,RealSubject的代理(中介)
UML类图如下:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。根据代理的创建时期,代理模式分为静态代理 和动态代理 。
- 静态代理:由程序员创建代理类或者特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
- 动态代理:在程序运行时,运用反射机制动态创建而成。
还是刚才的例子:
下面我们通过代码来加深对这两种代理模式的理解~~~
静态代理
定义接口(中介和房东要做的事情):
java
public interface HouseSubject {
void rentHouse();
}
实现接口(房东出租房子):
java
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房⼦");
}
}
代理(中介,帮房东出租房子):
java
public class HouseProxy implements HouseSubject{
//将被代理对象声明为成员变量
private HouseSubject houseSubject;
public HouseProxy(HouseSubject houseSubject) {
this.houseSubject = houseSubject;
}
@Override
public void rentHouse() {
//开始代理
System.out.println("我是中介, 开始代理");
//代理房东出租房⼦
houseSubject.rentHouse();
//代理结束
System.out.println("我是中介, 代理结束");
}
}
使用:
java
public class StaticMain {
public static void main(String[] args) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy proxy = new HouseProxy(subject);
//通过代理类访问⽬标⽅法
proxy.rentHouse();
}
}
运行结果:

此时如果我们添加了新的需求:对房屋进行出售,我们就需要对上述代码进行修改:
接口:
java
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
实现接口(房东出租房子):
java
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房⼦");
}
@Override
public void saleHouse() {
System.out.println("我是房东, 我出售房⼦");
}
}
代理(中介,帮房东出租房子):
java
public class HouseProxy implements HouseSubject{
//将被代理对象声明为成员变量
private HouseSubject houseSubject;public
HouseProxy(HouseSubject houseSubject) {
this.houseSubject = houseSubject;
}
@Override
public void rentHouse() {
//开始代理
System.out.println("我是中介, 开始代理");
//代理房东出租房⼦
houseSubject.rentHouse();
//代理结束
System.out.println("我是中介, 代理结束");
}
@Override
public void saleHouse() {
//开始代理
System.out.println("我是中介, 开始代理");
//代理房东出租房⼦
houseSubject.saleHouse();
//代理结束
System.out.println("我是中介, 代理结束");
}
}
从上面的代码可以看出:静态代理如果想实现功能增强需要修改很多的代码。但是既然代理的流程是一样的,有没有一种办法,让它们通过一个代理类来实现呢?
这就需要使用动态代理技术了。
动态代理
动态代理有两种实现形式:1、JDK动态代理 2、CGLIB动态代理
JDK动态代理
定义JDK动态代理类:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
//⽬标对象即就是被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
// 代理增强内容
System.out.println("我是中介, 开始代理");
//通过反射调⽤被代理类的⽅法
Object retVal = method.invoke(target, args);
//代理增强内容
System.out.println("我是中介, 代理结束");
return retVal;
}
}
创建一个代理对象并使用:
java
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target= new RealHouseSubject();
//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target)
);
proxy.rentHouse();
}
}
CGLIB动态代理
JDK动态代理有一个致命的问题:他只能代理实现接口的类。
有些场景下,我们的业务代码是直接实现的,并没有接口定义,为了解决这个问题,我们可以用GCLIB动态代理机制来解决。
添加依赖:
XML
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
自定义MethodInterceptor(方法拦截器) :
java
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
//⽬标对象, 即被代理对象
private Object target;
public CGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 代理增强内容
System.out.println("我是中介, 开始代理");
//通过反射调⽤被代理类的⽅法
Object retVal = methodProxy.invoke(target, objects);
//代理增强内容
System.out.println("我是中介, 代理结束");
return retVal;
}
}
创建代理类并使用:
java
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target= new RealHouseSubject();
HouseSubject proxy= (HouseSubject)
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
proxy.rentHouse();
}
}