在有了之前的数据基础之后,我们还需要再明确几个知识点,AOP把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流程是核心关注点(本框架中就是指PersonDAO这样的类),与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似,比如权限认证、日志、事务处理(本框架中就是指Check这样的类)。Aop的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。当然还有一个join point(连接点)我们也不能忽略,任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息,其可以看成是连接核心关注点与横切关注点的一个点(感觉说的还是很抽象..),在本框架中它起到了封装参数并将核心关注点的信息传递给横切关注点的作用, 如下:
packagenet.localer.aop.config;
public classJoinPoint {
private Object[] args = null;
public JoinPoint(Object[] args) {
this.args = args;
}
public Object[] getArgs() {
return args;
}
protected void setArgs(Object[] args) {
this.args = args;
}
}
其中的对象数组表示参数的类型是多种多样的,之前的check类的中test方法中的参数类型就是JoinPoint,这样统一规定了增强类(横切关注点)的方法的参数类型有利于框架下一步的编写。
现在我们可以开始着手动态代理,动态代理技术是利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行.用java实现动态代理需要用到JDK中的Proxy类与InvocationHandler接口.Proxy 提供用于创建动态代理类和实例的静态方法;InvocationHandler是代理实例的调用处理程序实现的接口,对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。(这是API里面的解释,对于这个动态代理,我懂得也不够深入,网上关于java动态代理的知识有很多,大家可以多看看)。于是我们需要编写DynaProxy这个调用处理程序类来实现InvocationHandler接口,真正的方法调用,方法切入都是在这里面实现,如下:
packagenet.localer.aop.config;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
public classDynaProxy implements InvocationHandler {
private AopConfig aopConfig = null;
public DynaProxy(AopConfig aopConfig) {
this.aopConfig = aopConfig;
}
/**
*invoke是生成后的代理类的增强方法
*我们应该根据配置文件中aop的配置来决定它是如何运作
*/
public Object invoke(Object proxy,Method method, Object[] args)
throws Throwable {
//获取目标类需要增强方法的前缀名
String methodPrefix =aopConfig.getTargetmethodprefix();
//判断出目标被调用的方法是否符合规定的前缀名,如果不符合,则调用目标类的方法直接返回
if(!method.getName().startsWith(methodPrefix)) {
//调用目标类的代码
method.invoke(aopConfig.getTarget(),args);
return proxy;
}
//这里一共分为了前置、后置、异常、环绕四种情况
//我们根据读取配置文件的情况,来确定是否要在适当的位置执行代码
//并且我们需要去判断是否我们置入的方法是正确有效的。
//获取植入位置
String location =aopConfig.getLocation();
//获取植入方法名字
String sourceMethod =aopConfig.getSourcemethod();
//获取植入的源类
Object obj =aopConfig.getSource();
//定义一个封装参数的类
JoinPoint jointPoint = newJoinPoint(args);
//获取植入方法的Mehtod实例
Method addMethod =obj.getClass().getMethod(sourceMethod, new Class[]{JoinPoint.class});
try {
//方法执行之前
//如果location定义了需要了before或者定义了around,那我们需要在方法执行之前植入代码
if(location.equals("before") || location.equals("around")) {
//反射调用植入方法
addMethod.invoke(aopConfig.getSource(),new Object[]{jointPoint});
}
//调用目标类的代码
method.invoke(aopConfig.getTarget(),args);
} catch (Exception e) {
e.printStackTrace();
//方法异常发生时
if(location.equals("exception") || location.equals("around")){
//反射调用植入方法
addMethod.invoke(aopConfig.getSource(),new Object[]{jointPoint});
}
}
//方法执行之后
if(location.equals("after") || location.equals("around")) {
//反射调用植入方法
addMethod.invoke(aopConfig.getSource(),new Object[]{jointPoint});
}
return proxy;
}
}
根据读取配置文件的情况来确定是否要在适当的位置执行代码,本框架就设置了四种情况:前置、后置、异常、环绕。之后获取植入方法的Mehtod实例,在适当的地方植入方法这样就能起到横切的效果。
接下来我们只要借助Proxy创建动态代理类即可大功告成,同时为了模仿spring框架我将创建动态代理的过程封装在BeanFactory类中,如下:
package net.localer.aop.config;
import java.lang.reflect.Proxy;
public class BeanFactory {
private ProxyConfig config = ProxyParser.parser();
/*
* 这个方法被调用的时候,传递一个bean标签的id属性的值
* 返回的是这个bean标签对应的class类增强后的代理类
*
*/
public Object getBean(String beanId) {
//根据beanId找到反射后的实例
AopConfig aopConfig = config.getAopConfigByBeanId(beanId);
Object target = aopConfig.getTarget();
//创建实现了InvocationHandler接口的实例
DynaProxy dynaProxy = new DynaProxy(aopConfig);
//调用Proxy的newInstance方法返回代理类
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), dynaProxy);
}
}
getBean方法只要求我们传一个bean的ID就可返回对应的代理类,整个过程全部封装在内部,外部调用接口时显得十分方便简单。(ProxyParser为解析XML类,这里不贴代码)
测试类如下:
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
BeanFactory bean = new BeanFactory();
IPersonDAO person = (IPersonDAO) bean.getBean("persondao");
person.addPerson("人员1");
//person.delPerson(1);
}
}
运行结果如下:
我们之前的配置文件将check方法作为切入方法并要求放在添加方法的后面,运行结果符合我们的配置要求,框架搭建成功。
这样当需求或者业务有所改变的时候我们基本可以不用修改源代码,而只需要对配置文件进行修改就能满足新的需求,简单方便,符合开闭原则.
小小的框架体现的是智慧的结晶,AOP对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 该框架还是有很多需要改进的地方,而且一定还有更好的实现方式,希望大家指出不足,多多交流,让我们有更大的进步。