AOP的最大作用:采用代码模式,将核心业务,与非核心业务进行分离关注
核心业务,我们采用纵向关注,而非核心业务,我们采用横向关注
交叉业务:不同的功能模块中,都拥有的业务(几乎都是非核心功能)
总结:AOP最终目的就是作用在方法上,并且对这个方法进行增强(添加逻辑代码)
用来匹配需要增强的切入点(多个方法)
execution()
:用于匹配方法声明符合格式的方法
args()
:用于匹配方法参数为指定类型的执行方法
this()
:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target()
:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
within()
:用于匹配指定类型内的方法执行;
@args()
:于匹配当前执行的方法传入的参数持有指定注解的执行;
@target()
:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@within()
:用于匹配所以持有指定注解的类的方法;
@annotation()
:用于匹配当前执行方法持有指定注解的方法;
通过权限修饰符、方法返回值类型、方法所在类路径、方法名称、参数匹配方法
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())
通过方法参数匹配
// 匹配方法第一个参数为User类型,剩余参数无限制
args(top.ygang.User,...)
// 匹配方法第一个参数为User类型,且仅有这一个参数
args(top.ygang.User)
需要引入spring-aspects
,才可以使用Spring AOP功能
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
Spring AOP的底层原理主要依赖于动态代理技术。它使用了两种主要的代理方式:JDK动态代理和CGLIB代理。
需要注意的是,Spring AOP是基于代理的方式实现的,只能拦截被代理对象的外部方法调用。如果在目标对象内部方法中进行自我调用,那么切面和通知将不会被触发。
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现的。Proxy.newProxyInstance()
方法创建代理对象,并指定InvocationHandler来处理方法调用。invoke()
方法在方法调用前后织入横切逻辑。创建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是一个面向切面的框架,不是Spring框架的一部分,可以单独使用,是目前最好用,最方便的AOP框架,和spring中的aop可以集成在一起使用,通过Aspectj提供的一些功能实现aop代理变得非常方便。
AspectJ来自于Eclipse基金会
AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:
agent:-javaagent:xxx/xxx/aspectjweaver.jar。
AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案
因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的