当前位置: 首页 > news >正文

学习springAOP

第三章 Spring AOP

第一节 AOP 简介

1. 概念

AOP全称为Aspect Oriented Programming,表示面向切面编程。何为切面呢?

由此可以得出,切面是一种将那些与业务无关,但业务模块都需要使用的功能封装起来的技术。这样便于减少系统的重复代码,降低模块之间的耦合度。

2. AOP 基本术语

  • 连接点( Joinpoint ):

    连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。连接点由两个信息确定:

    • 方法( 表示程序执行点,即在哪个目标方法)
    • 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
  • 切入点(Pointcut):

    切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则来匹配连接点,给满足规则的连接点添加通知。

  • 通知、增强(Advice):

    可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知、最终通知等。

  • 目标对象(Target):

    目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。

  • 织入(Weaving):

    织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理

  • 代理(Proxy):

    被AOP织入通知后,产生的结果类。

  • 切面(Aspect):

    切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

第二节 AOP 应用

AOP应用场景有许多,最典型的应用场景就是日志和事务。这里以事务实现为例进行讲解。

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.11</version>
</dependency>
<!-- 切面相关的包 -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version>
</dependency>

1. 编写业务层

public interface UserService {int saveUser(Map<String,Object> params);
}public class UserServiceImpl implements UserService {@Overridepublic int saveUser(Map<String, Object> params) {System.out.println("保存用户信息" + params);return 0;}
}

2. 配置业务层

AOP 功能的实现是基于 IOC 的,因此,业务层对象应该纳入 IOC 容器来管理。

<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns = xml namespace-->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 业务层对象--><bean id="userService" class="com.qf.spring.aop.service.impl.UserServiceImpl" />
</beans>

3. 编写通知类

通知分为前置通知、后置通知、异常抛出通知、环绕通知、最终通知五种。首先实现前置通知。

前置通知接口

public interface MethodBeforeAdvice extends BeforeAdvice {/*** Callback before a given method is invoked.*/void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
public class BeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));}
}

4. 配置通知

通知的实现也是基于 IOC 容器的,因此需要将通知对象纳入 IOC 容器管理。

<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns = xml namespace-->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 业务层对象--><bean id="userService" class="com.qf.spring.aop.service.impl.UserServiceImpl" /><!--配置通知对象--><bean id="before" class="com.qf.spring.aop.advice.BeforeAdvice" />
</beans>

5. AOP 配置

当通知对象和业务层对象都纳入 IOC 容器管理之后,需要将通知对象作用在业务层对象上。Spring 提供了 aop 标签来完成这一功能。

<aop:config><!--pointcut表示切点,也就是通知会在哪些位置触发expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上比如 * com.qf.spring.aop.service..*(..)第一个 * 表示任意访问修饰符com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数--><aop:pointcut id="切点ID" expression="切点表达式"/><aop:advisor advice-ref="通知ID" pointcut-ref="切点ID" />
</aop:config>

要使用 aop 标签,必须要在 beans 标签上添加 aop 命名空间

xmlns:aop="http://www.springframework.org/schema/aop"

然后在 xsi:schemaLocation 属性值中添加 aop 命名空间和约束文档

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
<aop:config><aop:pointcut id="pc" expression="execution(* com.qf.spring.aop.service..*(..))"/><aop:advisor advice-ref="before" pointcut-ref="pc" />
</aop:config>

6. 测试

public class AopTest {@Testpublic void saveUserTest(){ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = context.getBean("userService", UserService.class);Map<String,Object> params = new HashMap<>();params.put("name", "刘德华");params.put("sex", "男");userService.saveUser(params);}
}

7. 后置通知

后置通知接口

public interface AfterReturningAdvice extends AfterAdvice {/*** Callback after a given method successfully returned.*/void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
public class AfterAdvice implements AfterReturningAdvice {@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);}
}
<bean id="after" class="com.qf.spring.aop.advice.AfterAdvice" />
<aop:config><!--pointcut表示切点,也就是通知会在哪些位置触发expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上比如 * com.qf.spring.aop.service..*(..)第一个 * 表示任意访问修饰符com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数--><aop:pointcut id="pc" expression="execution(* com.qf.spring.aop.service..*(..))"/><aop:advisor advice-ref="before" pointcut-ref="pc" /><aop:advisor advice-ref="after" pointcut-ref="pc" />
</aop:config>

8. 异常抛出通知

异常抛出通知接口

