什么是AOP
AOP是Aspect Oriented Programming的缩写,意思是面向切面编程,与OOP(Object Oriented Programming)面向对象编程对等,都是一种编程思想。
AOP主要遵循关注点分离原则,程序总是被分为几块,有核心部分,也有辅助部分。当我们在重点关注核心业务时,此时可以考虑将非核心部分剥离出来,例如订单核心模块时下单、支付,而对于记录日志、通知或触发机制可以考虑通过切面编程实现。而接下来我们将通过几个例子来具体了解怎么实现,看完后你将学习到Java重要的的反射机制,代理等知识。
反射和动态代理
当我们在面试时可能会碰到这样的笔试题:
你开发的核心订单服务,有一个支付方法,现在要为这个支付方法增加统计执行时间的切面。请通过面向切面编程围绕pay方法计算耗费时间, 已有代码:
public interface IOrder {
void pay() throws InterruptedException;
void show();
}
public class Order implements IOrder {
int state = 0;
@Override
public void
pay() throws InterruptedException {
Thread.sleep(50);
this.state = 1;
}
@Override
public void show() {
System.out.println("order status:" + this.state);
}
}
public class TimeUsageAspect implements Aspect{
long start;
@Override
public void before()
{
start = System.currentTimeMillis();
}
@Override
public void after()
{
long usage = System.currentTimeMillis() - start;
System.out.format("time usage : %dms\n", usage);
}
}
IOrder order = Aspect.getProxy(Order.class,
"coding.proxy.TimeUsageAspect");
order.pay();
order.show();
我们重点需要实现getProxy方法,实现如下:
public interface Aspect
{
void before();
void after();
/**
* 切面实现
* @param cls
* @param aspects
* @param <T>
* @return
*/
static <T> T getProxy(Class<T> cls,String ... aspects)
{
List<Aspect> aspectIns=Arrays.stream(aspects).map(aspect->{
try {
// 动态加载类
Class clazz= Class.forName(aspect);
// 获取代理类实例
return (Aspect)clazz.getConstructor().newInstance();
} catch (Exception e) {
return null;
}
}).filter(t->t!=null).collect(Collectors.toList());
// 获取具体业务类实例,比如new Order()
T inst = cls.getConstructor().newInstance();
// 代理的是接口(Interfaces),不是类(Class),也不是抽象类
return (T)Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行一些pay之前的逻辑
aspectIns.forEach(aspect -> aspect.before());
Object result=method.invoke(inst);
// 执行一些pay之后的逻辑
aspectIns.forEach(aspect -> aspect.after());
return result;
}
});
}
}
动态代理的是接口(Interfaces),不是类(Class),也不是抽象类,所以最后我们都需要通过接口去IOrder 去接受,否则会转换失败。
上述通过Java的反射调用动态代理Proxy的newProxyInstance方法实现切面编码,而其中有三个参数我们需要明白:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
invocationHandler:动态代理方法在执行时,会调用h里面的invoke方法去执行
什么是反射?也许你应该可以理解到:
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
实现AOP注解
对于上面的方式,如何通过注解来实现呢?也许你可以这样:
首先定义一个注解的接口
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
public Class type();
}
RetentionPolicy.RUNTIME表示在运行中可以加载到
ElementType.TYPE表示注解可以加载到接口、类、枚举上
其它几种类型有
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
接着将注解添加到Order类上
@Aspect(type=TimeUsageAspect.class)
public class Order implements IOrder {
int state = 0;
@Override
public void
pay() throws InterruptedException {
Thread.sleep(50);
this.state = 1;
}
@Override
public void show() {
System.out.println("order status:" + this.state);
}
}
最后实现注解的拦截
public class ObjectFactory {
public static <T> T newInstance(Class<T> clazz) throws NoSuchMethodException,InstantiationException,IllegalAccessException,InvocationTargetException{
Annotation[] annotations=clazz.getConstructor().getAnnotations();
List<IAspect> aspects=new LinkedList<>();
for(Annotation annotation:annotations){
if(annotation instanceof Aspect){
Class aspect=((Aspect) annotation).type();
aspects.add((IAspect)aspect.getConstructor().newInstance());
}
}
T inst=clazz.getConstructor().newInstance();
return (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),new OrderInvocationHandler(aspects,inst));
}
}
通过添加注解的方式,我们确实发现这样的业务代码逻辑就十分简单了,不需要再去传入多余的参数,只关注业务本身即可。