Java AOP
Posted kexinxin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java AOP相关的知识,希望对你有一定的参考价值。
AOP
今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之。翻译过来就是"面向方面编程",可我更倾向于翻译为"面向切面编程"。它听起有些的神秘,为什么呢?当你看完这篇文章的时候,就会知道,我们做的很重要的工作就是去写这个"切面" 。那么什么是"切面"呢?
没错!就是用一把刀来切一坨面。注意,相对于面而言,我们一定是横着来切它,这简称为"横切"。可以把一段代码想象成一坨面,同样也可以用一把刀来横切它,下面要做的就是如何去实现这把刀!
需要澄清的是,这个概念不是由 Rod Johnson(老罗)提出的。其实很早以前就有了,目前最知名最强大的 Java 开源项目就是 AspectJ 了,然而它的前身是 AspectWerkz(该项目已经在 2005 年停止更新),这才是 AOP 的老祖宗。老罗(一个头发秃得和我老爸有一拼的天才)写了一个叫做 Spring 框架,从此一炮走红,成为了 Spring 之父。他在自己的 IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。
那么 AOP 到底是什么?如何去使用它?本文将逐步带您进入 AOP 的世界,让您感受到前所未有的畅快!
不过在开始讲解 AOP 之前,我想有必要回忆一下这段代码:
1. 写死代码
先来一个接口:
public interface Greeting {
void sayHello(String name);
}
还有一个实现类:
public class GreetingImpl implements Greeting {
@Override
public void sayHello(String name) {
before();
System.out.println("Hello! " + name);
after();
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
before() 与 after() 方法写死在 sayHello() 方法体中了,这样的代码的味道非常不好。如果哪位仁兄大量写了这样的代码,肯定要被你的架构师骂个够呛。
比如:我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢?
再比如:我们要写一个 JDBC 程序,那是不是也要在方法的开头去连接数据库,方法的末尾去关闭数据库连接呢?
这样的代码只会把程序员累死,把架构师气死!
一定要想办法对上面的代码进行重构,首先给出三个解决方案:
2. 静态代理
最简单的解决方案就是使用静态代理模式了,我们单独为 GreetingImpl 这个类写一个代理类:
public class GreetingProxy implements Greeting {
private GreetingImpl greetingImpl;
public GreetingProxy(GreetingImpl greetingImpl) {
this.greetingImpl = greetingImpl;
}
@Override
public void sayHello(String name) {
before();
greetingImpl.sayHello(name);
after();
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
就用这个 GreetingProxy 去代理 GreetingImpl,下面看看客户端如何来调用:
public class Client {
public static void main(String[] args) {
Greeting greetingProxy = new GreetingProxy(new GreetingImpl());
greetingProxy.sayHello("Jack");
}
}
这样写没错,但是有个问题,XxxProxy 这样的类会越来越多,如何才能将这些代理类尽可能减少呢?最好只有一个代理类。
这时我们就需要使用 JDK 提供的动态代理了。
3. JDK 动态代理
public class JDKDynamicProxy implements InvocationHandler {
private Object target;
public JDKDynamicProxy(Object target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
客户端是这样调用的:
public class Client {
public static void main(String[] args) {
Greeting greeting = new JDKDynamicProxy(new GreetingImpl()).getProxy();
greeting.sayHello("Jack");
}
}
这样所有的代理类都合并到动态代理类中了,但这样做仍然存在一个问题:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。有什么方法可以解决呢?
4. CGLib 动态代理
我们使用开源的 CGLib 类库可以代理没有接口的类,这样就弥补了 JDK 的不足。CGLib 动态代理类是这样玩的:
public class CGLibDynamicProxy implements MethodInterceptor {
private static CGLibDynamicProxy instance = new CGLibDynamicProxy();
private CGLibDynamicProxy() {
}
public static CGLibDynamicProxy getInstance() {
return instance;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(target, args);
after();
return result;
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
以上代码中了 Singleton 模式,那么客户端调用也更加轻松了:
public class Client {
public static void main(String[] args) {
Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);
greeting.sayHello("Jack");
}
}
到此为止,我们能做的都做了,问题似乎全部都解决了。但事情总不会那么完美,而我们一定要追求完美!
老罗搞出了一个 AOP 框架,能否做到完美而优雅呢?请大家继续往下看吧!
5. Spring AOP:前置增强、后置增强、环绕增强(编程式)
在 Spring AOP 的世界里,与 AOP 相关的术语实在太多,往往也是我们的"拦路虎",不管是看那本书或是技术文档,在开头都要将这些术语逐个灌输给读者。我想这完全是在吓唬人了,其实没那么复杂的,大家放轻松一点。
我们上面例子中提到的 before() 方法,在 Spring AOP 里就叫 Before Advice(前置增强)。有些人将 Advice 直译为"通知",我想这是不太合适的,因为它根本就没有"通知"的含义,而是对原有代码功能的一种"增强"。再说,CGLib 中也有一个 Enhancer 类,它就是一个增强类。
此外,像 after() 这样的方法就叫 After Advice(后置增强),因为它放在后面来增强代码的功能。
如果能把 before() 与 after() 合并在一起,那就叫 Around Advice(环绕增强),就像汉堡一样,中间夹一根火腿。
这三个概念是不是轻松地理解了呢?如果是,那就继续吧!
我们下面要做的就是去实现这些所谓的"增强类",让他们横切到代码中,而不是将这些写死在代码中。
先来一个前置增强类吧:
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before");
}
}
注意:这个类实现了 org.springframework.aop.MethodBeforeAdvice 接口,我们将需要增强的代码放入其中。
再来一个后置增强类吧:
public class GreetingAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After");
}
}
类似地,这个类实现了 org.springframework.aop.AfterReturningAdvice 接口。
最后用一个客户端来把它们集成起来,看看如何调用吧:
public class Client {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory(); // 创建代理工厂
proxyFactory.setTarget(new GreetingImpl()); // 射入目标类对象
proxyFactory.addAdvice(new GreetingBeforeAdvice()); // 添加前置增强
proxyFactory.addAdvice(new GreetingAfterAdvice()); // 添加后置增强
Greeting greeting = (Greeting) proxyFactory.getProxy(); // 从代理工厂中获取代理
greeting.sayHello("Jack"); // 调用代理的方法
}
}
请仔细阅读以上代码及其注释,您会发现,其实 Spring AOP 还是挺简单的,对吗?
当然,我们完全可以只定义一个增强类,让它同时实现 MethodBeforeAdvice 与 AfterReturningAdvice 这两个接口,如下:
public class GreetingBeforeAndAfterAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before");
}
@Override
public void afterReturning(Object result, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After");
}
}
这样我们只需要使用一行代码,同时就可以添加前置与后置增强:
proxyFactory.addAdvice(new GreetingBeforeAndAfterAdvice());
刚才有提到"环绕增强",其实这个东西可以把"前置增强"与"后置增强"的功能给合并起来,无需让我们同时实现以上两个接口。
public class GreetingAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before();
Object result = invocation.proceed();
after();
return result;
}
private void阿里四面:你知道Spring AOP创建Proxy的过程吗?