Spring 梳理-AOP
- 界面应用场景
- 日志、声明式事务、安全、缓存
- AOP功能演化图
-
- 图片引用地址:https://www.cnblogs.com/best/p/5679656.html
-
- AOP设计模式-代理模式
- 静态代理:手动编写代理类,手动编写代码拦截每个方法。工作量极大。
- JDK代理:利用反射,实现InvocationHandler接口。 业务类需要实现“业务接口”,利用反射技术代理该“业务接口”
- 动态代理:利用cglib。cglib是一种动态代码生成器,可以在运行期动态生成代理类
- AOP术语
- 关注点:重复代码就叫做关注点;
- 切面: 关注点形成的类,就叫切面(类)!是通知和节点的集合,这个集合决定了切面:是什么,在何时和何处完成其功能。
- 通知(advice):切面类函数。切面要完成的工作(切面类中的方法)。通知定义了切面是什么以及何时使用。Spring的通知有5种类型:before、after、after-returning、after-throwing和around这五种类型。
- 连接点(joinpoint):程序事件。连接点表示在何种操作发生时应用切面。比如方法调用时、修改字段时和抛出异常时等。我们的应用可能有数以千计的时机,这些时机被称为连接点。连接点是应用执行过程中能够插入切面的一个点。
- 切点(拦截的作用)(pointcut): 执行目标对象方法,动态植入切面代码。可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面“是什么”、“何时”,那么切点就定义了“何处”。
- 可能应用有很多事件(连接点)可以用来对外提供“触角”,但通过某种方式选定的连接点与通知发生作用,这个连接点成为切点。
- Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截
- Spring 项目与AspectJ项目 之间有大量的合作;Spring 使用 AspectJ项目的注解语法,实现Spring自己基于代理的AOP。也就是Spring与AspectJ在注解的语法上是相同的,但底层实现是不同的,Spring底层只支持方法拦截,无法在bean创建时应用通知将;,AspectJ却支持构造器、属性、方法拦截。
- Spring AOP的切面类的编写使用纯java语法;
- AspectJ AOP的切面类编写使用java语言扩展,自成一套新的语法。
- Spring在运行时才创建AOP代理对象。
- 联合使用 切面类以一个普通的全局单实例bean的形式注入到容器中,;可以在其他业务类型的bean中使用@Autowired注解 将切面类bean注入到普通业务bean中,获取其运行时状态值。
- Spring使用AsjpectJ定义切点表达式
- 明确具体方法 execution( * cn.jt.ClassA.run(..))
- 限定切点所属的包 execution(* cn.jt.ClassA.run(..)) within(cn.*)
- 限定切点所在bean execution(* cn.jt.ClassA.run(..)) and bean(‘beanID’)
- 使用AOP,为已封装好的Java类,添加新的方法,实现类似Ruby的动态类的概念。但实际上,Spring将一个bean分拆到多个类中:原有类方法在一个类中,新添加的方法在一个类中。
- XML方式实现AOP编程 :在没有源码,或者不想把AspectJ注解污染到代码之中的情况下使用
- 步骤
-
1) 引入jar文件 【aop 相关jar, 4个】
2) 引入aop名称空间
3)aop 配置
* 配置切面类 (重复执行代码形成的类)
* aop配置
拦截哪些方法 / 拦截到方法后应用通知代
-
- 示例
-
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- dao 实例 在这里配置后就不用在类中使用注解 --> <bean id="aDao" class="xx"></bean> <bean id="bDao" class="yy"></bean> <!-- 切面类 --> <bean id="aop" class="aopClass1"></bean> <!-- Aop配置 --> <aop:config> <!-- 定义一个切入点表达式: 拦截哪些方法 --> <aop:pointcut expression="execution(* aopClass1.*.*(..))" id="pt"/> <!-- 切面 --> <aop:aspect ref="aop"> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="pt"/> <!-- 前置通知: 在目标方法调用前执行 --> <aop:before method="begin" pointcut-ref="pt"/> <!-- 后置通知: --> <aop:after method="after" pointcut-ref="pt"/> <!-- 返回后通知 --> <aop:after-returning method="afterReturning" pointcut-ref="pt"/> <!-- 异常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/> </aop:aspect> </aop:config> </beans>
-
- 步骤
- 注解方式实现AOP编程
- 先引入aop相关jar文件
spring-aop-3.2.5.RELEASE.jar
aopalliance.jar
aspectjweaver.jar
aspectjrt.jar
-
bean.xml中引入aop名称空间
-
开启aop注解
- 使用注解
-
@Aspect 指定一个类为切面类
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知: 目标方法之前执行
@After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
@AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
@Around("pointCut_()") 环绕通知: 环绕目标方法执行
-
- 关于@Pointcut注解
- 是一个节点表达式,通过在一个空函数run()上使用@Pointcut注解,我们实际上扩展了切点表达式语言,这样就可以在任何切点表达式中使用run(),否则,需要在每个切点表达式使用那个更长的表达式。例如:
-
@AspectJ public class Audience{ @Pointcut("execution(** cn.jt.run(..))") public void run(){} @Before("run()") public void beforeRun(ProceedingJoinPoint jp){ System.out.println("berfor"); jp.proceed(); System.out.println("after"); } @Around("run()") public void watchRun(ProceedingJoinPoint jp){ System.out.println("berfor"); jp.proceed(); System.out.println("after"); } }
相当于: -
@AspectJ public class Audience{ @Before("execution(** cn.jt.run(..))") public void beforeRun(){ System.out.println("berfor"); } @Around("execution(** cn.jt.run(..))") public void watchRun(ProceedingJoinPoint jp){ System.out.println("berfor"); jp.proceed(); System.out.println("after"); } }
- 关于环绕通知:
- 环绕通知需要额外的ProceedingJoinPoint类型参数
- 如果不调用jp.proceed()方法,那么通知会阻塞对被通知方法的调用;或者调用多次,类似与“失败后重试”这样的业务逻辑。
- 将业务类方法中的参数,传递到通知(切面类方法)中。(使用 args 表达式)
-
@Component @Aspect public class Audience { private Map<Integer, Integer> trackCounts = new HashMap<>(); @Pointcut("execution(** concert.JayPerform.playTrack(int)) && args(trackNum)") //<1> public void track(int trackNum) {} //<2> @AfterReturning("track(trackNum)") //<3> public void countTrack(int trackNum) //<4> { int currentCount = getPlayCount(trackNum); trackCounts.put(trackNum, currentCount+1); System.out.println("------- 这首歌播放了"+(currentCount+1)+"次"); } public int getPlayCount(int trackNumber) { return trackCounts.containsKey(trackNumber)?trackCounts.get(trackNumber):0; } }
- 代码中 <1>、<2>、<3>、<4> 处,int型参数的名称都是trackNum,这样保证了从命名切点到通知方法的参数转移。并且,这里的参数trackNum与concert.JayPerform.playTrack(int trackNum) 的参数命名相同。
经过实验,发现这4处的参数名称与concert.JayPerform.playTrack(int trackNum)中的参数名称不必相同,只要<1>与<2>处参数名称相同、<3>与<4>处参数名称相同即可
-
- 示例
- bean.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 使用注解时要开启注解扫描 要扫描的包 --> <context:component-scan base-package="cn.jt"></context:component-scan> <!-- 开启aop注解方式 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
- Java代码
@Component //加入IOC容器 @Aspect // 指定当前类为切面类 public class Aop { // 指定切入点表达式: 拦截哪些方法; 即为哪些类生成代理对象 //解释@Pointcut("execution(* cn.jt.*.*(..))") //@Pointcut("execution(* 切入点表达式固定写法, cn.itcast.e_aop_anno表示包.类名(可以用*表示包下所有的类).方法名(可以用*表示类下所有的方法)(..)表示参数可以用.. @Pointcut("execution(* cn.jt.*.*(..))") public void pointCut_(){ } //@Before("execution(* cn.jt.*.*(..))")每个方法需要写相同的引用,所以将相同的部分抽取到一个空的方法中pointCut_(), // 前置通知 : 在执行目标方法之前执行 @Before("pointCut_()") public void begin(){ System.out.println("开始事务/异常"); } // 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】 @After("pointCut_()") public void after(){ System.out.println("提交事务/关闭"); } // 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】 @AfterReturning("pointCut_()") public void afterReturning() { System.out.println("afterReturning()"); } // 异常通知: 当目标方法执行异常时候执行此关注点代码 @AfterThrowing("pointCut_()") public void afterThrowing(){ System.out.println("afterThrowing()"); } // 环绕通知:环绕目标方式执行 @Around("pointCut_()") public void around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕前...."); pjp.proceed(); // 执行目标方法 System.out.println("环绕后...."); } }
测试类
public class App { ApplicationContext ac = new ClassPathXmlApplicationContext("cn/jt/bean.xml"); // 目标对象有实现接口,spring会自动选择“JDK代理” @Test public void testApp() { IUserDao userDao = (IUserDao) ac.getBean("userDao"); System.out.println(userDao.getClass()); userDao.save(); } // 目标对象没有实现接口, spring会用“cglib代理” @Test public void testCglib() { OrderDao orderDao = (OrderDao) ac.getBean("orderDao"); System.out.println(orderDao.getClass()); orderDao.save(); }
- bean.xml
- 先引入aop相关jar文件