利用反射手写代码实现spring AOP
前言:上一篇博客自己动手编写spring IOC源码受到了大家的热情关注,在这里博客十分感谢。特别是给博主留言建议的@玛丽的竹子等等。本篇博客我们继续,还是在原有的基础上进行改造。下面请先欣赏一下博主画的一张aop简图(没有艺术天分,画的不好莫见怪)
解析:往往在我们的系统的多个核心流程中会有一部分与之关系不大的相同的横切流程,例如权限认证,事务管理。因此我们一般会抽象出这些相同的比较次要的交给spring aop的Handler来统一处理这些横切流程也就是上图中绿色部分。接下来我们看一下本例结构图:
解析:1,我们的Hostess对象是Master接口的实现,主要实现了WalkDog()和shopping()两个方法,而WalkDog()方法则是调用的是Dog接口的实现类的bark()方法。
2,我们整个程序的入口Client调用的Hostess对象的两个核心方法,HumanHandler处理的Hostess对象的横切流程。
1 public class Client { 2 3 public static void main(String[] args) { 4 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 5 6 Master master = (Master)context.getBean("humanProxy"); 7 8 System.out.println(""); 9 System.out.println(""); 10 System.out.println(""); 11 System.out.println(""); 12 13 master.shopping(); 14 master.WalkDog(); 15 16 } 17 }
1 package human; 2 3 import dog.Dog; 4 5 public class Hostess implements Master { 6 7 private Dog dog; 8 9 public void setDog(Dog dog) { 10 this.dog = dog; 11 } 12 13 @Override 14 public void WalkDog() { 15 16 dog.bark(); 17 } 18 19 @Override 20 public void shopping(){ 21 System.out.println("疯狂购物中"); 22 } 23 24 }
解析:通过以上代码我们不难发现我们的程序只是调用核心业务,而往往核心业务的周围有很多繁琐的相对于比较次要的横切业务。利用本例中遛狗,购物之前,我们需要再家做一些前提准备。例如:整理一下着装,锁上房门等等,回家之后有需要换鞋之类的。因此我们还需要一个handler来处理这些业务。
1 package aop; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 public class HumanHandler implements InvocationHandler { 7 8 private Object target;// 目标是不固定 9 10 public void setTarget(Object target) { 11 this.target = target; 12 } 13 14 /* 15 * return 返回是原来目标方法所返回的内容 method 就是要执行的方法 16 */ 17 @Override 18 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 19 // TODO Auto-generated method stub 20 before(); 21 // 具体的业务逻辑代码 22 Object returnValue = method.invoke(target, args); 23 24 after(); 25 return returnValue; 26 } 27 28 private void before() { 29 // 前置任务 30 System.out.println("[代理执行前置任务]整理着装"); 31 System.out.println("[代理执行前置任务]带上钥匙"); 32 System.out.println(""); 33 System.out.println("[核心业务开始]*****************"); 34 } 35 36 private void after() { 37 // 后置任务 38 System.out.println("[核心业务结束]*****************"); 39 System.out.println(""); 40 System.out.println("[代理执行后置任务]开门"); 41 System.out.println("[代理执行后置任务]换鞋"); 42 } 43 44 }
解析:有了handler我们还需要一个代理工厂
1 package org.springframework.aop.framework; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Proxy; 5 6 7 public class ProxyFactoryBean { 8 9 private Object target; 10 11 private InvocationHandler handler; 12 13 public ProxyFactoryBean(Object target,InvocationHandler handler){ 14 this.target = target; 15 this.handler = handler; 16 } 17 18 //返回本类的一个实例 19 public Object getProxyBean() throws IllegalArgumentException, InstantiationException, IllegalAccessException, ClassNotFoundException{ 20 Object obj = Proxy.newProxyInstance( 21 target.getClass().getClassLoader(), 22 target.getClass().getInterfaces(), 23 handler); 24 return obj; 25 } 26 }
接下来我们来看一下本例的具体配置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans> 3 <bean id="hostess" class="human.Hostess" scope="prototype"> 4 <property name="dog" ref="dog1"></property> 5 </bean> 6 7 <bean id="dog1" class="dog.Taidi" scope="prototype"></bean> 8 9 <bean id="dog2" class="dog.Labuladuo" scope="prototype"></bean> 10 11 <bean id="humanHandler" class="aop.HumanHandler"> 12 <property name="target" ref="hostess"></property> 13 </bean> 14 15 <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 16 <property name="handlerName" ref="humanHandler"></property> 17 <property name="target" ref="hostess"></property> 18 </bean> 19 20 21 </beans>
最后一步也是关键,本类中使用到的实例需要我们通过读取上面这份配置文件然后通过反射构造出来。
1 package aop; 2 3 import java.io.File; 4 import java.lang.reflect.InvocationHandler; 5 import java.lang.reflect.Method; 6 import org.dom4j.Document; 7 import org.dom4j.Element; 8 import org.dom4j.io.SAXReader; 9 import org.springframework.aop.framework.ProxyFactoryBean; 10 11 public class ClassPathXmlApplicationContext implements ApplicationContext { 12 13 private String fileName; 14 15 public ClassPathXmlApplicationContext(String fileName){ 16 this.fileName = fileName; 17 } 18 19 @Override 20 public Object getBean(String beanid) { 21 22 System.out.println("传递过来的ID:"+beanid); 23 24 //获取本类的当前目录 25 String currentPath = this.getClass().getResource("").getPath().toString(); 26 SAXReader reader = new SAXReader();//DOM4J解释器 27 Document doc = null;//xml文档本身 28 Object obj = null;//目标表创建出来的实例 29 try { 30 doc = reader.read( new File(currentPath+fileName) ); 31 String xpath = "/beans/bean[@id='"+beanid+"']"; 32 Element beanNode = (Element) doc.selectSingleNode(xpath); 33 String className = beanNode.attributeValue("class"); 34 35 36 if ("org.springframework.aop.framework.ProxyFactoryBean".equals(className)){ 37 38 39 Element interceptorNamesNode = 40 (Element) beanNode.selectSingleNode("property[@name='handlerName']"); 41 42 String handlerName_value = interceptorNamesNode.attributeValue("ref"); 43 44 Element targetNode = (Element) beanNode.selectSingleNode("property[@name='target']"); 45 String targetName_value = targetNode.attributeValue("ref"); 46 47 return forProxyFactoryBean(targetName_value,handlerName_value); 48 } 49 50 obj = Class.forName(className).newInstance(); 51 52 53 //查找下一代 54 Element propertyNode = (Element) beanNode.selectSingleNode("property"); 55 if (propertyNode!=null){ 56 //注入 57 //System.out.println("发现property节点,准备注入"); 58 //需要注入的属性名 59 String propertyName = propertyNode.attributeValue("name"); 60 //System.out.println("需要注入的属性:"+propertyName); 61 62 //注入方法 63 String executeMethod = "set"+(propertyName.substring(0, 1)).toUpperCase()+propertyName.substring(1,propertyName.length()); 64 //System.out.println("需要执行注入方法:"+executeMethod); 65 66 //需要注入的对象实例 67 String di_object_name = propertyNode.attributeValue("ref"); 68 //System.out.println("注入的对象是:"+di_object_name); 69 70 //定义我们的需要注入的对象实例[递归算法:1.层级是不知道多少层的 2.自己调用自己 3.最后1层会自己结束] 71 Object di_object = getBean(di_object_name); 72 //System.out.println("xxx:"+di_object); 73 74 //Method method = obj.getClass().getMethod(executeMethod,di_object.getClass().getInterfaces());// new Method(executeMethod); 75 Method []methods = obj.getClass().getMethods(); 76 77 for (Method m : methods) { 78 if(executeMethod.equals(m.getName()) ) { 79 m.invoke(obj, di_object); 80 break; 81 } 82 } 83 84 } 85 else{ 86 System.out.println("没有属性,结束即可"); 87 } 88 89 } catch (Exception e) { 90 e.printStackTrace(); 91 } 92 93 System.out.println("返回实例:"+obj); 94 return obj; 95 } 96 97 98 public Object forProxyFactoryBean(String targetName_value,String handlerName_value) throws Exception{ 99 System.out.println("目标对象"+targetName_value); 100 101 Object target = getBean(targetName_value); 102 103 System.out.println("代理对象"+handlerName_value); 104 105 InvocationHandler handler = (InvocationHandler) getBean(handlerName_value); 106 107 return new ProxyFactoryBean(target,handler).getProxyBean(); 108 } 109 }
下面是运行结果
总结:1 spring aop将我们的系统分为两部分,一核心业务,二横切业务。我们的只需关注核心业务,横切业务统一交给代理去处理。
2 本例依旧是利用反射调用横切方法实现aop,还是那句话,我们自己写的自然是漏洞百出,只是为了说明问题。作为一个开源的框架,如果对spring源码感兴趣的朋友可以自行查看。
另:博主的个人博客也在努力搭建中,欢迎与各位学习交流,谢谢!个人博客地址:http://www.singletonh.top/