介绍
AOP被称为面向切面编程,是将横切出来的逻辑代码融合到业务逻辑中,来实现和没横切之前的一样的功能。
SpringAOP是将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
相关概念
1、JoinPoint(连接点):能够被拦截到的点,在spring中,这些点指的是方法,每一个成员方法为一个连接点
2、Pointcut(切点):具体定位的连接点,具体定位到某一个方法成为切点
3、Advice(通知):添加到切点的一段逻辑代码,Spring中定义了五类通知
- 前置通知
- 后置通知
- 返回通知
- 异常通知
- 环绕通知
4、Weave(织入):将Advice(通知)添加到目标类的具体连接点的过程
5、Aspect(切面):由Pointcut(切点)和Advice(通知)组成,包括横切逻辑和连接点定义
6、Target(目标对象):被代理的目标对象,已经存在的原对象
注解配置
1、创建xml配置文件进行包扫描和切面类
2、切入点pointcut属性配置:execution(返回值类型 包名.类名.方法名(形参类型))
3、在前置通知@Before和最终通知@After的属性中配置的是切入点的方法
4、在返回通知@AfterReturning和异常通知@AfterThrowing中
- pointcut配置的是切入点的方法
- 返回通知的returning配置的是返回值对象的名称,名称必须与形参一致,形参必须是Object
- 异常通知的throwing配置的是异常方法名称,名称必须与形参一直,形参为Exception
5、每个通知方法中的JoinPoint为接入点,即方法,可以通过属性获取到相关信息
- jp.getSignature().getName():获取到执行对象的方法名
- jp.getTarget():获取到接口的实现类
- jp.getArgs():获取方法传入的形参
6、使用环绕通知,方法的返回值必须是Object,形参必须是ProceedingJoinPoint
在xml配置头中需要引入spring-aop的配置地址
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="io.jtxyh"></context:component-scan>
<!--
开启aop注解识别 ,
属性 proxy-target-class:
默认false:创建代理对象,有接口的话用Proxy,没接口用cglib
true:全部用cglib创建代理对象
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
// 两个注解加其他是切面bean对象
@Component // 实现被包扫描扫描到
@Aspect // 日志切面aspect
public class MyAop {
// 切入点pointcut(joinPoint的集合)
// execution(返回值类型 包名.类名.方法名(形参类型))
@Pointcut("execution(* io.jtxyh.service.*.*(..))")
public void servicePointcut() {};
// 前置通知
@Before("servicePointcut()")
public void write(JoinPoint jp) {
Object target = jp.getTarget();
System.out.println("通知方法:"+target);
}
// 返回通知
@AfterReturning(pointcut = "servicePointcut()",returning = "obj")
public void afterReturn(JoinPoint jp,Object obj) {
String methedName = jp.getSignature().getName();
System.out.println("返回通知:"+methedName+"-------返回信息:"+obj);
}
// 异常通知
@AfterThrowing(pointcut = "servicePointcut()",throwing = "ex")
public void afterThrow(JoinPoint jp,Exception ex) {
String methedName = jp.getSignature().getName();
System.out.println("异常通知:"+methedName+"-------异常信息:"+ex.getMessage());
}
// 最终通知
@After("servicePointcut()")
public void after(JoinPoint jp) {
String methedName = jp.getSignature().getName();
System.out.println("最终通知:"+methedName);
}
}
// 切面bean对象
@Component // 实现被包扫描扫描到
@Aspect // 日志切面aspect
public class MyAop2 {
@Pointcut("execution(* io.jtxyh.service.*.*(..))")
public void myAop() {};
// 环绕通知,方法的返回值必须是Object,形参必须是ProceedingJoinPoint
@Around("myAop()")
public Object around(ProceedingJoinPoint jp) {
Object target = jp.getTarget(); "impl对象"
String methodName = jp.getSignature().getName(); "方法名"
Object[] args = jp.getArgs(); "调用方法传入的形参"
Object result = null;
System.out.println("impl对象:"+target+"----方法名:"+methodName+"----方法的形参:"+Arrays.toString(args));
try {
System.out.println("环绕前置通知。。。。。");
result = jp.proceed();
System.out.println("环绕后置通知");
} catch (Throwable e) {
System.out.println("环绕异常通知。。。。。");
e.printStackTrace();
}finally {
System.out.println("环绕结束通知");
}
return result; "返回调用方法的返回值"
}
}
XML配置
定义一个普通的类,里面写上需要的方法,在xml文件中配置相关标签
public class MyXmlAop {
// 设置前置通知
public void before(JoinPoint jp) {
System.out.println("前置通知========="+jp.getSignature().getName());
}
// 设置返回通知方法
public void afterRu(JoinPoint jp) {
System.out.println("后置通知========="+jp.getSignature().getName());
}
// 设置异常通知方法
public void afterThrowing(JoinPoint jp) {
System.out.println("异常通知=========");
}
// 设置最终通知
public void after(JoinPoint jp) {
System.out.println("最终通知========="+jp.getSignature().getName());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="io.jtxyh"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 将文件加载到bean中 -->
<bean id="myXmlAop" class="io.jtxyh.aop.MyXmlAop"></bean>
<aop:config>
<!-- 通过bean找到文件 -->
<aop:aspect ref="myXmlAop">
<!-- 指定切入点 -->
<aop:pointcut expression="execution(* io.jtxyh.service.*.*(..))" id="myXmlPon"/>
<!-- 指定前置方法 -->
<aop:before method="before" pointcut-ref="myXmlPon"/>
<!-- 指定后置通知 -->
<aop:after-returning method="afterRu" pointcut-ref="myXmlPon"/>
<!-- 指定异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="myXmlPon"/>
<!-- 指定最终通知方法 -->
<aop:after method="after" pointcut-ref="myXmlPon"/>
</aop:aspect>
</aop:config>
</beans>
注解配置和XML配置只需要使用其中一种,多配会报错
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 必须调用的是接口,不能是impl
UserService us = ac.getBean(UserService.class);
UserInfo user = us.getUser();
System.out.println("打印的返回值:"+user.toString());
}