术语
术语不是为了限制你的设计,而是借此可以更方便的讨论和思考这个问题。这是我早上在《游戏设计要则探秘》上看的一句话,当然术语也不能华而不实,最好做到顾名思义或指出本质。
在使用 AOP 之前,我们需要了解AOP涉及的相关概念。
-
JoinPint:AOP的功能模块要植入到OOP的模块中,需要知道在哪些执行点上进行植入,这些执行点就是JoinPoint。JoinPoint可以是方法的调用,字段设置,异常处理执行等等。
-
PointCut:PointCut是对JoinPoint的表现形式,可以直接用方法名,正则表达式还有特定的PointCut表述语言。
-
Advice:Advice就是横切点功能的载体。包括,Before Advice,AAfter Advice,Around Advice。这三个切入的时间不同,可顾名思义。
-
Aspect:Aspect可以理解为切面,它可以包含多个PointCut和Advice。
Spring AOP 中的代理模式
Spring 文档里这样一句话:“It is important to grasp the fact that Spring AOP is proxy-based. ”Spring AOP 是基于代理的 AOP。配置AOP可以分为@AspecJ和XML格式。
Spring AOP 的实现机制同时使用JDK的动态代理和**CGLIB
的动态字节码生成**,当需要切入的类有实现某个接口时,Spring 会选择使用动态代理,为其动态生成代理类。当没有实现任何接口时,使用 CGLIB
动态生成其子类,并进行扩展。
代理模式
复习一下代理模式,代理模式是常见的一种设计模式。最近在Android开发中也有遇到。我们来看UML图:
从图中,我们看到不直接使用SubjectImpl而是通过SubjectProxy,这样SubjectProxy可以在其方法内对SubjectImpl进行扩展。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class SubjectProxy implements ISubject {
private ISubject subject;
@Override
public String request(String header) {
if (!header.startsWith("https")) {
return null;
}
return "Proxy: " + subject.request(header);
}
public void setSubject(ISubject subject) {
this.subject = subject;
}
}
|
1
2
3
4
5
6
7
8
9
|
public class Client {
public static void main(String[] args) {
SubjectProxy subjectProxy = new SubjectProxy();
subjectProxy.setSubject(new SubjectImpl());
System.out.println(subjectProxy.request("https://leer.moe"));
System.out.println(subjectProxy.request("http://leer.moe"));
}
}
|
这里我们的JoinPoint是request()方法,如果其他的接口也有这个方法,它也是我们的JoinPoint,就需要为每个接口编写代理类。为了解决这个问题,Java在JDK1.3之后提供了动态代理。
动态代理
动态代理的实现主要由一个类和一个接口实现:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。
上面的代理模式可用动态代理实现,编写我们自己的InvocationHandler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class RequestInvocationHandler implements InvocationHandler {
private Object target;
public RequestInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable {
if (method.getName().equals("request")) {
if (!(args[0] instanceof String) || !((String)args[0]).startsWith("https"))
return null;
return "Proxy: " + method.invoke(target, args);
}
return null;
}
}
|
对不同目标对象,但是都拥有request方法JoinPoint的对象(比如下面代码中的SubjectImpl
和 RequsetableImpl
),我们不再需要为他们分别创建代理类了,动态代理机制会为它们动态的创建代理实例,当调用request方法时,InvocationHandler就会对相应的方法进行拦截和处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
static void dynamic() {
ISubject subject = (ISubject)Proxy.newProxyInstance(
Client.class.getClassLoader(),
new Class[]{ISubject.class},
new RequestInvocationHandler(new SubjectImpl())
);
System.out.println(subject.request("https://leer.moe"));
System.out.println(subject.request("http://leezoom.xyz"));
Requestable requestable = (Requestable)Proxy.newProxyInstance(
Client.class.getClassLoader(),
new Class[]{Requestable.class},
new RequestInvocationHandler(new RequsetableImpl())
);
System.out.println(requestable.request("https://leer.moe"));
System.out.println(requestable.request("http://leezoom.xyz"));
}
|
不过,动态代理也有无能无力的时候,如果一个类没有实现任何接口的话,就无法生成代理类了,Spring AOP 在无法使用动态代理的时候,会使用CGLIB为目标对象生成子类。
CGLIB
CGLIB 的使用请自己搜索
为了演示CGLIB的使用,我们定义的目标对象如下:
1
2
3
4
5
|
public class Request {
public void request(String headers) {
System.out.println("request " + headers);
}
}
|
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
|
public class RequestCallback implements MethodInterceptor {
public boolean before(Object arg) {
System.out.println("before: check https....");
return arg instanceof String && ((String) arg).startsWith("https");
}
public void after() {
System.out.println("after");
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("request")) {
if (before(args[0])) {
Object result = methodProxy.invokeSuper(o, args);
after();
return result;
} else {
System.out.println("Check failed");
return null;
}
}
return null;
}
}
|
可以看到,我增加了before和after方法,这就类似AOP中Advice的雏形了,
分别在 methodProxy.invokeSuper(o, args) 的前后调用。
接下来在客户端中使用CGLIB:
1
2
3
4
5
|
static void cglib() {
Request requestInstance = (Request) Enhancer.create(Request.class, new RequestCallback());
requestInstance.request("https://leer.moe");
requestInstance.request("http://leezoom.xyz");
}
|
CGLIB为 Request生成子类,并对request方法按我们的需求进行扩展。GCLIB的唯一限制是无法对final方法进行扩展。输入结果如下:
小试 Spring AOP
在对Spring AOP的实现机制有所了解之后,我们开始实战使用一下Spring AOP吧。
简单起见,我使用@AspectJ风格的AOP配置,它是使用 Annotation 来配置AOP的。
Spring AOP可以使用@AspectJ风格是配置也可以用XML,但是底层还是基于动态代理和CGLIB的实现,并不是使用AspectJ的专门编译器来实现的。当然Spring 也支持完全使用AspectJ,但是这已经不是Spring AOP了,而是AspectJ的内容。
定义一个Aspect:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Aspect
@Configuration
public class UserAccessAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Before("execution(* moe.leer.springdemo.aop.service.*.*(..))")
public void before(JoinPoint joinPoint) {
//Advice
logger.info("Check for user access");
logger.info("Allowed execution for {}", joinPoint);
}
@After("execution(* moe.leer.springdemo.aop.service.*.*(..))")
public void after(JoinPoint joinPoint) {
//Advice
logger.info("Execution finished {}", joinPoint);
}
}
|
PointCut的定义使用正则表达式,execution(* moe.leer.springdemo.aop.service..(..)) 表示匹配service包下的所有方法。@Before 和 @After 注解的两个方法分别对应Before Advice 和After Advice,来模拟对用户的检查和在执行完成后打印。
我们再来看Service包,里面就是简单测试业务:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Service
public class Business1 {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private Dao1 dao1;
public String calculateSomething() {
String val = dao1.returnSomething();
logger.info("In Business1 -{}", val);
return val;
}
}
|
接着编写测试代码,对其进行测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@RunWith(SpringRunner.class)
@SpringBootTest
public class BusinessAopTests {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private Business1 business1;
@Autowired
private Business2 business2;
@Test
public void invokeAOP() {
logger.info(business1.calculateSomething());
logger.info(business2.calculateSomething());
}
}
|
在 calculateSomething方法前后,植入了两个Advice。细心一点你可以发现,打印的Log中有“BySpringCGLIB”,很好的验证了我们之前的说法:Spring AOP 在无法使用动态代理的时候,会使用CGLIB为目标对象生成子类。