本篇文章是根据B站狂神说Java系列的Spring5的笔记记录 —— 第三篇Spring AOP介绍
gitee:https://gitee.com/ywq869819435/spring5
AOP概述
什么是AOP
- 面向切面编程
- 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
- 利用AOP可以对业务逻辑的各个部分进行分离,从而使业务逻辑的各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率
以日志打印为例子的示例图:
AOP在Spring中的作用
提供声明式事务;运行用户自定义切面
实现方式
- aop的本质形式就是动态代理。
- 切面相当于一个动态代理,它实现切点(任意的真实对象)需要实现的功能同时按规定完成自己要做的事,比如打印出执行日志
以日志打印为例
方式一:使用spring的API接口
主要是SpringAPI接口的实现
示例
导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
配置xml
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean -->
<bean id="userService" class="com.yang.service.impl.UserServiceImpl"/>
<bean id="beforeLog" class="com.yang.log.BeforeLog"/>
<bean id="afterLog" class="com.yang.log.AfterLog"/>
<!--方式一: 使用原生的Spring API接口-->
<!--配置AOP: 需要导入aop的约束-->
<aop:config>
<!--切入点
expression="execution([需要执行的位置] [修饰符] [返回值] [列名] [方法名] [参数])"
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的,也就是带?的可以省略
这里的这个意思是可以是任意修饰符的这个包下的这个类的任意方法名,参数也是任意的-->
<aop:pointcut id="pointcut" expression="execution(* com.yang.service.UserService.*(..))"/>
<!--执行环绕增加
advice-ref: 增强的执行的方法,也就是额外执行的方法
pointcut-ref: 需要增加的切点,也就是那些类的方法执行需要增强
-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
设置增强类
public class BeforeLog implements MethodBeforeAdvice {
// method: 要执行的目标对象的方法
// objects: 参数
// target: 目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("准备执行" + target.getClass().getName()
+ "的" + method.getName());
}
}
public class AfterLog implements AfterReturningAdvice {
// returnValue: 执行之后的返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName()
+ "返回的结果为:" + returnValue);
}
}
业务接口以及实现
public interface UserService {
void create();
void update();
void read();
void delete();
}
public class UserServiceImpl implements UserService {
@Override
public void create() {
System.out.println("增加一个用户");
}
@Override
public void update() {
System.out.println("修改一个用户");
}
@Override
public void read() {
System.out.println("查询一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
}
测试类
public class MyTest {
@Test
public void userServiceTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理就是一个代理它可以通知不同的真实的对象的实现类需要实现的功能同时,还能完成自己需要完成事
// 而静态代理只能通知限定的真实对象的功能
// 同理,接口类请求切面完成一些事,切面相当于一个代理,它通知切点的实现类(可以是不同的业务功能的实现类)需要实现的功能同时按规定完成自己要做的事,比如打印出执行日志
UserService userService = context.getBean("userService", UserService.class);
userService.create();
}
}
方式二:自定义类实现AOP
主要是切面的定义
配置xml
将原来的方式一的配置改为方式二,相比较起来方式一使用的官方的API,可调用方法更多更强大,但是方式二的自定义更清晰
<!-- 方式二:自定义类 -->
<bean id="diy" class="com.yang.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref要引用的类的位置-->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.yang.service.UserService.*(..))"/>
<!-- 通知(什么时候来执行) -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
自定义切面类
public class DiyPointCut {
public void before(){
System.out.println("=====执行方法前");
}
public void after(){
System.out.println("执行方法后====");
}
}
方式三:注解实现AOP
配置文件
<!-- 方式三:使用注解实现AOP -->
<bean id="annotationPointCut" class="com.yang.diy.AnnotationPointCut"/>
<!-- 开启注解支持
proxy-target-class="false" 表示基于接口的AOP,使用JDK实现,默认为这个
proxy-target-class="true" 表示基于类,使用cglib实现,默认为这个
-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
注解定义切面
/**
* 使用注解方式定义AOP
* @Aspect 标注这类是一个切面
* @Before("切面位置") 前置增强
*/
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.yang.service.UserService.*(..))")
public void before(){
System.out.println("=====执行方法前");
}
@After("execution(* com.yang.service.UserService.*(..))")
public void after(){
System.out.println("方法执行后=====");
}
/**
* 在环绕增强中,开业给定一个参数,代表要获取处理切入的点(这个方法的相关信息)
*/
@Around("execution(* com.yang.service.UserService.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
// 获取签名
System.out.println("Signature:" + jp.getSignature());
// 执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
总结
- 三种方式选择自己喜欢的就好
- 环绕前Around(可以获取到签名[方法信息]) -> 执行前Before -> 切点执行 -> 环绕后 Around(签名销毁) -> 执行后After
遇到的问题
目标和代理对象的区别
- 目标对象:在SpringAOP被增强的对象
- 代理对象:通过AOP中对目标对象进行增强,加入代理逻辑的而产生的对象
基于接口动态代理
- 使用jdk自带的实现动态代理
- 原理:通过注入的方式将目标对象注入到代理对象中,这样可以保证不改变目标对象的代码达到目标对象被实现,通过反射使得目标对象可以是动态的
基于类动态代理
- 使用CGLIB的动态代理
- 让目标对象作为父类,生成子类作为代理对象,将代理对象需要做的额外方法放入子类,通过invoke方法去执行目标对象,用反射使得目标对象可变
注意切点关注
- 基于接口切点,默认配置就好,jdk的动态代理
- 基于方法切点,基于类使用
cglib
实现,将proxy-target-class属性设为"true",默认为false
示例:
public class Target {
public void save(){
System.out.println("running......");
}
}
public class Advice {
public void before(){
System.out.println("前置加强");
}
public void afterRunning(){
System.out.println("后置加强");
}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class cglibTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//加强对象
final Advice advice = new Advice();
//返回值就是动态代理对象 基于cglib
//创建增强器
Enhancer enhancer = new Enhancer();
//设置父类(目标)
enhancer.setSuperclass(Target.class);
//设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] ages, MethodProxy methodProxy) throws Throwable {
advice.before();//执行前置
Object invoke = method.invoke(target, ages);//执行目标
advice.afterRunning();//执行后置
return invoke;
}
});
//创建代理对象
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
AOP内部调用陷阱
-
由于AOP是动态代理,通过代理对象进行调用的,所以当使用目标对象直接调用的时候AOP会无法拦截,情况示例:
@Service public class Son implements ISon { @Override public void A() { System.out.println("method A"); } @Override public void B() { System.out.println("method B"); A(); } }
-
解决方案
-
需要通过代理对象去调用,那就在方法内使用代理对象调用即可
-
// SpringBoot使用注解或者修改配置文件 @EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = true) // Spring5 xml文件开启AOP注解 <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/> // 使用官方配置类配置注入或者自定义
-
// 内部调用 @Override public void B() { System.out.println("method B"); ((Son) AopContext.currentProxy()).A(); }
-