/*** There are not any methods on this interface, as methods are invoked by* reflection. Implementing classes must implement methods of the form:* void afterThrowing([Method, args, target], ThrowableSubclass);* public void afterThrowing(Exception ex)</pre>* public void afterThrowing(RemoteException)</pre>* public void afterThrowing(Method method, Object[] args, Object target, Exception ex)* public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)*/
public interface ThrowsAdvice extends AfterAdvice {
}
public class ExceptionAdvice implements ThrowsAdvice {public void afterThrowing(Method method, Object[] args, Object target, Exception ex){String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + ex.getMessage());}
}public class UserServiceImpl implements UserService {@Overridepublic int saveUser(Map<String, Object> params) {System.out.println("保存用户信息" + params);throw new RuntimeException("异常抛出演示");}
}
<bean id="exception" class="com.qf.spring.aop.advice.ExceptionAdvice" />
<aop:config><!--pointcut表示切点,也就是通知会在哪些位置触发expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上比如 * com.qf.spring.aop.service..*(..)第一个 * 表示任意访问修饰符com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数--><aop:pointcut id="pc" expression="execution(* com.qf.spring.aop.service..*(..))"/><aop:advisor advice-ref="before" pointcut-ref="pc" /><aop:advisor advice-ref="after" pointcut-ref="pc" /><aop:advisor advice-ref="exception" pointcut-ref="pc" />
</aop:config>

9. 环绕通知

环绕通知接口

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {/*** Implement this method to perform extra treatments before and* after the invocation.*/@NullableObject invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}
public class AroundAdvice implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod(); //获取被拦截的方法对象Object[] args = invocation.getArguments();//获取方法的参数Object target = invocation.getThis(); //获取代理对象String methodName = method.getName();String className = method.getDeclaringClass().getName();try {System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));Object returnValue = method.invoke(target, args);System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);return returnValue;} catch (Throwable t){System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());throw t;}}
}
<!--配置通知对象-->
<!--  <bean id="before" class="com.qf.spring.aop.advice.BeforeAdvice" /><bean id="after" class="com.qf.spring.aop.advice.AfterAdvice" /><bean id="exception" class="com.qf.spring.aop.advice.ExceptionAdvice" />-->
<bean id="around" class="com.qf.spring.aop.advice.AroundAdvice" />
<aop:config><!--pointcut表示切点,也就是通知会在哪些位置触发expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上比如 * com.qf.spring.aop.service..*(..)第一个 * 表示任意访问修饰符com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数--><aop:pointcut id="pc" expression="execution(* com.qf.spring.aop.service..*(..))"/><!--   <aop:advisor advice-ref="before" pointcut-ref="pc" /><aop:advisor advice-ref="after" pointcut-ref="pc" /><aop:advisor advice-ref="exception" pointcut-ref="pc" />--><aop:advisor advice-ref="around" pointcut-ref="pc" />
</aop:config>

第三节 AspectJ

1. AspectJ 简介

AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP 语法,能够在编译期提供代码的织入。Spring通过集成AspectJ实现了以注解的方式定义增强类,大大减少了配置文件中的工作量

2. AspectJ 注解

  • @Aspect 切面标识
  • @Pointcut 切入点
  • @Before 前置通知
  • @AfterReturning 后置通知
  • @Around 环绕通知
  • @AfterThrowing 异常抛出通知

3. AspectJ 应用

