1、初识AOP
很久以前就听说了AOP编程,也知道字母上的解释是“面向切面的编程”,更久之前我也听过OOP“面向对象编程”。然而AOP不是OOP的代替,而是基于OOP 的完善,AOP只是一种编程风格。
下面是一个OOP用烂了的例子,账户类封装了存钱和取钱的接口供外部类调用。
但是现实中的账户存取操作远比这个复杂,需要有事务、日志、同步等操作。如果使用Java面向对象的方法来扩展该类,常用的方式是继承,但是JAVA的单继承属性导致扩充起来很麻烦,而且每当要增加一个新的功能就需要修改原有的代码,其可扩展性比较差。如果使用AOP编程,其效果是这样的:
通过AOP的方式,使得类拓展功能起来非常方便,而且功能与类之间的耦合度更低。下面从零开始编写一个AOP小程序,我们要仿照主流的框架Spring来编写,顺便理解Spring AOP的原理。
2、简单AOP框架实现
根据上图所示的模型,在不改变类的基础上能够给类的实现进行扩充,除了继承还有一种方式,那就是代理(Proxy)。代理是什么,举个简单的例子,明星唱歌,明星只需要会唱歌就好了,那么签约什么技能该怎么办?这时候需要一个经纪人(代理),在明星唱歌的时候,代理要做一些额外的工作。
下面依然用为账户存取添加事务功能为例,给账户添加一个代理。Java中,代理的生成过程比较简单,首先我们需要一个基础账户的接口,以及一个账户类。
1、账户接口
package proxy;
import annotation.MyAnnotation;
import annotation.Transaction;
/**
* Created by maitian13 on 2016/11/3.
*/
public interface CountInterface {
public void add(int c);
public void sub(int c);
}
2、账户类
/**
* Created by maitian13 on 2016/11/3.
*/
public class Count implements CountInterface{
private int money;
public Count(int m){this.money=m;}
public void add(int c){this.money+=c;}
public void sub(int c){this.money-=c;}
public void display(){
System.out.println(this.money);
}
}
3、代理调用
public class Main {
public static void main(String[] args) {
Count c=new Count(10);
Object pro=Proxy.newProxyInstance(c.getClass().getClassLoader(), c.getClass().getInterfaces(), new MyProxyHandler(c));
((CountInterface)pro).add(1);
c.display();
}
}
代理生成代码非常简单,需要一个ClassLoader、代理类的接口、以及InvocationHandler实例,其中InvocationHandler定义了代理在调用的时候的操作,需要自己定义,如下所示为自定义的InvocationHandler:
4、自定义InvocationHandler
/**
* Created by maitian13 on 2016/11/3.
*/
public class MyProxyHandler implements InvocationHandler{
Object target;
public MyProxyHandler(Object o){this.target=o;}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation[] a=method.getAnnotations();
System.out.println("start session");
Object res=method.invoke(target,args);
System.out.println("end session");
return res;
}
}
要看懂上述代码,需要知道一些反射的知识。如果通过代理类来调用方法,例如上述Main中((CountInterface)pro).add(1)就会调用invoke方法,在本例中代理先输出“start session”,然后调用acount类的add方法,在调用成功之后打印“end session”,该过程模拟了事务的操作过程。
对比Spring事务的流程,我们可能会质疑,我们从来就没有定义过代理类啊?我们在增加事务支持的时候只需要添加一个@Transaction注解就行了,如下所示
@Transaction
public void add(int money){...}
其中Transaction称为注解,以前一直不明白,为何在Spring里面写上一句注解就可以让一个方法具有事务的功能。经过仔细地调研,我才知道一句注解后面也包含了反射、代理等JAVA中高级又核心的技术。下面我们也使用注解的方式来支持事务。
5、事务注解
Java有系统自带注解以及自定义注解之分。系统自带注解用途也不尽相同,如@override注解用于编译时检测,@Document、@Target等属于注解的注解。下面自定义一个注解:
@Documented//生成JAVADoc的时候会自动生成
@Target({ElementType.TYPE})//只用于类型定义前注解,如Class定义
public @interface MyAnnotation {
String author();
String date();
int version() default 1;
}
上述注解可以用在类定义前,表明作者信息。下面是一个Transaction注解,该注解只能在方法前注解,能够在运行时被发现。
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
@Inherited
public @interface Transaction {
/*
use to defined Transaction method
*/
}
此时,为了支持注解,需要稍微修改一下MyProxyHandler:
public class MyProxyHandler implements InvocationHandler{
Object target;
public MyProxyHandler(Object o){this.target=o;}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation[] a=method.getAnnotations();
if(method.getAnnotation(Transaction.class)!=null){//判断该函数是否支持事务
System.out.println("start session");
}
Object res=method.invoke(target,args);
if(method.getAnnotation(Transaction.class)!=null){//判断该函数是否支持事务
System.out.println("end session");
}
return res;
}
}
6、测试
下面使用3中的测试代码,得到如下结果:
start session
end session
11
可以看到调用的同事还模拟了事务的流程,同事账户的数值也得到了增加。
3、小结
此博客中对AOP的介绍还处于很肤浅的阶段,大家仅当科普,有错误还望指正。Spring是一个使用了AOP编程模式的框架,很多地方值得学习,本文中的例子只是从一个侧面模仿Spring,还有很多不足之处,例如通常在使用事务的时候我们都没有发现代理的存在,而实际上代理由Spring容器维护着,不需要我们去生成,这就是Spring的厉害之处,即使不懂AOP也能编写功能完善的代码。