AOP现在对大家来说都不是陌生的词,网上也有着大量的介绍与讲解,在这里我对AOP的基本知识就不多介绍(虽然懂得也不多),直入主题,我在学习研究过一段时间后总结一下,发现我们自己也可以写一个简单的AOP框架,能够实现简单的功能横切,虽说简单,但是AOP的核心思想着实能体现出来。本文就是想与大家交流一下我的迷你AOP框架,希望大家能多多分享自己的经验与想法。
我们首先明确一下框架的目标,框架框架肯定是可以多次套用并能在这个框架的规则下用相对较少的代码量实现我们想要的功能,那AOP 是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。 比如说,要在多个核心功能中同时加入一个日志功能,用传统的OOP(面向对象编程)思想就不能实现,自上而下的关系无法为对象引入公共的行为,那我们就目标明确:这个框架要可以在基于某种规则的配置下给程序动态统一添加功能并且不修改源代码(大部分情况).
AOP的实现可用多种语言,本框架使用JDK的动态代理API(API里面已经实现了动态代理的大部分,所以相对编写简单...).
一步一步来首先我们简单编写核心业务类:
package net.localer.test.dao;
public interface IPersonDAO {
publicvoid addPerson(String name);
publicvoid delPerson(int id);
}
package net.localer.test.dao;
public class PersonDAO implementsIPersonDAO{
publicvoid addPerson(String name) {
System.out.println("添加人员"+name);
}
publicvoid delPerson(int id) {
System.out.println("删除人员");
}
}
PersonDAO 里面有两个方法分别是添加和删除人员,但是突然有一天我们需要在两个方法前面加上一个check步骤。如果采用笨方式修改方法的源代码是非常浪费精力并且影响进度,同时check放发作为一个单独的逻辑放在增加和删除人员里面也是不合情理的。
所以我们需要借助AOP思想来把check方法横切到所有的方法前面,当然这时候我们就需要一个配置文件来进行配置,通过这个配置文件,我们能知道哪个类的哪个方法需要横切到具体哪个位置,这样通过修改配置文件我们可以轻松的给所有方法加上统一的'前奏',以下是配置文件与Check类
<?xml version="1.0"encoding="UTF-8"?>
<proxy>
<beanid="persondao" class="net.localer.test.dao.PersonDAO"/>
<beanid="check" class="net.localer.test.dao.aop.Check"/>
<!--
id只是一个标识,没有实际意义
source是包含了增强功能的类,target是源系统的类
sourcemethod是增强功能的类中的什么方法会动态的加入到目标类里面去
targetmethodprefix是源系统类什么前缀开头的方法需要加入增强功能类的方法
location是在target的什么位置去加入增强的代码,可以取值before after exception around
-->
<aopid="testaop" source="check" target="persondao"sourcemethod="test" targetmethodprefix="add"location="after" />
</proxy>
package net.localer.test.dao.aop;
importnet.localer.aop.config.JoinPoint;
public class Check {
publicvoid test(JoinPoint joinPoint) {
System.out.println("执行检查");
}
//N多方方法
}
proxy配置文件里bean节点对应业务类,在需要的时刻我们会根据class反射出对应的实体,aop节点使我们的重点配置项,其中source,target属性对应于bean节点的id属性,如同文件中所说的,我们要在这个节点配置多个属性以便框架知道用什么在哪里横切。
配置文件需要解析,解析之后的数据理应也要存到实体类中去,所以我们还要准备多个实体类来存配置文件的信息,实体类中的属性要与配置文件中节点属性相一致。如下:
package net.localer.aop.config;
/**
*
*该类对应于配置文件的<bean id="persondao"class="net.localer.spring.test.PersonDAOImpl"/>
*/
public class BeanConfig {
privateString id;
privateString clz;
publicString getId() {
returnid;
}
publicvoid setId(String id) {
this.id= id;
}
publicString getClz() {
returnclz;
}
publicvoid setClz(String clz) {
this.clz= clz;
}
}
package net.localer.aop.config;
/**
* 对应配置文件
* <aop id="testaop"source="check" target="persondao"sourcemethod="jdbc" targetmethodprefix="add"location="before" />
*
*/
public class AopConfig {
privateString id;
//因为source和target都是引用了bean标签配置的类
//而这个配置的类是不确定的类型,所以用Object来作为属性
privateObject source;
privateObject target;
privateString sourcemethod;
privateString targetmethodprefix;
privateString location;
publicObject getSource() {
returnsource;
}
publicvoid setSource(Object source) {
this.source= source;
}
publicObject getTarget() {
returntarget;
}
publicvoid setTarget(Object target) {
this.target= target;
}
publicString getSourcemethod() {
returnsourcemethod;
}
publicvoid setSourcemethod(String sourcemethod) {
this.sourcemethod= sourcemethod;
}
publicString getTargetmethodprefix() {
returntargetmethodprefix;
}
publicvoid setTargetmethodprefix(String targetmethodprefix) {
this.targetmethodprefix= targetmethodprefix;
}
publicString getLocation() {
returnlocation;
}
publicvoid setLocation(String location) {
this.location= location;
}
publicString getId() {
returnid;
}
publicvoid setId(String id) {
this.id= id;
}
}
要注意因为source和target都是引用了bean标签配置的类,而这个配置的类是不确定的类型,所以用Object来作为属性类型。
同时AopConfig 与BeanConfig 的对象肯定是以集合的方式存在所以我们需要ProxyConfig类,其中的两个HashMap将AopConfig 与BeanConfig存了起来并以id作为key值,如下:
package net.localer.aop.config;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ProxyConfig {
privateMap<String,BeanConfig> beanConfig = newHashMap<String,BeanConfig>();
privateMap<String,AopConfig> aopConfig = new HashMap<String,AopConfig>();
//根据bean标签的id获取到对应的target属性的aopConfig。对应关系bean的id属性,对应到aop标签的target和source属性
publicAopConfig getAopConfigByBeanId(String beanId) {
Iterator<AopConfig>it = aopConfig.values().iterator();
AopConfigconfig = null;
while(it.hasNext()) {
config= it.next();
//判断aop标签中的target属性是否和bean标签的id属性一致
if(config.getTarget().equals(beanId)) {
break;
}
}
returnconfig;
}
publicBeanConfig getBeanConfig(String id) {
returnbeanConfig.get(id);
}
publicvoid setBeanConfig(String id, BeanConfig config) {
beanConfig.put(id,config);
}
publicvoid setProxyConfig(String id, AopConfig config) {
aopConfig.put(id,config);
}
publicAopConfig getProxyConfig(String id) {
returnaopConfig.get(id);
}
}
其中的 getAopConfigByBeanId 方法是根据bean标签的id获取到对应的target属性的aopConfig,当然方法体的实现方式是在配置文件中找到第一个之后就停止不再继续往下找...
解析XML的过程不再敖述,这里也不再贴代码,有了以上的基础数据做铺垫之后我们就可以使用JDK的动态代理API完成自己的面向切面编程.