3.1 通知类编写
@Aspect
public class AspectJAdvice {@Before(value = "execution(* com.qf.spring.aop.service..*(..))")public void before(JoinPoint jp){Object[] args = jp.getArgs(); //获取方法参数Signature signature = jp.getSignature(); //获取签名if(signature instanceof MethodSignature){ //如果签名是方法签名Method method = ((MethodSignature) signature).getMethod(); //获取方法String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));}}@AfterReturning(value = "execution(* com.qf.spring.aop.service..*(..))", returning = "returnValue")public void after(JoinPoint jp, Object returnValue){Object[] args = jp.getArgs(); //获取方法参数Signature signature = jp.getSignature(); //获取签名if(signature instanceof MethodSignature){ //如果签名是方法签名Method method = ((MethodSignature) signature).getMethod(); //获取方法String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);}}@AfterThrowing(value = "execution(* com.qf.spring.aop.service..*(..))", throwing = "t")public void exception(JoinPoint jp, Throwable t){Object[] args = jp.getArgs(); //获取方法参数Signature signature = jp.getSignature(); //获取签名if(signature instanceof MethodSignature){ //如果签名是方法签名Method method = ((MethodSignature) signature).getMethod(); //获取方法String methodName = method.getName();String className = method.getDeclaringClass().getName();System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());}}@Around("execution(* com.qf.spring.aop.service..*(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs();//获取方法的参数Object target = pjp.getTarget(); //获取代理对象Signature signature = pjp.getSignature(); //获取签名if(signature instanceof MethodSignature) { //如果签名是方法签名Method method = ((MethodSignature) signature).getMethod(); //获取被拦截的方法对象String methodName = method.getName();String className = method.getDeclaringClass().getName();try {System.out.println("准备执行方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args));Object returnValue = method.invoke(target, args);System.out.println("执行完方法:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",得到返回值:" + returnValue);return returnValue;} catch (Throwable t){System.out.println("执行方法时:" + className + "." + methodName + ",参数:" + Arrays.toString(args) + ",发生了异常:" + t.getMessage());throw t;}}return null;}
}
3.2 启用注解支持
<bean  class="com.qf.spring.aop.advice.AspectJAdvice" />
<!--配置通知对象-->
<!--  <bean id="before" class="com.qf.spring.aop.advice.BeforeAdvice" /><bean id="after" class="com.qf.spring.aop.advice.AfterAdvice" /><bean id="exception" class="com.qf.spring.aop.advice.ExceptionAdvice" />-->
<!--    <bean id="around" class="com.qf.spring.aop.advice.AroundAdvice" />-->
<!--<aop:config>&lt;!&ndash;pointcut表示切点,也就是通知会在哪些位置触发expression表示切点表达式,切点表达式必须是execution(), execution()方法中的参数必须配置到方法上比如 * com.qf.spring.aop.service..*(..)第一个 * 表示任意访问修饰符com.qf.spring.aop.service.. 最后的两个..表示service包下面的所有子包中的类*(..) 表示任意方法, 如果()中没有..,则表示不带参数的方法;有,就表示带任意参数&ndash;&gt;<aop:pointcut id="pc" expression="execution(* com.qf.spring.aop.service..*(..))"/>&lt;!&ndash;   <aop:advisor advice-ref="before" pointcut-ref="pc" /><aop:advisor advice-ref="after" pointcut-ref="pc" /><aop:advisor advice-ref="exception" pointcut-ref="pc" />&ndash;&gt;<aop:advisor advice-ref="around" pointcut-ref="pc" /></aop:config>--><!-- 启动AspectJ 注解  自动为类生成代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />

第四节 代理模式

代理模式一共分为两种: 静态代理和动态代理

1. 静态代理

静态代理模式由三个部分构成:

  • 一个公共的接口

  • 一个代理角色

