1 基本概念
什么是AOP?
- 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
- 不通过修改源代码的方式,在主干功能中添加新功能。
- 登录例子。
基本术语
- 连接点:在被增强类中,那些方法可以被增强,就称为连接点。
- 切入点:实际增强的方法,称为切入点。
- 通知(增强):实际增强的逻辑部分称为通知。通知有五种类型:前置通知、后置通知、环绕通知、异常通知、最终通知。
- 前置通知:增强方法之前执行的通知。
- 后置通知:增强方法之后执行的通知。
- 环绕通知:增强方法之前和之后执行的通知。
- 异常通知:增强方法出现异常执行的通知。
- 最终通知:类似
中的try-catch-finally
,无论是否出异常,最终都会执行的通知。finally
- 切面:是一个动作,把通知应用到切入点的过程。
2 底层原理
AOP底层使用动态代理方式增强源代码。
有接口情况,使用JDK动态代理。创建接口实现类的代理对象。
没有接口情况,使用CGLIB动态代理。创建当前子类的代理对象。
3 JDK动态代理
类。
Proxy
包中,用于创建代理对象。
java.lang
loader
:当前类加载器。
interfaces
:增强类所在的接口,可以写多个接口。
是个接口,传入实现对象,其中写增强方法。
h:InvocationHandler
代码实现
(1) 创建接口
public interface UserDao {
int add(int a, int b);
String update(String id);
}
(2) 创建接口实现类
public class UserDaoImpl implements UserDao {
public int add(int a, int b) {
return a + b;
}
public String update(String id) {
return id;
}
}
(3) 使用
Proxy
类创建接口代理对象
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
// 三个参数:第一个当前类的加载器,第二个被增强类实现的接口,InvocationHandler的实现类对象(用于增强方法)
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println(result);
}
static class UserDaoProxy implements InvocationHandler{
// 把被增强对象传递过来,即UserDaoImpl
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
// 增强的逻辑
// 三个参数:第一个表示被增强类,第二个当前增强的方法,第三个被增强方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 可以在方法执行之前,通过method判断是add还是update方法,从而对不同的方法进行不同的增强操作。
// 方法之前
System.out.println("方法之前执行" + method.getName() + Arrays.toString(args));
// 执行被增强方法,两个参数,所属对象,方法所需的参数。res是原方法的返回值。
Object res = method.invoke(obj,args);
// 方法之后
System.out.println("方法之后执行" + obj);
return res;
}
}
}
(4) 输出结果。add执行了这句需要加载
UserDaoImpl
的add方法中,否则不会输出。
4 Spring中的AOP操作
4.1 准备工作
Spring框架一般基于 AspectJ
实现AOP操作。
AspectJ
不是Spring的组成部分,独立AOP框架,一般把
AspectJ
和
Spring
框架一起使用,进行AOP操作。
基于 AspectJ
实现AOP操作
(1) 基于xml配置文件实现
(2) 基于注解方式实现(较常使用)
在项目中引入AOP相关依赖
<dependencies>
<!-- Beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<!-- Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<!-- Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</dependency>
<!-- aop相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance -->
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/com.springsource.org.aspectj.weaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
</dependency>
<!-- 日志相关 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
</dependencies>
切入点表达式:知道对哪个类中的哪个方法进行增强。
语法结构:
excution([权限修饰符(可以写*代表所有权限)][返回类型(可省略)][类全路径][方法名称][参数列表(可用..代表两个参数)])
增强
com.wit.dao.BookDao
中的
add
方法:
excution(* com.wit.dao.BookDao.add(..))
增强
com.wit.dao.BookDao
中的所有方法:
excution(* com.wit.dao.BookDao.*(..))
4.2 基于注解方式实现
(1) 创建类,在类里面定义方法
public class User {
public int add(int a,int b){
System.out.println("add....");
}
}
(2) 创建增强类(编写增强逻辑),在增强类里面,创建方法,让不同方法代表不同的通知类型。
// 增强类
public class UserProxy {
// 前置通知
public void before(){
System.out.println("Before...");
return a + b;
}
}
(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">
<context:component-scan base-package="com.wit.aop"/>
</beans>
使用IOC创建和
User
UserProxy
在增强类上面添加注解 @Aspect
在spring配合文件中开启生成代理对象
<!-- 开启aspectj生成代理对象 -->
<aop:aspectj-autoproxy/>
(4) 配置不同类型的通知
在增强类里面,作为通知方法上面添加通知类型注解,使用切入点表达式配置。
// 增强类
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("Before...");
}
}
(5) 测试
@Test
public void testAop(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
user.add(1,2);
}
不同通知类型测试:
// 增强类
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("before...");
}
// 后置通知
// 有异常则不会通知
@AfterReturning(value = "execution(* com.wit.aop.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
// 最终通知
// 任何情况下都会执行
@After(value = "execution(* com.wit.aop.User.add(..))")
public void after(){
System.out.println("after...");
}
// 异常通知
// 有异常才会通知
@AfterThrowing(value = "execution(* com.wit.aop.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
// 环绕通知
@Around(value = "execution(* com.wit.aop.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕之前...");
// 被增强的方法
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
}
测试结果与spring版本有。5.2.8是分界线,5.2.8之前
5.2.8及5.2.8之后。相比之下5.2.8之后更加符合认知。有两点:第一,环绕通知包含其他通知,更加符合环绕的概念。第二,
after
在
afterReturning
之后输出,
after
是最终通知,
afterReturning
是后置通知,最终通知更想finally块,异常发生之后仍然执行。
公共切入点提取
上面测试中,多个测试方法切入表达式是相同的,可以对切入点表达式进行提取。
// 切入点提取
@Pointcut(value = "execution(* com.wit.aop.User.add(..))")
public void pointDemo(){
}
// 如何调用
// 前置通知
@Before(value = "pointDemo()")
public void before(){
System.out.println("before...");
}
增强类优先级
一个类有多个增强类,可以对增强类的优先级进行设置。在增强类上添加 @Order
,数字类型越小优先级越高。
创建
USerProxy2
,设置
@Order(1)
,设置
UserProxy
中为
@Order(3)
。
@Component
@Aspect
@Order(1)
public class UserProxy2 {
@Before(value = "execution(* com.wit.aop.User.add(..))")
public void before(){
System.out.println("Order 1 before...");
}
}
测试结果
全注解开发
把配置在xml文件中的
<aop:aspectj-autoproxy/>
配置在配置类上,加注解
@EnableAspectJAutoProxy(proxyTargetClass = true)
4.3 基于xml方式实现
使用较少,一般使用注解方式。xml方法不需要配置 <aop:aspectj-autoproxy/>
(1) 创建两个类,增强类和被增强类,创建方法
public class Student {
public void work(){
System.out.println("work.......");
}
}
public class StudentProxy {
public void before(){
System.out.println("before......");
}
}
(2) 在spring配置文件中创建两个类对象
<bean id="student" class="com.wit.aop.Student"/>
<bean id="studentProxy" class="com.wit.aop.StudentProxy"/>
(3) 在spring配置文件中配置切入点
<!-- 配置aop增强 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="p" expression="execution(* com.wit.aop.Student.work(..))"/>
<!-- 配置切面,应用到方法的过程 -->
<aop:aspect ref="studentProxy">
<!-- 增强作用在具体的方法上 -->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
(4)测试