Spring AOP :面向切面编程
- 概述:
- AOP 是一种编程示范,隶属于软工范畴,知道开发者如何组织程序结构
- AOP 是 OOP 的延续,是软甲开发中的一个热点,也是 Spring 框架中的一个总要的内容,是函数式编程的一种衍生范式
- 利用 AOP 可以对业务逻辑的各个部分进行分离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
- AOP 面向切面编程
- AOP 采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
- 优势:
- 运行期间不修改源码的情况下对已有的方法进行增强
- 减少重复的代码
- 提供开发的效率
- 维护方便
- 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率
- AOP 底层原理:
- JDK 的动态代理技术:
- 为接口创建代理类的字节码文件
- 使用 ClassLoader 将字节码文件加载到 JVM
- 创建代理类实例对象,执行对象的目标方法
- 拓展:cglib 代理技术
- JDK 的动态代理技术:
- AOP 相关术语:
- Joinpoint(连接点):类当中的哪些方法可以增强,这些方法就是连接点
- Pointcut(切入点):实际被增强的方法就是切入点
- Advice(增强 / 通知):增强功能的代码逻辑
- Target(目标对象):代理目标的对象
- Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程
- Proxy(代理):一个类别 AOP 织入后,就产生一个结果代理类
- Aspect(切面):是一个动作,切入点和通知的结合
- AOP 配置文件方式的入门:
- 创建 maven 项目,导入依赖:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- AOP联盟 --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- Spring Aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!-- aspectj --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version> </dependency> </dependencies>
- 创建 Spring 的配置文件,具体引入 AOP 的 schema 约束:
<?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>
- 编写具体的接口和实现类:
//接口 public interface UserService { public void save(); } //实现类 public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("业务层:保存用户···"); } }
- 将目标类配置到 Spring 中:
<bean id="userService" class="org.example.service.impl.UserServiceImpl"/>
- 定义切面类:
public class MyXmlAspect { public void log(){ //进行业务逻辑的增强··· System.out.println("增强方法已被执行"); } }
- 在配置文件中定义切面类:
<bean id="myXmlAspect" class="org.example.util.MyXmlAspect"/>
- 在配置文件中完成 AOP 的配置:
<!--配置 AOP 增强--> <aop:config> <!--配置切面 = 切入点 + 通知组成 --> <aop:aspect ref="myXmlAspect"> <!--前置通知: UserServiceImpl 的 save 方法执行之前,会增强--> <aop:before method="log" pointcut="execution(public void org.example.service.impl.UserServiceImpl.save())"/> </aop:aspect> </aop:config>
- 编写测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:application.xml") public class AppTest { @Autowired private UserService userService; @Test public void run(){ userService.save(); } }
- 创建 maven 项目,导入依赖:
- 切入点表达式:
- excution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 修饰符:可以省略不写
- 返回值类型:可以省略不写,可以用 * 代替。根据方法编写返回值类型
- 包名:com.qcby.demo.BookDaoImpl
- 首先 com 是不能省略不写的,但是可以使用 * 代替
- 中间的包名也可以使用 * 代替
- 如果想省略中间的包名,可以使用 .. 代替
- 类名:可以使用 * 代替,类似的写法:*DaoImpl
- 方法:可以使用 * 代替
- 参数:如果是一个参数可以用 * 代替,多个参数时用 .. (可以代表任意参数)
<!--配置 AOP 增强--> <aop:config> <!--配置切面 = 切入点 + 通知组成 --> <aop:aspect ref="myXmlAspect"> <!--前置通知: UserServiceImpl 的 save 方法执行之前,会增强--> <!-- <aop:before method="log" pointcut="execution(public void org.example.service.impl.UserServiceImpl.save())"/>--> <!--切入点的表达式: execution():固定的写法 public: 修饰符可省略不写 方法的返回值: int String 通用的写法,可以编写 * ,不能省略不写 包名 + 类名: 不能省略不写,可编写 * 方法名称: save() 可以写 * 参数列表: (..) 表示任意类型和个数的参数 比较通用的表达式: execution(* com.qcby.*.*ServiceImpl.*(..)--> <aop:before method="log" pointcut="execution(* org.example.*.*.*ServiceImpl.save*(..))"/> </aop:aspect> </aop:config>
- excution([修饰符] 返回值类型 包名.类名.方法名(参数))
- AOP 通知类型:
- 前置通知:增强代码在切入点之前执行
- 后置通知:增强代码在切入点之后执行
- 环绕通知:增强代码在切入点之前和之后都会执行
- 异常通知:增强代码在切入点发生异常之后执行
- 最终通知:增强代码在切入点之后执行(类似于 finally)
<!--获取类--> <bean id="user" class="com.spring.service.User"/> <bean id="demo" class="com.spring.service.Demo"/> <!--配置切面--> <aop:config> <!--切面 = 切入点 + 通知--> <aop:aspect ref="demo"> <!--aop:after:最终通知--> <!--pointcut:切入点--> <!--execution():需要执行的切入点 写全名称--> <aop:after method="verify" pointcut="execution(public void com.spring.service.User.LoginJudge())"/> <!--前置通知--> <aop:before method="verify" pointcut="execution(* com.spring.service.User.LoginJudge())"/> <!--后置通知--> <aop:after-returning method="verify" pointcut="execution(* com.spring.service.User.LoginJudge())"/> <!--异常通知--> <aop:after-throwing method="verify" pointcut="execution(* com.spring.service.User.LoginJudge())"/> <!--环绕通知--> <aop:around method="verify" pointcut="execution(* com.spring.service.User.LoginJudge())"/> </aop:aspect> </aop:config>
- 示例:环绕通知
- 导入依赖:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--AOP联盟--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!--Spring Aspects--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.2.RELEASE</version> </dependency> <!--aspectj--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version> </dependency> </dependencies>
- xml 配置切面:
<!--获取类--> <bean id="user" class="com.spring.service.User"/> <bean id="demo" class="com.spring.service.Demo"/> <!--配置切面--> <aop:config> <!--切面 = 切入点 + 通知--> <aop:aspect ref="demo"> <!--aop:after:最终通知--> <!--pointcut:切入点--> <!--execution():切入点表达式--> <aop:after method="verify" pointcut="execution(public void com.spring.service.User.LoginJudge())"/> </aop:aspect> </aop:config>
- 增强类:
public class User { public void LoginJudge(){ System.out.println("登录判断"); } }
- 切入类:
public class Demo { public void verify(){ System.out.println("通知"); } }
- 导入依赖:
- AOP 注解方式
- 案例:
- 编写注解扫描:
<!--扫描包--> <context:component-scan base-package="com.spring"/> <!--扫描注解--> <aop:aspectj-autoproxy/>
- 直接通知:
@Component @Aspect //切入类注解 public class Demo { //环绕通知 @Around(value = "execution(* com.spring.service.User.LoginJudge())") public void verify(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("判断前通知"); proceedingJoinPoint.proceed(); System.out.println("判断后通知"); } //最终通知 @After(value = "execution(* com.spring.service.User.LoginJudge())") //前置通知 @Before(value = "execution(* com.spring.service.User.LoginJudge())") //后置通知 @AfterReturning(value = "execution(* com.spring.service.User.LoginJudge())") //异常通知 @AfterThrowing(value = "execution(* com.spring.service.User.LoginJudge())") public void verify(){ System.out.println("通知"); } }
- 编写注解扫描:
- 通知类型的注解:
- @Before:前置通知
- @AfterReturning:后置通知
- @Around:环绕通知(目标对象方法默认不执行,需要手动执行)
- @After:最终通知
- @AfterThrowing:异常抛出通知
- 纯注解的方式:
@Configuration //配置类 @ComponentScan("org.example") //扫描包 @EnableAspectJAutoProxy //开启自动代理 == <aop:aspectj-autoproxy/> public class SpringConfig { }
- 案例: