编程思想: 面向切面编程(Aspect-Oreinted Pro
面向对象编程(Object-Orentied Programming - OOP)的特点是继承, 多态和封装, 其中封装指的是把属性和方法按类(Class)进行划分, 从而复用代码并降低编程的复杂性. 随着业务的变化, 项目中的类越来越多, 开发者又发现了新的问题:
- 不同类之间重复的代码越来越多. 例如每个类的方法中都要打日志.
- 方法中除了业务逻辑之外, 要实现大量与业务无关的功能, 例如调试, 缓存, 鉴权等.
为了解决上述问题, 第一个思路是把重复的代码或者业务逻辑之外的功能抽象出来, 然后在新的类中实现. 这样一来新类与旧类在项目中就耦合了, 即, 新类的改变会影旧类. 第二个思路是在旧类需要的时候(编译或运行时)动态地加入这些功能, 即 面向切面编程. 切面代表了一个功能点, 它一般是对类的功能的补充. 站在业务的角度来看, 切面是与业务逻辑无关的功能.
如果把面向对象编程看成是纵向的(类之间功能独立), 那么面向切面编程就是横向的, 它为纵向的类提供业务无关的能力, 因此面向切面编程可以看成是对面向对象编程的补充. 下面引入一些例子来介绍AOP的实现.
Python实现
Python是动态语言, 利用装饰器语法糖可以容易地实现切面. 下面是计时器的例子.
import time
def timer(func):
"""
计时装饰器.
:param func 被装饰的函数
:return 装饰之后的函数(wrapper)
"""
def wrapper(*args, **kwargs):
"""
装饰之后的函数
"""
start = time.time()
f = func(*args, **kwargs) # 执行被装饰的函数
end = time.time()
print("[%s] costs %.2f seconds" % (func.__name__, end-start))
return f
return wrapper
@timer
def test():
time.sleep(1)
if __name__ == '__main__':
test()
Java实现
要实现一个Reminder
接口, 其核心功能是发一条提醒的消息. 除此之外, 发消息之前需要取得授权, 成功发消息之后需要打一条日志. 在这里我们把授权和打日志看成是切面 (非业务逻辑). 为了实现切面功能, 我们需要用到设计模式中的 代理模式.
// Reminder.java
public interface Reminder {
public void remind();
}
Reminder
的实现如下
// ReminderImpl.java
public class ReminderImpl implements Reminder {
@Override
public void remind() {
System.out.println("Reminder: Wake up!");
}
}
1. 静态代理
创建一个代理类ReminderProxy
, 只负责实现授权和打日志功能. 业务逻辑的处理则调用Reminder
中的方法.
// ReminderProxy.java
public class ReminderProxy implements Reminder {
private Reminder reminder; // 被代理的对象
public ReminderProxy(Reminder reminder) {
this.reminder = reminder;
}
private void authorize() {
System.out.println("Authorization: OK.");
}
private void log() {
System.out.println("Log info: Remind at " + new Date().getTime());
}
@Override
public void remind() {
authorize(); // 授权
reminder.remind(); // 调用代理类的业务逻辑
log(); // 打日志
}
}
Client可以这样使用代理类:
// Client.java
public class Client {
@Test
public void clientOfProxy() {
Reminder reminder = new ReminderProxy(new ReminderImpl());
reminder.remind();
}
}
静态代理虽然能为类增加新的功能, 但它依赖已经实现的类. 当有多个实现类时, 我们需要依次实现多个代理类, 这样并不能减少重复代码. 为了解决这个问题, 我们利用java的反射动态生成代理类, 即在编译或运行时生成类.
2. 动态代理
Java对动态代理提供了一下支持:
-
java.lang.reflect.Proxy
动态生成代理类和对象 -
java.lang.reflect.InvocationHandler
实现对代理类和对象的访问
下面我们用动态代理的方法来实现上述功能(即AOP的思想). 我们考虑一个非常简单的模型, 即在代理对象执行的方法之前和之后分别加入bofore
和after
的方法.
- 定义新功能的接口:
-
BeforeAdvice
: 在代理方法之前执行(例如, 授权) -
AfterAdvice
: 在代理方法之后执行(例如, 打日志)
-
// BeforeAdvice.java
public interface BeforeAdvice {
public void before();
}
// AfterAdvice.java
public interface AfterAdvice {
public void after();
}
- 创建代理工厂, 利用
InvocationHandler
调用被代理的对象.
public class ProxyFactory {
private BeforeAdvice beforeAdvice;
private Object object; // 被代理的对象
private AfterAdvice afterAdvice;
public ProxyFactory(BeforeAdvice beforeAdvice, Object object, AfterAdvice afterAdvice) {
this.beforeAdvice = beforeAdvice;
this.object = object;
this.afterAdvice = afterAdvice;
}
// 用来调用被代理对象
private InvocationHandler invocationHandler = new InvocationHandler() {
/**
*
* @param proxy 代理对象
* @param method 被调用的方法
* @param args 被调用方法的参数
* @return 被调用方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用代理对象之前执行的方法
if (beforeAdvice != null) beforeAdvice.before();
// 调用被代理对象object的方法
Object result = null;
if (object != null) result = method.invoke(object, args);
// 调用代理对象之后执行的方法
if (afterAdvice != null) afterAdvice.after();
return result;
}
};
// 创建被代理对象
public Object createProxy() {
// Proxy.newProxyInstance参数:
// 1. ClassLoader: 定义代理类的类加载器
// 2. Class[]: 接口类的列表
// 3. InvocationHandler: 调用方法的处理器
return Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
object.getClass().getInterfaces(),
invocationHandler
);
}
}
- Client通过实现
BeforeAdvice
和AfterAdvice
为代理对象添加新的功能.
public class Client {
private static class ReminderBeforeAdvice implements BeforeAdvice {
@Override
public void before() {
System.out.println("Authorization: OK.");
}
}
private static class ReminderAfterAdvice implements AfterAdvice {
@Override
public void after() {
System.out.println("Log info: Remind at " + new Date().getTime());
}
}
@Test
public void clientOfProxyFactory() {
Reminder reminder = (Reminder)new ProxyFactory(
new ReminderBeforeAdvice(),
new ReminderImpl(),
new ReminderAfterAdvice()
).createProxy();
reminder.remind();
}
}