AOP
一,AOP概念及作用
1,概念作用
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDFvsXFd-1595069781332)(E:\每日讲课笔记\Spring\img\1594862540226.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90WZbO4P-1595069781334)(E:\每日讲课笔记\Spring\img\1594862623416.png)]
-
AOP概念(不修改源代码进行功能增强)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yP0yHW21-1595069781336)(E:\每日讲课笔记\Spring\img\1594886045169.png)]
-
AOP(Aspect Oriented Proframing)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
-
AOP弥补了OOP的不足,基于OOP基础之上进行横向开发
◆ OOP规定程序开发以类为主体模型,一切围绕对象进行,完成某个任务先构建模型
◆ AOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先
构建可能遇到的所有共性功能(当所有功能都开发出来也就没有共性与非共性之分)
-
-
AOP作用
- 伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开
发完毕,最终以功能组合来完成个别业务模块乃至整体业务系统的开发 。(应用场景:加日志和计算时间)
- 目标:将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”搭建
-
AOP优势
-
提高代码的可重用性
业务代码编码更简洁
业务代码维护更高效
业务功能扩展更便捷
-
二,AOP入门案例
1,AOP核心概念
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3Aq2fCQ-1595069781338)(E:\每日讲课笔记\Spring\img\1594863879567.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTfWoC4U-1595069781340)(E:\每日讲课笔记\Spring\img\1594863914166.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BN3b98I-1595069781341)(E:\每日讲课笔记\Spring\img\1594863946594.png)]
-
操作的对象就是方法
-
作用:动态的给方法添加一些功能
//用于下面理解的目标对象,给各个方法增强功能 @Component("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account); } public void update(Account account){ accountDao.update(account); } public void delete(Integer id) { accountDao.delete(id); } public Account findById(Integer id) { return accountDao.findById(id); } public List<Account> findAll() { return accountDao.findAll(); } }
-
⚫ Joinpoint(连接点):就是所有方法 (类中哪些方法可以被增强,这些方法称为连接点)
⚫ Pointcut(切入点):就是指具有共性功能的方法 (实际被真正增强的方法,称为切入点)
⚫ Advice(通知):就是共性功能,最终以一个方法的形式呈现 (**实际增强的逻辑部分(内容)**称为通知(增强))
⚫ Aspect(切面):就是共性功能与挖的位置的对应关系 (把通知应用到切入点的过程,用通知类型来指定)
⚫ Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的
⚫ Weaving(织入):就是将挖掉的功能回填的动态过程
⚫ Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现
⚫ Introduction(引入/引介) :就是对原始对象无中生有的添加成员变量或成员方法
-
AOP开发过程
-
⚫ 开发阶段(开发者完成)
◆ 正常的制作程序
◆ 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
◆ 将共性功能独立开发出来,制作成通知
◆ 在配置文件中,声明切入点
◆ 在配置文件中,声明切入点与通知间的关系(含通知类型),即切面
⚫ 运行阶段(AOP完成)
◆ Spring容器加载配置文件,监控所有配置的切入点方法的执行
◆ 当监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行
-
2,案例制作
-
重点:必须要创建一个接口,因为他实现功能增强会自动生成一个动态代理类,他需要和你的具体类(UserServiceImpl)一样继承同一个接口。才能测试类中类型强转为UserService所以一定要写这个接口
-
步骤:
- 导入坐标
- 编写公共功能的方法(eg:编写一个方法执行打印当前时间)
- 知道哪些方法需要增强(pointcut标签确定哪些方法需要增强)
- 在applicationContext.xml中进行关联配置各种关系
-
代码演示
-
结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSu50Xs8-1595069781342)(E:\每日讲课笔记\Spring\img\1594953780989.png)]
-
坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itheima</groupId> <artifactId>Spring_day03</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> </dependencies> </project>
-
抽取要增强的方法在一个类中
package com.itheima.aop; //制作通知类,在类中定义一个方法用于完成共性功能 public class AOPAdvice { public void function(){ System.out.println("wangling"); } public void currentTime(){ System.out.println(System.currentTimeMillis()); } }
-
写出具体对象类中的方法,然后再配置文件中再指定具体要增强的是这里面的哪些方法
package com.itheima.service; public interface UserService { void save(); void save1(); }
package com.itheima.service.impl; import com.itheima.service.UserService; public class UserServiceImpl implements UserService { //要真正增强的方法,切入点 public void save() { //System.out.println("wangling");要抽取的共性功能,配置在通知类中具体的方法中 System.out.println("林铭"); } public void save1(){ System.out.println("王林"); } }
-
配置关联关系,包括具体通知和切入点的关系,切面
<?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"> <!--保证这俩类是spring控制的资源--> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/> <!--配置共性功能成为spring控制的资源--> <bean id="Advice" class="com.itheima.aop.AOPAdvice"/> <!--开启AOP命名空间,配置AOP--> <aop:config> <!--配置切入点(实际要增强的方法,表达式代表指定哪些方法需要增强),指定一个变量指定当前需要增强的方法--> <aop:pointcut id="pt" expression="execution(* *..*(..))"/> <!--<aop:pointcut id="pt" expression="execution(* *..save*(..))"/>--> <!--配置切面,切入点与通知的关系,通过aspect指定pt中指定的方法到底要做什么增强,在哪个位置增强--> <aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中(通知中具体方法的内容就是实际要增强的内容)--> <!--配置具体的切入点对应通知中的哪个操作方法(把通知中具体哪个方法通过通知类型放到切入点的具体位置,达到增强效果)--> <aop:before method="function" pointcut-ref="pt"/><!--在pt指定的方法之前执行function方法--> <aop:after method="currentTime" pointcut-ref="pt"/> </aop:aspect> </aop:config> </beans>
-
测试加结果(因为做了在方法前后都增强了东西)
import com.itheima.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.save(); System.out.println("————————"); userService.save1(); } }
wangling 林铭 1594953191579 wangling 王林 1594953191579
-
-
三,AOP配置(xml)
1,AOP基本配置
-
AspectJ
⚫ Aspect(切面)用于描述切入点与通知间的关系,是AOP编程中的一个概念
⚫ AspectJ是基于java语言对Aspect的实现
-
⚫ 名称:aop:config
⚫ 类型:标签
⚫ 归属:beans标签
⚫ 作用:设置AOP
⚫ 说明:一个beans标签中可以配置多个aop:config标签
-
⚫ 名称:aop:aspect
⚫ 类型:标签
⚫ 归属:aop:config标签
⚫ 作用:设置具体的AOP通知对应的切入点
⚫ 说明:
一个aop:config标签中可以配置多个aop:aspect标签
⚫ 基本属性:
◆ ref :通知所在的bean的id
-
⚫ 名称:aop:pointcut
⚫ 类型:标签
⚫ 归属:aop:config标签、aop:aspect标签
⚫ 作用:设置切入点
⚫ 说明:
一个aop:config标签中可以配置多个aop:pointcut标签,且该标签可以配置在aop:aspect标签内
⚫ 基本属性:
◆ id :识别切入点的名称
◆ expression :切入点表达式
<bean id="Advice" class="com.itheima.aop.AOPAdvice"/>
<!--开启AOP命名空间,配置AOP-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<!--配置切面,切入点与通知的关系-->
<aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中-->
<!--配置具体的切入点对应通知中的哪个操作方法-->
<aop:before method="function" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
2,切入点表达式
-
切入点
- 切入点描述的是某个方法(让我们快速知道是对哪个类中的哪个方法进行增强)
- 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式。
-
切入点表达式的组成
- 切入点描述的是某个方法
⚫ 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式
关键字(访问修饰符 返回值 包名.类名.方法名(参数)异常名)
◆ 关键字:描述表达式的匹配模式(参看关键字列表)
◆ 访问修饰符:方法的访问控制权限修饰符
◆ 类名:方法所在的类(此处也可以配置接口名称)
◆ 异常:方法定义中指定抛出的异常
-
切入点表达式——通配符
⚫ * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
- 匹配com.itheima包下的任意包下的UserService接口或者类中的find开头的带有一个任意参数的方法。
⚫ … :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
- 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
⚫ +:专用于匹配子类类型
execution(* *..*Service+.*(..))
-
切入点表达式——逻辑运算符
⚫ && :连接两个切入点表达式,表示两个切入点表达式同时成立的匹配
⚫ || :连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配
⚫ ! :连接单个切入点表达式,表示该切入点表达式不成立的匹配
————————————————————
-
切入点表达式——范例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXtMK72a-1595069781344)(E:\每日讲课笔记\Spring\img\1594869020523.png)]
3,三种切入点配置方式
<aop:config>
<!--配置公共切入点-->
<aop:pointcut id="pt1" expression="execution(* *(..))"/>
<aop:aspect ref="myAdvice">
<!--配置局部切入点-->
<aop:pointcut id="pt2" expression="execution(* *(..))"/>
<!--引用公共切入点-->
<aop:before method="logAdvice" pointcut-ref="pt1"/>
<!--引用局部切入点-->
<aop:before method="logAdvice" pointcut-ref="pt2"/>
<!--直接配置切入点-->
<aop:before method="logAdvice" pointcut="execution(* *(..))"/>
</aop:aspect>
</aop:config>
- 全局切入点把切入点配置写到切面外面,局部切入点把切入点写到切面里面,第三种是直接配置切入点格式
4,五种通知类型配置
-
通知类型
-
⚫ AOP的通知类型共5种
◆ 前置通知:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
应用:数据校验
◆ 后置通知:原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
应用:现场清理
◆ 返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
应用:返回值相关数据处理
◆ 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
应用:对原始方法中出现的异常信息进行处理
◆ 环绕通知:在原始方法执行前后均有对应执行执行,还可以阻止原始方法的执行
应用:十分强大,可以做任何事情
-
-
前置通知
-
⚫ 名称:aop:before
⚫ 类型:标签
⚫ 归属:aop:aspect标签
⚫ 作用:设置前置通知
⚫ 格式:
<aop:aspect ref="adviceId"> <aop:before method="methodName" pointcut="……"/> </aop:aspect>
⚫ 说明:一个aop:aspect标签中可以配置多个aop:before标签
⚫ 基本属性:
◆ method :在通知类中设置当前通知类别对应的方法
◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
-
-
后置通知
-
⚫ 名称:aop:after
⚫ 类型:标签
⚫ 归属:aop:aspect标签
⚫ 作用:设置后置通知
⚫ 格式:
<aop:aspect ref="adviceId"> <aop:after method="methodName" pointcut="……"/> </aop:aspect>
⚫ 说明:一个aop:aspect标签中可以配置多个aop:after标签
⚫ 基本属性:
◆ method :在通知类中设置当前通知类别对应的方法
◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
-
-
返回后通知
-
⚫ 名称:aop:after-returning
⚫ 类型:标签
⚫ 归属:aop:aspect标签
⚫ 作用:设置返回后通知
⚫ 格式:
<aop:aspect ref="adviceId"> <aop:after-returning method="methodName" pointcut="……"/> </aop:aspect>
⚫ 说明:一个aop:aspect标签中可以配置多个aop:after-returning标签
⚫ 基本属性:
◆ method :在通知类中设置当前通知类别对应的方法
◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
-
-
抛出异常后通知
-
⚫ 名称:aop:after-throwing
⚫ 类型:标签
⚫ 归属:aop:aspect标签
⚫ 作用:设置抛出异常后通知
⚫ 格式:
<aop:aspect ref="adviceId"> <aop:after-throwing method="methodName" pointcut="……"/> </aop:aspect>
⚫ 说明:一个aop:aspect标签中可以配置多个aop:after-throwing标签
⚫ 基本属性:
◆ method :在通知类中设置当前通知类别对应的方法
◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
-
-
环绕通知(ProceedingJoinPoint)
-
⚫ 名称:aop:around
⚫ 类型:标签
⚫ 归属:aop:aspect标签
⚫ 作用:设置环绕通知
⚫ 格式:
<aop:aspect ref="adviceId"> <aop:around method="methodName" pointcut="……"/> </aop:aspect>
⚫ 说明:一个aop:aspect标签中可以配置多个aop:around标签
⚫ 基本属性:
◆ method :在通知类中设置当前通知类别对应的方法
◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突
◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突
-
-
⚫ 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}
⚫ 环绕通知方法相关说明:
-
方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为
void,通知方法也可以设定返回值类型为void,最终返回null
- 方法需在第一个参数位置设定ProceedingJoinPoint对象,通过该对象调用proceed()方法,实
现对原始方法的调用。如省略该参数,原始方法将无法执行
- 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛
出Throwable对象,封装原始方法中可能出现的异常信息
-
环绕通知代码演示
package com.itheima.aop; import org.aspectj.lang.ProceedingJoinPoint; //制作通知类,在类中定义一个方法用于完成共性功能 public class AOPAdvice { //所以这个环绕通知就包括前三种通知(相当于动态代理) public Object around(ProceedingJoinPoint pjp) { try { System.out.println("前置通知"); Object ret = pjp.proceed();//调用原始方法 System.out.println("返回后通知"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("异常通知"); }finally { System.out.println("后置通知"); } return null; } }
- 配置文件
```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">
<!--保证这俩类是spring控制的资源-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
<!--配置共性功能成为spring控制的资源-->
<bean id="Advice" class="com.itheima.aop.AOPAdvice"/>
<!--开启AOP命名空间,配置AOP-->
<aop:config>
<!--配置切入点(实际要增强的方法,表达式代表指定哪些方法需要增强),指定一个变量指定当前需要增强的方法-->
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<!--<aop:pointcut id="pt" expression="execution(* *..save*(..))"/>-->
<!--配置切面,切入点与通知的关系,通过aspect指定pt中指定的方法到底要做什么增强,在哪个位置增强-->
<aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中(通知中具体方法的内容就是实际要增强的内容)-->
<!--配置具体的切入点对应通知中的哪个操作方法(把通知中具体哪个方法通过通知类型放到切入点的具体位置,达到增强效果)-->
<aop:around method="around" pointcut-ref="pt"/><!--在指定方法环绕执行around方法-->
</aop:aspect>
</aop:config>
</beans>
- 接口和实现类的代码和上面一样,
5,通知顺序
- 当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准
6,通知中获取参数
- 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组
public void before(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
}
- 所有的通知均可以获取参数
7,通知中获取返回值
-
应用场景
执行方法之前记录日志 记录是哪一个类 记录方法的名称 记录方法调用的具体参数 记录返回值 记录异常
-
方法签名(signature):包括,方法名+方法返回值+方法的参数,全限定类名等
-
拿到原始方法的信息(通过获得的签名对象)
-
JoinPoint poj必须放在第一个参数位置
-
通知类
-
public void currentTime(JoinPoint poj){ //获取签名,然后通过签名获取方法名 Signature signature=poj.getSignature(); String name = signature.getName(); System.out.println(name+":start"+System.currentTimeMillis()); }
获取返回值数据的两种(结束后返回,和环绕)
-
⚫ 1,环绕通知类,的方法中调用原始方法获取返回值
⚫ 原始方法
public int save() { System.out.println("user service running..."); return 100; }
⚫ AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt2" expression="execution(* *(..)) "/> <aop:around method="around" pointcut-ref="pt2" /> </aop:aspect>
⚫ 通知类
public Object around(ProceedingJoinPoint pjp) { Object ret=null; try { System.out.println("前置通知"); ret = pjp.proceed(pjp.getArgs());//调用原始方法,这里不确定有没有返回值,所以传了参数 System.out.println("返回后通知"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("异常通知"); }finally { System.out.println("后置通知"); } return ret; }
2,返回后通知,设定返回值变量名
⚫ 原始方法
public int save() { System.out.println("user service running..."); return 100; }
⚫ AOP配置
<aop:aspect ref="myAdvice"> <aop:pointcut id="pt3" expression="execution(* *(..)) "/> <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/> </aop:aspect>
⚫ 通知类
blic void afterReturning(Object ret) { System.out.println(ret); }
⚫ 适用于返回后通知(after-returning)
8,通知中获取异常对象
四,AOP配置(注解)
1,注解配置AOP
- 需要配这几个点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJI2KGtl-1595069781345)(E:\每日讲课笔记\Spring\img\1594972584043.png)]
-
类上方配@Aspect,设置当前类为切面类
-
步骤(参照上图) 1,类上方配@Aspect,设置当前类为切面类 2,方法定义上方@Pointcut,使用当前方法名作为切入点引用名称 3,方法定义上方。标注切入点和切面关系@Before@After@AfterReturning@AfteThrowing@Around 4,主配置类(springConfig)中,开启包扫描注解和AOP注解驱动支持注解(@Configuration @ComponentScan("com.itheima"):@EnableAspectJAutoProxy)
-
@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }//创建一个config包,然后在这里面创造这个SpringConfig主配置类,里面开启包扫描和AOP注解驱动支持
-
注解开发AOP注意事项
-
切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
-
引用切入点时必须使用方法调用名称,方法后面的()不能省略
-
切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用
-
可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性
@AfterReturning(value="pt()",returning = "ret") public void afterReturning(Object ret) { System.out.println("before"); }
-
2,注解AOP通知执行顺序控制
3,AOP注解驱动
五,综合案例
-
案例分析
⚫ 对项目进行业务层接口执行监控,测量业务层接口的执行效率
public interface AccountService { void save(Account account); void delete(Integer id); void update(Account account); List<Account> findAll(); Account findById(Integer id); }
⚫ 测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长
◆ System.currentTimeMillis( )
⚫ 对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码
◆ 环绕通知 (因为要算原始方法执行前后的时间,所以用环绕)
◆ proceed()方法执行前后获取系统时间
-
代码实现
-
项目结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3sHE2XZa-1595069781347)(E:\每日讲课笔记\Spring\img\1594984574576.png)]
-
接口
package com.itheima.service; import com.itheima.domain.Account; import java.util.List; public interface AccountService { void save(Account account); void delete(Integer id); void update(Account account); List<Account> findAll(); Account findById(Integer id); }
-
实现类
package com.itheima.service.impl; import com.itheima.dao.AccountDao; import com.itheima.domain.Account; import com.itheima.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account); } public void update(Account account){ accountDao.update(account); } public void delete(Integer id) { accountDao.delete(id); } public Account findById(Integer id) { return accountDao.findById(id); } public List<Account> findAll() { return accountDao.findAll(); } }
-
环绕通知类
package com.itheima.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; //制作AOP环绕通知类 @Component @Aspect public class RunTimeMonitorAdvice { //切入点,监控业务层接口 @Pointcut("execution(* com.itheima.service.AccountService.find*(..))") public void pt(){} @Around("pt()") public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable { //获取执行签名信息 Signature signature = pjp.getSignature(); //通过签名获取执行类型(接口名) String className = signature.getDeclaringTypeName(); //通过签名获取执行操作名称(方法名) String methodName = signature.getName(); //执行时长累计值 long sum = 0L; //线上写不能这样写一万次,因为数据库一级缓存,findById和findAll测试的时间差距会越来越小,所以不能写一万次 for (int i = 0; i < 10000; i++) { //获取操作前系统时间beginTime long startTime = System.currentTimeMillis(); //原始操作调用 pjp.proceed(pjp.getArgs()); //获取操作后系统时间endTime long endTime = System.currentTimeMillis(); sum += endTime-startTime; } //打印信息 System.out.println(className+":"+methodName+" (万次)run:"+sum+"ms"); return null; } }
-
主配置类
package com.itheima.config; import org.springframework.context.annotation.*; @Configuration @ComponentScan("com.itheima")//开启包扫描 @PropertySource("classpath:jdbc.properties") @Import({JDBCConfig.class,MyBatisConfig.class}) @EnableAspectJAutoProxy//开启AOP注解驱动支持 public class SpringConfig { }
-
测试和结果
package com.itheima.service; import com.itheima.config.SpringConfig; import com.itheima.domain.Account; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; //设定spring专用的类加载器 @RunWith(SpringJUnit4ClassRunner.class) //设定加载的spring上下文对应的配置 @ContextConfiguration(classes = SpringConfig.class) public class UserServiceTest { @Autowired private AccountService accountService; @Test public void testFindById(){ Account ac = accountService.findById(2); // System.out.println(ac); } @Test public void testFindAll(){ List<Account> list = accountService.findAll(); // System.out.println(list); } }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwtWkaHY-1595069781348)(E:\每日讲课笔记\Spring\img\1594976910120.png)]
-
其他是sping_mybatis整合的类,参照上一个文件
六,AOP底层原理
1,装饰模式(静态代理)
2,动态代理
-
1,jdk动态代理实现(Spirng可以通过配置或注解的形式控制使用的代理形式,默认使用jdkproxy,)
-
使用jdk动态代理,使用Proxy类里面的方法创建代理对象,比如创建一个接口,有一个这个接口的实现类,现在对接口进行代理,对其方法做增强,那么他会自动创建一个代理类实现这个接口,然后在这个类做你要做的动态代理,然后强转为接口类型。
-
newProxyInstance方法
- 方法有三个参数分别是:
- 类加载器,
- 增强方法所在的类,这个类实现的接口,支持多个接口
- 实现这个接口InvocationHandler,创建代理对象,写增强的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xv9ksyIg-1595069781350)(E:\每日讲课笔记\Spring\img\1594988450890.png)]
- 方法有三个参数分别是:
-
代码实现
-
准备接口和实现类
package com.itheima.service.impl; import com.itheima.service.UserService; public class UserServiceImpl implements UserService { //要真正增强的方法,切入点 public void save() { //System.out.println("wangling");要抽取的共性功能,配置在通知类中具体的方法中 System.out.println("林铭"); } public void save1(){ System.out.println("王林"); } }
package com.itheima.service; public interface UserService { void save(); void save1(); }
-
准备一个包实现jdk动态代理
package base.proxy; import com.itheima.service.UserService; import org.springframework.transaction.PlatformTransactionManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class UserServiceJDKProxy { public static UserService createUserServiceJDKProxy(final UserService userService){ //获取被代理对象的类加载器 ClassLoader cl = userService.getClass().getClassLoader(); //获取被代理对象实现的接口 Class[] classes = userService.getClass().getInterfaces(); //对原始方法执行进行拦截并增强 InvocationHandler ih = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //原始调用 Object ret = method.invoke(userService, args); //后置增强内容 System.out.println("刮大白2"); System.out.println("贴壁纸2"); return ret; } }; //使用原始被代理对象创建新的代理对象 UserService service = (UserService )Proxy.newProxyInstance(cl,classes,ih); return service; } }
-
测试
package base.proxy; import com.itheima.service.UserService; import com.itheima.service.impl.UserServiceImpl; public class App { public static void main(String[] args) { UserService userService = new UserServiceImpl(); UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService); userService1.save(); } }
-
2,动态代理——CGLIB实现
⚫ CGLIB(Code Generation Library),Code生成类库
⚫ CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
⚫ CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpgkOMsw-1595069781351)(E:\每日讲课笔记\Spring\img\1594984780039.png)]
-
代码实现
method, Object[] args) throws Throwable {
//原始调用
Object ret = method.invoke(userService, args);
//后置增强内容
System.out.println(“刮大白2”);
System.out.println(“贴壁纸2”);
return ret;
}
};
//使用原始被代理对象创建新的代理对象
UserService service = (UserService )Proxy.newProxyInstance(cl,classes,ih);
return service;
}
}
- 测试
```java
package base.proxy;
import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl;
public class App {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService);
userService1.save();
}
}
-
2,动态代理——CGLIB实现
⚫ CGLIB(Code Generation Library),Code生成类库
⚫ CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
⚫ CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
[外链图片转存中…(img-rpgkOMsw-1595069781351)]
-
代码实现