Spring-AOP的理解

AOP(Aspect Oriented Programming),即面向切面编程,它和 OOP(Object Oriented Programming)一样,属于一种编程思想。

为什么需要AOP

想象我们的项目中有很多的Controller,现在需要打印每个Controller中每个方法的执行时间,如果执行时间超过2s的要打印出调用方法的参数,难道我们在每个Controller中的方法里都去加一行日志吗?显然这样的做法是繁琐冗余的,这就促使我们设想是否有某种方式,在执行Controller的方法时会去调用另外一段代码?确实有,解决方法就是本文所讲的AOP。AOP就是指,在程序运行期间,将某段代码动态的切入到指定方法的指定位置。
除了上面说的日志记录,AOP也可以用于权限验证、事务管理等场景。

AOP怎么实现

AOP的实现方式有多种,其中Spring的AOP是使用动态代理的方式来实现的。

动态代理

Java对代理模式提供了内在的支持,在java.lang.reflect包下面,提供了一个Proxy的类和一个InvocaationHandler的接口,是动态代理中用到的关键类。
Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。具体的内部实现细节这里不去讨论。
如果要实现类(非接口)的代码,可以使用cglib。cglib是一个代码生成的类库(Code Generation Library),可以在运行时动态的生成某个类的子类。cglib是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用cglib做动态代理的。

下面简单看一下Java动态代理的代码示例。
先定义一个pojo、接口和实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Getter
@Setter
@ToString
public class User {

private Long id;

private String name;

private Integer age;


}

public interface UserService {

void add(User user);

}


@Slf4j
public class UserServiceImpl implements UserService {

@Override
public void add(User user) {
log.info("新增用户|succ|id={}", user.getId());
}

}

定义一个动态代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
public class DymicProxyImpl implements InvocationHandler {

private Object targetObject;

public Object getProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logMethodParams(args); //在执行目标方法时,先执行一段指定的代码
return method.invoke(targetObject, args);
}

//记录方法参数
private void logMethodParams(Object[] args) {
log.info("方法入参|{}", args);
}
}

单元测试方法:

1
2
3
4
5
6
7
8
9
@Test
public void test() {
User user = new User();
user.setId(2L);
user.setName("Jack");
user.setAge(8);
UserService proxyUserService = (UserService) new DymicProxyImpl().getProxyInstance(new UserServiceImpl());
proxyUserService.add(user);
}

输出:

1
2
20:11:28.352 [main] INFO com.lzumetal.java.learn.proxy.DymicProxyImpl - 方法入参|User(id=2, name=Jack, age=8)
20:11:28.358 [main] INFO com.lzumetal.java.learn.proxy.service.UserServiceImpl - 新增用户|succ|id=2

至此,我们应该了解了动态代理的机制了,AOP的功能也可以在此基础上实现了。

AOP中的几个术语

连接点(Joinpoint):表示应用程序执行过程中可以插入切面的某个特定位置。例如:某个方法调用前、调用后、方法捕获到异常后等。

1
2
3
@Before("pointcut()")
public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点
}

切入点(PointCut):每个程序的连接点(Joinpoint)有很多个,切点用来指定某一个或者某一些连接点,通常我们采用表达式的方式(切入点表达式)来设置。
通知(Advice):是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。
切面(Aspect):切面由切入点和通知组成,既包括了切入点的定义,也包含了切入之后要进行的操作。
织入(Weaving):织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

AOP的意义

AOP在没有改变业务处理的流程情况下,把那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

------ 本文完 ------