AOP #
AOP的作用 #
AOP的最大作用:采用代码模式,将核心业务,与非核心业务进行分离关注
核心业务,我们采用纵向关注,而非核心业务,我们采用横向关注
交叉业务:不同的功能模块中,都拥有的业务(几乎都是非核心功能)
总结:AOP最终目的就是作用在方法上,并且对这个方法进行增强(添加逻辑代码)
AOP在项目的应用(面试题) #
- 事务控制
- 日志记录
- 异常处理
- 敏感词过滤
AOP的名词 #
- Joinpoint(连接点):切面中需要加入公共程序的地方!(某一个具体的要增强的方法)
- Pointcut(切入点):符合某种规则的连接点,好多个连接点用对象化表示出来!(多个要增强的方法的组合)
- Advice(通知):需要加入的公共程序!(要增强的逻辑代码),根据通知增强在方法上的位置不同分为不同的类型
- 环绕通知:目标方法前/后调用,阻止方法调用
- 前置通知:在目标方法前调用
- 后置通知:在目标方法后调用
- 异常通知:当目标方法抛出异常时调用
- 返回通知:在我们的目标方法正常返回值后运行
- Aspect(切面),在连接点上做的一系列行为!(多个要增强的方法添加增强逻辑后的结果)
切入点表达式 #
用来匹配需要增强的切入点(多个方法)
execution():用于匹配方法声明符合格式的方法
args():用于匹配方法参数为指定类型的执行方法
this():用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target():用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
within():用于匹配指定类型内的方法执行;
@args():于匹配当前执行的方法传入的参数持有指定注解的执行;
@target():用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@within():用于匹配所以持有指定注解的类的方法;
@annotation():用于匹配当前执行方法持有指定注解的方法;
execution() #
通过权限修饰符、方法返回值类型、方法所在类路径、方法名称、参数匹配方法
- 固定格式:
execution([权限修饰符][返回类型][类全路径][方法名称][参数列表]) execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)modifiers-pattern:访问修饰符,可以省略ret-type-pattern:返回类型,不能省略declaring-type-pattern:类的类路径,可以省略name-pattern:方法的名称,不能省略param-pattern:参数列表,不能省略throws-pattern:异常列表,可以省略*:通配符
// 匹配top.ygang.service包下所有类的所有方法
execution(* top.ygang.service.*.*(..))
// 匹配所有public方法
execution(public * *(..))
// 匹配save开头的方法
execution(* save*(..))
// 匹配指定类的指定方法, 拦截时候一定要定位到方法
execution(public top.ygang.g_pointcut.OrderDao.save(..))
// 匹配指定类的所有方法
execution(* top.ygang.g_pointcut.UserDao.*(..))
// 匹配指定包,以及其子包下所有类的所有方法
execution(* top..*.*(..))
// || 和 or表示两种满足其一即可,取两个表达式的并集
execution(* top.ygang.g_pointcut.UserDao.save()) || execution(* top.ygang.g_pointcut.OrderDao.save())
execution(* top.ygang.g_pointcut.UserDao.save()) or execution(* top.ygang.g_pointcut.OrderDao.save())
// && 和 and表示两种都同时满足才行,取交集
execution(* top.ygang.g_pointcut.UserDao.save()) &&execution(* com.ygang.g_pointcut.OrderDao.save())
execution(* top.ygang.g_pointcut.UserDao.save()) and execution(* top.ygang.g_pointcut.OrderDao.save())
// 取非值, !和not表示不在该范围内的作为切点
!execution(* top.ygang.g_pointcut.OrderDao.save())
not execution(* top.ygang.g_pointcut.OrderDao.save())
args() #
通过方法参数匹配
// 匹配方法第一个参数为User类型,剩余参数无限制
args(top.ygang.User,...)
// 匹配方法第一个参数为User类型,且仅有这一个参数
args(top.ygang.User)
Spring AOP #
依赖 #
需要引入spring-aspects,才可以使用Spring AOP功能
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
Spring AOP的底层原理(动态代理) #
Spring AOP的底层原理主要依赖于动态代理技术。它使用了两种主要的代理方式:JDK动态代理和CGLIB代理。
需要注意的是,Spring AOP是基于代理的方式实现的,只能拦截被代理对象的外部方法调用。如果在目标对象内部方法中进行自我调用,那么切面和通知将不会被触发。
JDK动态代理 #
- 当目标对象实现了接口时,Spring AOP使用JDK动态代理来生成代理对象。
- JDK动态代理是通过
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现的。 - 在运行时,通过
Proxy.newProxyInstance()方法创建代理对象,并指定InvocationHandler来处理方法调用。 - InvocationHandler中的
invoke()方法在方法调用前后织入横切逻辑。
CGlib #
- 当目标对象没有实现接口时,Spring AOP使用CGLIB代理来生成代理对象。
- CGLIB(Code Generation Library)是一个开源的字节码生成库,可以在运行时生成子类来代理目标对象。
- CGLIB通过继承的方式创建代理类,重写目标类中的方法,并在方法调用前后织入横切逻辑。
Spring AOP的实现 #
基于XML配置文件 #
使用Spring提供的通知类型接口 #
创建Bean,以及需要被增强的方法
public class MyService {
public void doSomeThing(String name){
System.out.println("my service: doSomeThing");
System.out.println(name);
}
}
创建通知类,根据需求实现通知类型的接口即可
- 环绕通知:
org.aopalliance.intercepter.MethodInterceptor - 前置通知:
org.springframework.aop.MethodBeforeAdvice - 后置通知:
org.springframework.aop.AfterAdvice - 异常通知:
org.springframework.aop.ThrowsAdvice - 返回通知:
org.springframework.aop.AfterReturningAdvice
// 前置通知
public class TestAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("被代理的对象执行的方法:"+ method);
System.out.println("被代理的对象执行的方法的参数是:"+ Arrays.toString(objects));
System.out.println("被代理的对象是:"+ o);
}
}
Spring配置文件中管理Bean以及定义切面
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
<bean id="myService" class="top.ygang.springdemo.MyService"/>
<bean id="testAdvice" class="top.ygang.springdemo.TestAdvice"/>
<aop:config>
<aop:advisor advice-ref="testAdvice" pointcut="execution(* top.ygang.springdemo.MyService.*(..))"/>
</aop:config>
</beans>
main方法测试
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService bean = applicationContext.getBean(MyService.class);
bean.doSomeThing("grady");
}
输出结果
被代理的对象执行的方法:public void top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的对象执行的方法的参数是:[grady]
被代理的对象是:top.ygang.springdemo.MyService@34b7ac2f
my service: doSomeThing
grady
自定义通知类 #
创建Bean,以及需要被增强的方法
public class MyService {
public String doSomeThing(String name){
System.out.println("my service: doSomeThing");
System.out.println(name);
return "returnMsg";
}
}
编写自定义通知类,注意方法的形参
public class TestAdvice {
public void beforeMethod(JoinPoint jp) {
//获取被代理方法对象
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
System.out.println("前置通知");
System.out.println("被代理的对象是:" + jp.getTarget());
System.out.println("被代理的方法是:" + method);
System.out.println("被代理的方法形参是:" + Arrays.toString(jp.getArgs()));
}
public void afterMethod(JoinPoint jp) {
System.out.println("后置通知");
}
public void afterReturning(JoinPoint jp, Object c) {
System.out.println("后置返回通知");
System.out.println("被代理的方法返回值是:" + c);
System.out.println(c);
}
public void afterThrowing(JoinPoint jp,Exception e) {
System.out.println("异常通知");
System.out.println("异常为:" + e);
}
public void around(ProceedingJoinPoint pjp) {
try {
System.out.println("环绕通知开始");
Object a = pjp.proceed();
System.out.println("环绕通知结束");
System.out.println("被代理的方法返回值是:" + a);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Spring配置文件中管理Bean以及定义切面
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"
>
<bean id="myService" class="top.ygang.springdemo.MyService"/>
<bean id="testAdvice" class="top.ygang.springdemo.TestAdvice"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* top.ygang.springdemo.MyService.*(..))"/>
<aop:aspect ref="testAdvice">
<aop:before method="beforeMethod" pointcut-ref="pc"/>
<aop:before method="afterMethod" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" returning="c" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pc"/>
<aop:around method="around" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
</beans>
main方法测试
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService bean = applicationContext.getBean(MyService.class);
bean.doSomeThing("grady");
}
输出结果
前置通知
被代理的对象是:top.ygang.springdemo.MyService@a514af7
被代理的方法是:public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的方法形参是:[grady]
后置通知
环绕通知开始
my service: doSomeThing
grady
环绕通知结束
被代理的方法返回值是:returnMsg
后置返回通知
被代理的方法返回值是:null
null
基于注解 #
常用注解 #
| 注解 | 说明 |
|---|---|
@Aspect |
把当前类声明为切面类(自定义通知类),该类需要再加@Component注解,这样才可以被spring容器管理 |
@Before |
把当前方法看成是前置通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用 |
@AfterReturning |
把当前方法看成是后置返回通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用 |
@AfterThrowing |
把当前方法看成是异常通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用 |
@After |
把当前方法看成是后置通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用 |
@Around |
把当前方法看成是环绕通知,value:用于指定切入点表达式,还可以指定切入点表达式的引用 |
@Pointcut |
指定切入点表达式,value:指定表达式的内容 |
@Order |
用于多个通知增强同一个方法时,在通知类上设置优先级,value:数值类型,越小优先级越高 |
注意:要使用以上注解,必须在Spring配置文件中开启自动代理,对Spring AOP相关注解的支持<aop:aspectj-autoproxy/>
<!-- 启动自动代理,开启对AOP注解的支持 -->
<aop:aspectj-autoproxy/>
该标签有几个属性可以选择
proxy-target-class(默认值为false):用于指定是否使用基于类的代理,默认情况下使用基于接口的代理。如果设置为true,则将使用CGLIB库创建基于类的代理。expose-proxy(默认值为false):用于指定是否将代理对象公开给AopContext,以便在切面内部通过AopContext.currentProxy()方法访问。如果设置为true,则可以在切面内部访问代理对象。
代码示例 #
创建Bean,以及需要被增强的方法
@Component
public class MyService {
public String doSomeThing(String name){
System.out.println("my service: doSomeThing");
System.out.println(name);
return "returnMsg";
}
}
创建切面类
@Aspect
@Component
public class TestAdvice {
@Pointcut("execution(* top.ygang.springdemo.MyService.*(..))")
public void pc(){}
@Before("pc()")
public void before(JoinPoint jp){
//获取被代理方法对象
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
System.out.println("前置通知");
System.out.println("被代理的对象是:" + jp.getTarget());
System.out.println("被代理的方法是:" + method);
System.out.println("被代理的方法形参是:" + Arrays.toString(jp.getArgs()));
}
}
配置文件开启Spring各种注解的支持
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
>
<context:component-scan base-package="top.ygang.*"/>
<context:annotation-config/>
<!-- 启动自动代理,开启对AOP注解的支持 -->
<aop:aspectj-autoproxy/>
</beans>
main方法测试
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService bean = applicationContext.getBean(MyService.class);
bean.doSomeThing("grady");
}
输出结果
前置通知
被代理的对象是:top.ygang.springdemo.MyService@f2ff811
被代理的方法是:public java.lang.String top.ygang.springdemo.MyService.doSomeThing(java.lang.String)
被代理的方法形参是:[grady]
my service: doSomeThing
grady
AspectJ #
AspectJ是一个面向切面的框架,不是Spring框架的一部分,可以单独使用,是目前最好用,最方便的AOP框架,和spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。
Spring AOP和AspectJ的区别 #
Spring AOP #
- 基于动态代理来实现,默认如果实现接口的,用JDK提供的动态代理实现,如果没有实现接口则使用CGLIB实现
- Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
- 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好
- 注意:Spring AOP归根结底也是动态代理,所以和AspectJ的关系并不大,只是引入了Aspect、advice、joinpoint等等概念
AspectJ #
-
AspectJ来自于Eclipse基金会
-
AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:
- 编译期织入(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- 编译后织入(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- 类加载后织入(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的
agent:-javaagent:xxx/xxx/aspectjweaver.jar。
-
AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案
-
因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的