  • 一个被代理角色

其中代理角色和被代理角色均需要实现公共接口。

/*** 人类接口,描述人类购物*/
public interface Person {//购物void shopping();
}
/*** 被代理的角色*/
public class ZhangSan implements Person{@Overridepublic void shopping() {System.out.println("购物");}
}
/*** 代理角色*/
public class PersonProxy implements Person{private ZhangSan zhangSan; //维护一个被代理的角色public PersonProxy(ZhangSan zhangSan) {this.zhangSan = zhangSan;}@Overridepublic void shopping() {System.out.println("代理人准备代理购物");zhangSan.shopping();System.out.println("代理人代理购物完毕");}
}
/*** 静态代理测试*/
public class StaticProxyTest {public static void main(String[] args) {Person p = new PersonProxy(new ZhangSan());p.shopping();}
}

思考:如果有多人想要代理购物,那么像PersonProxy这样的类就需要写多次;从编程的角度出发,这显然不合理。应该如何解决?

使用泛型进行解决。

public class StaticProxy<T> implements Person {private T t;public StaticProxy(T t) {this.t = t;}@Overridepublic void shopping() {//判断当前对象是否继承或者实现了Person接口if(!Person.class.isAssignableFrom(t.getClass()))throw new RuntimeException(t.getClass().getName() + "没有实现Person接口");System.out.println("代理人准备代理购物");((Person)t).shopping();System.out.println("代理人代理购物完毕");}
}/*** 静态代理测试*/
public class StaticProxyTest {public static void main(String[] args) {
//        Person p = new PersonProxy(new ZhangSan());
//        p.shopping();Person p = new StaticProxy<>(new ZhangSan());p.shopping();}
}

思考:如果有多个接口想要代理,应该如何解决?

使用动态代理。

2. 动态代理

动态代理也分为两种:JDK 动态代理 和 CGLIB 动态代理

2.1 JDK 动态代理

JDK 动态代理是基于接口实现的,因此只能为实现了接口的类做代理。其实现原理是:在内存中生成一个代理类继承与 Proxy, 同时实现代理接口,然后在内存中进行编译,编译后使用类加载器将该代理类加载进来,从而创建一个代理对象。

public class DynamicProxyTest {public static void main(String[] args) {Person p = new ZhangSan();Class<?> clazz = Person.class;Class[] interfaces = { clazz };ClassLoader loader = clazz.getClassLoader();InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("准备进行代理购物");return method.invoke(p, args);}};Person proxy = (Person) Proxy.newProxyInstance(loader, interfaces, handler);proxy.shopping();}
}
2.2 CGLIB 动态代理

CGLIB 动态代理是基于继承实现的,因此可以为任何类做代理。如果一个类实现了接口,通常会使用 JDK 动态代理,但也可以强制使用 CGLIB 动态代理。

public class CgLibProxy {public static void main(String[] args) {final Person p = new ZhangSan();//创建字节码增强对象Enhancer enhancer = new Enhancer();//设置父类,需要继承enhancer.setSuperclass(p.getClass());//需要注意:这里的InvocationHandler是CGLIB提供的net.sf.cglib.proxy.InvocationHandlerenhancer.setCallback(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] params) throws Throwable {System.out.println("准备进行代理购物");return method.invoke(p, params);}});//创建动态代理实例Person proxy = (Person) enhancer.create();proxy.shopping();}
}

Spring CGLIB

public class SpringCgLibProxy {public static void main(String[] args) {final Person p = new ZhangSan();//创建字节码曾强对象Enhancer enhancer = new Enhancer();//设置父类,需要继承enhancer.setSuperclass(p.getClass());enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {System.out.println("准备进行代理购物");return method.invoke(p, params);}});//创建动态代理实例Person proxy = (Person) enhancer.create();proxy.shopping();}
}
2.3 区别

JDK 动态代理只能为实现了接口的类做代理, CGLIB 动态代理能够为所有的类做代理。JDK 动态代理的创建代理从效率上来说要比 CGLIB 动态代理快。Cglib不能对声明final的方法进行代理,因为final关键字修饰的方法不可被重写。

相关文章:

  • 重写父类方法、创建单例对象 题目
  • 发布一个Yii2扩展把debug信息存储到MongoDB中
  • el-scrollbar组件使用踩坑记录
  • 求推荐几款http可视化调试工具?
  • HNU_ACM:10415分硬币(动态规划)
  • 解析Kotlin中的委托(包括类委托,属性委托)【笔记摘要】
  • 国家海岸线变化评估:新英格兰和中大西洋沿岸海岸线的历史变化
  • Handling `nil` Values in `NSDictionary` in Objective-C
  • 煤矿安全大模型:微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答
  • 基于C#在WPF中使用斑马打印机进行打印
  • 58.鸿蒙系统app(HarmonyOS)(ArkUI)更改应用程序图标
  • UE5 动画蓝图
  • 计算机是如何看到图像的
  • 泰雷茲具有首个通过FIPS 140-3 三级认证的HSMs
  • 引领AI新时代:深度学习与大模型的关键技术
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Git同步原始仓库到Fork仓库中
  • gulp 教程
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • JavaScript对象详解
  • js
  • JS学习笔记——闭包
  • laravel with 查询列表限制条数
  • LintCode 31. partitionArray 数组划分
  • MaxCompute访问TableStore(OTS) 数据
  • Node项目之评分系统(二)- 数据库设计
  • React Transition Group -- Transition 组件
  • React中的“虫洞”——Context
  • 基于webpack 的 vue 多页架构
  • 基于游标的分页接口实现
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 前端存储 - localStorage
  • 区块链分支循环
  • 三栏布局总结
  • 王永庆:技术创新改变教育未来
  • 网页视频流m3u8/ts视频下载
  • 项目管理碎碎念系列之一:干系人管理
  • 优化 Vue 项目编译文件大小
  • 正则表达式
  • No resource identifier found for attribute,RxJava之zip操作符
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​iOS安全加固方法及实现
  • ​MySQL主从复制一致性检测
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #if 1...#endif
  • #Linux(权限管理)
  • #前后端分离# 头条发布系统
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • $(selector).each()和$.each()的区别
  • (四) Graphivz 颜色选择