我们所希望的AOP是这样的:
业务单独开发,服务也单独开发.将希望被切入的业务颗粒扔到容器中,通过AOP这种思想(AOP的实现有多种)将服务切进去,换句话说,就是在AOP提供的切面类上配置服务与业务间的切入关系,然后将业务和服务都分别交给容器管理.
原来我们一直把它做成了这样:
这种实现确实也能够满足业务和服务单独开发,但是AOP不是作为工作的bean存在,切面类不可复用,不灵活.
为了让系统更灵活,我们首先要把写死在代理类(切面类)中的服务分离出去.
问题一:现在业务和服务已经彻底分离,如何让二者在需要的时候联合起来?
问题二:当有多个服务同时要切入业务系统,各个服务的方法不能写到代理类即切面类里,切面如何被复用?
我们来看一下不借助任何框架的简单实现.
业务:
public class CourseManager {
public void addCourse(){
System.out.println("The course has been added successfully !");
}
}
日志服务:
public class LogUtil {
public void Info(){
System.out.println("Info: this is Infolog.");
}
}
封装服务的容器:
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* 日志,权限,工作流等类和方法的封装类
* @author ghy
* version 3.0.0 , 2015年5月25日 上午10:57:31
*/
public class ProxyMethods {
private HashMap<String, Object> logBeans;
private HashMap<String, String> logMethodBeans;
public void writeLog(){
try {
for(HashMap.Entry<String,Object> entry : logBeans.entrySet()){
String key =entry.getKey();
Object value =entry.getValue();
Method writeLogMethod=value.getClass().getMethod(logMethodBeans.get(key));
writeLogMethod.invoke(value);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
public HashMap<String, Object> getLogBeans() {
return logBeans;
}
public void setLogBeans(HashMap<String, Object> logBeans) {
this.logBeans = logBeans;
}
public HashMap<String, String> getLogMethodBeans() {
return logMethodBeans;
}
public void setLogMethodBeans(HashMap<String, String> logMethodBeans) {
this.logMethodBeans = logMethodBeans;
}
}
容器中定义了两个hashmap,分别是类和方法的集合,简单的实现只有一个日志类,这个类中只有一个Info()方法.而且它们保存的key值相同,我们遍历集合,得到这个类里面的方法.
代理类:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 一个业务类,一个方法
* 代理类,即AOP的切面类,连接核心业务类和日志等功能类的方法,实现了拦截
* @author ghy
* version 3.0.0 , 2015年5月25日 上午10:59:11
*/
public class CGLibDynamicProxy implements MethodInterceptor{
//单例模式获得代理对象
public static CGLibDynamicProxy instance=new CGLibDynamicProxy();
private CGLibDynamicProxy(){
}
public static CGLibDynamicProxy getInstance(){
return instance;
}
//持有对proxyMethod的引用
private ProxyMethods proxyMethods;
//泛型方法,得到代理类
public <T> T getProxy(Class<T> cls){
return (T)Enhancer.create(cls, this);
}
//拦截核心业务的方法,在核心业务方法执行前添加写日志的方法
@Override
public Object intercept(Object target, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
proxyMethods.writeLog();
Object result=methodProxy.invokeSuper(target, args);
return result;
}
public ProxyMethods getProxyMethods() {
return proxyMethods;
}
public void setProxyMethods(ProxyMethods proxyMethods) {
this.proxyMethods = proxyMethods;
}
}
实现原理是CGLib动态代理,代理类中持有对proxyMethod容器的引用,这里可以调用容器的方法,容器就是一个空壳子,在运行时具体执行的类和方法才被装载.
客户端:
public class Client {
public static void main(String[] args){
//定义两个hashmap分别盛放类和方法
HashMap<String, Object> logBeans=new HashMap();
HashMap<String, String> logMethodBeans=new HashMap();
//将日志类添加到盛放类的hashmap,将写日志的方法添加到盛放方法的hashmap
//两个hashmap内均有一个类和一个方法,key值相同
logBeans.put("LogUtil", new LogUtil());
logMethodBeans.put("LogUtil", "Info");
//实例化一个类和方法的封装类的对象,将盛放日志类和写日志的方法的两个hashmap放到对象中
ProxyMethods proxyMethods=new ProxyMethods();
proxyMethods.setLogBeans(logBeans);
proxyMethods.setLogMethodBeans(logMethodBeans);
//实例化代理对象
CGLibDynamicProxy cglib =CGLibDynamicProxy.getInstance();
//将对象和方法封装类的对象放到代理中
cglib.setProxyMethods(proxyMethods);
//声明一个业务类对象,并获得它的代理
CourseManager courseManager=cglib.getProxy(CourseManager.class);
//调用方法
courseManager.addCourse();
}
}
客户端手动new了一个容器,将日志服务注册到容器中,实例化代理对象,把容器挂到代理对象上,然后获得添加课程业务类的代理,调用方法.
运行结果:
如果我们希望在添加课程方法的后面切入日志方法,没关系,只要把容器的方法调用写在添加课程方法的后面就行了.类比springAOP的before,after,around等,我们这里都可以实现.另外要说的是,这只是写活了AOP的一个简单程序,还可以继续实现多个业务颗粒,多个服务,以及每个业务颗粒和服务中有多个类,每个类中有多个方法的例子.写代码和写文章是一样一样的,没有最好的代码只有更完善的代码.