AOP:AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。(AOP是一种编程技术) AOP是对OOP的补充延伸。(AOP底层是通过动态代理来实现的)
Spring的AOP的动态代理:JDK动态代理+CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
1. 为什么要使用AOP?
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务 这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。 如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
- 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
- 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
使用AOP可以很轻松的解决以上问题。
1️⃣ 使用AOP的好处:
- 代码复用性增强
- 代码易维护
- 使开发者更关注业务逻辑
2️⃣ 总结 将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
2. AOP相关概念
2.1 术语
-
连接点 Joinpoint:在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
-
切点 Pointcut:在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
-
通知 Advice:通知又叫增强,就是具体你要织入的代码。
- 包括
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 包括
-
切面 Aspect:切点 + 通知就是切面。
-
织入 Weaving:把通知应用到目标对象上的过程。
-
代理对象 Proxy:一个目标对象被织入通知后产生的新对象。
-
目标对象 Target:被织入通知的对象。
2.2 切点表达式
可以使用切点表达式定义通知(Advice)往哪些方法上切入。
execution([访问控制权限修饰符] 返回值类型 [权限定类名] 方法名 (形式参数列表) [列表])
3. 在Spring中使用AOP
Spring对AOP的实现包括以下3种方式:
**第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
第三种方式:Spring框架自己实现的AOP,基于XML配置方式。(不常用了解即可)
3.1 配置
1️⃣使用Spring+Aspectj的AOP需要引入如下依赖:
<!--spring context依赖--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0-M2</version></dependency><!--spring aop依赖--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.0-M2</version></dependency><!--spring aspects依赖--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.0-M2</version></dependency>
2️⃣ Spring配置文件中添加context和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: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">
</beans>
3.2 AOP注解开发
使用Spring中的通知:
- 前置通知:@Before 目标方法执行之前的通知
- 后置通知:@AfterReturning 目标方法执行之后的通知
- 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
- 异常通知:@AfterThrowing 发生异常之后执行的通知
- 最终通知:@After 放在finally语句块中的通知
3.2.1 实现步骤
1️⃣ 定义目标类以及目标方法
package com.powernode.spring6.service;
// 目标类public class OrderService { // 目标方法 public void generate(){ System.out.println("订单已生成!"); }}
2️⃣ 定义切面类
package com.powernode.spring6.service;
import org.aspectj.lang.annotation.Aspect;
// 切面类@Aspectpublic class MyAspect {}
3️⃣ 目标类和切面类都纳入Spring bean管理
- 在目标类OrderService上添加@Component注解。
- 在切面类MyAspect类上添加@Component注解。
4️⃣ 在Spring配置文件分钟添加组件扫描
<?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: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="com.powernode.spring6.service"/></beans>
5️⃣ 在切面类添加通知
package com.powernode.spring6.service;
import org.springframework.stereotype.Component;import org.aspectj.lang.annotation.Aspect;
// 切面类@Aspect@Componentpublic class MyAspect { // 这就是需要增强的代码(通知) public void advice(){ System.out.println("我是一个通知"); }}
6️⃣ 在通知上添加切点表达式
package com.powernode.spring6.service;
import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;import org.aspectj.lang.annotation.Aspect;
// 切面类@Aspect@Componentpublic class MyAspect {
// 切点表达式 @Before("execution(* com.powernode.spring6.service.OrderService.*(..))") // 这就是需要增强的代码(通知) public void advice(){ System.out.println("我是一个通知"); }}
7️⃣ 在Spring配置文件中启用自动代理
- <aop
proxy-target-class=“true”/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。 - proxy-target-class=“true” 表示采用cglib动态代理。
- proxy-target-class=“false” 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
<?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: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="com.powernode.spring6.service"/> <!--开启自动代理--> <aop:aspectj-autoproxy proxy-target-class="true"/></beans>
3.2.2 切面的先后顺序
业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
3.2.3 提高切面的可复用性
1️⃣反面示例
package com.powernode.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
// 切面类@Component@Aspect@Order(2)public class MyAspect {
@Around("execution(* com.powernode.spring6.service.OrderService.*(..))") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); // 执行目标方法。 proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); }
@Before("execution(* com.powernode.spring6.service.OrderService.*(..))") public void beforeAdvice(){ System.out.println("前置通知"); }
@AfterReturning("execution(* com.powernode.spring6.service.OrderService.*(..))") public void afterReturningAdvice(){ System.out.println("后置通知"); }
@AfterThrowing("execution(* com.powernode.spring6.service.OrderService.*(..))") public void afterThrowingAdvice(){ System.out.println("异常通知"); }
@After("execution(* com.powernode.spring6.service.OrderService.*(..))") public void afterAdvice(){ System.out.println("最终通知"); }
}
缺点:切点表达式重复,如果切点表达式需要维护,需要修改多处。
2️⃣ 应该这么做
将切点表达式用@Pointcut注解单独的定义出来,在需要的位置引入即可。
package com.powernode.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
// 切面类@Component@Aspect@Order(2)public class MyAspect {
@Pointcut("execution(* com.powernode.spring6.service.OrderService.*(..))") public void pointcut(){}
@Around("pointcut()") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); // 执行目标方法。 proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); }
@Before("pointcut()") public void beforeAdvice(){ System.out.println("前置通知"); }
@AfterReturning("pointcut()") public void afterReturningAdvice(){ System.out.println("后置通知"); }
@AfterThrowing("pointcut()") public void afterThrowingAdvice(){ System.out.println("异常通知"); }
@After("pointcut()") public void afterAdvice(){ System.out.println("最终通知"); }
}
3.2.4 全注解开发AOP
Spring配置类
package com.powernode.spring6.service;
import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration@ComponentScan("com.powernode.spring6.service")@EnableAspectJAutoProxy(proxyTargetClass = true)public class Spring6Configuration {}
测试程序
@Testpublic void testAOPWithAllAnnotation(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class); OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.generate();}
3.3 基于XML配置方式的AOP(了解即可)
1️⃣编写目标类(略)
2️⃣ 编写切面类,并编写通知(略)
3️⃣编写Spring配置文件
<?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: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">
<!--纳入spring bean管理--> <bean id="vipService" class="com.powernode.spring6.service.VipService"/> <bean id="timerAspect" class="com.powernode.spring6.service.TimerAspect"/>
<!--aop配置--> <aop:config> <!--切点表达式--> <aop:pointcut id="p" expression="execution(* com.powernode.spring6.service.VipService.*(..))"/> <!--切面--> <aop:aspect ref="timerAspect"> <!--切面=通知 + 切点--> <aop:around method="time" pointcut-ref="p"/> </aop:aspect> </aop:config></beans>