Spring的MethodBeforeAdvice实现原理探究&&实现一个简单的Advice

来源:互联网 发布:水平位移监测数据分析 编辑:程序博客网 时间:2024/05/16 14:58

一、类结构

MethodBeforeAdvice整个过程大致就是使用的jdk提供的动态代理的接口InvocationHandler和Proxy

所以首先在这讨论下动态代理在这里是如何使用的

1、获得动态代理动态生成的类($Proxy0.class)

参考博文:http://sin90lzc.iteye.com/blog/1037448

Field field = System.class.getDeclaredField("props");    field.setAccessible(true);    Properties props = (Properties) field.get(null);    props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  


2、反编译$Proxy0.class

Frontend 2.0 plus(cracked)下载(download)

反编译得到的java文件,查看生成的源代码,


//该类为动态代理Proxy生成的,最后getBean也是获取的该类的对象,//但是无法在编译阶段访问,所以只能够转成接口类型或父类型访问相关的方法//这里实现Seller接口是Spring配置文件中指明的,实现的SpringProxy接口是Marker Interface,主要就是标志这是一个Spring生成的代理类//然后实现Advised接口,可以通过Advised接口获取代理配置信息public final class $Proxy0 extends Proxy implements Seller, SpringProxy, Advised{  public $Proxy0(InvocationHandler invocationhandler)//构造函数,构建的时候传递一个invocationHandler处理后续的方法调用   {        super(invocationhandler);    //此处的invocationHandler实际为JdkDynamicAopProxy的实例}.....//拿两个比较典型的看看该类是如何工作的 //该方法使用invocationHandler调用m2//m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);public final String toString()     {        try        {            return (String)super.h.invoke(this, m2, null);        }        catch(Error _ex) { }        catch(Throwable throwable)        {            throw new UndeclaredThrowableException(throwable);        }     }//添加通知的方法        //    m18 = Class.forName("org.springframework.aop.framework.Advised").getMethod("addAdvice", new Class[] {        //        Integer.TYPE, Class.forName("org.aopalliance.aop.Advice")         //   });    public final void addAdvice(int i, Advice advice)        throws AopConfigException    {        try        {            super.h.invoke(this, m18, new Object[] {                Integer.valueOf(i), advice            });            return;        }        catch(Error _ex) { }        catch(Throwable throwable)        {            throw new UndeclaredThrowableException(throwable);        }    }//需要代理的目标类的方法//m3 = Class.forName("com.iss.spring.advice.Seller").getMethod("sell", new Class[] {    //            Class.forName("java.lang.String")    //        });    public final void sell(String s)    {        try        {            super.h.invoke(this, m3, new Object[] {                s            });            return;        }        catch(Error _ex) { }        catch(Throwable throwable)        {            throw new UndeclaredThrowableException(throwable);        }    }......}

3.调用过程

通过上述源代码以及类图可以发现生成的$Proxy0这个类,也就是最后返回给用户的类对象,

给用户提供封装好的使用接口中这些方法直接调用,而不必自己去调用InvocationHandler

可以将该对象的引用强转为target接口类型,调用目标方法

这里的过程是调用了$Proxy0的sell方法,$Proxy0通过查找Seller接口找到方法,获取Method对象

然后传递给invocationHandler进行调用,

Seller seller = (Seller)context.getBean("proxy");seller.sell("Genke");


也可以把该对象的引用强转为Advised来获取代理配置信息

这里的过程是调用了$Proxy0的getTargetClass方法,$Proxy0通过查找Advised接口找到方法,获取Method对象

然后传递给invocationHandler进行调用,


Advised advised = (Advised) context.getBean("proxy");System.out.println(advised.getTargetClass());


InvocationHandler判断是否是Advised接口的,如果是就直接执行而不去管有通知

如果不是Advised接口的方法,则先获取通知链或者拦截器,执行完所有拦截器后将会执行目标对象的该方法,

注明:这里讨论的是MethodBeforeAdvice,几种Advice的执行顺序与目标对象的方法执行顺序见Spring的四种Advice源码探究


二、实现一个简单的通知(Advice)

Advice.java
package com.iss.spring.myadvice;import java.lang.reflect.Method;/** * 提供一个通知的接口 * @author Administrator * */public interface Advice{public void before(Object target,Method method,Object[] args);}


Bean.java
package com.iss.spring.myadvice;/** * 维护bean信息 * @author Administrator * */public class Bean{private String id;private String clazz;        /*..........getter and setter..........*/}

Configuration.java
package com.iss.spring.myadvice;import java.util.List;/** * 该接口用来维护相关的配置文件信息 * @author Administrator * */public interface Configuration{public List<String> getProxyInterfaces();public List<String> getInterceptorNames();public String getTarget();}

ConfigurationImpl.java
package com.iss.spring.myadvice;import java.util.ArrayList;import java.util.List;/** * 实现Configuration,维护配置信息 * 继承Bean,维护bean本身信息 * @author Administrator *  */public class ConfigurationImpl extends Bean implements Configuration{private List<String> proxyInterfaces;private List<String> interceptorNames;private String target;private List<Class> Interfaces;private List<Object> Interceptor;private Object targetSource;public ConfigurationImpl(){proxyInterfaces = new ArrayList<String>();interceptorNames = new ArrayList<String>();Interfaces = new ArrayList<Class>();Interceptor = new ArrayList<Object>();}/*...getter and setter...*/}

MyHandler.java
package com.iss.spring.myadvice;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 代理类,执行$Proxy0传递过来的方法 * @author Administrator * */public class MyHandler implements InvocationHandler{private Configuration config;public MyHandler(Configuration config){this.config = config;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable{if (config == null)return null;if(method.getDeclaringClass()==Configuration.class) //如果是调用获取配置信息则直接执行,忽略拦截器{return method.invoke(config,args);}if (config instanceof ConfigurationImpl){for (Object interceptor : ((ConfigurationImpl) config)  //需要执行所有的拦截器或通知.getInterceptor()){if (interceptor instanceof Advice) //如果该拦截器类实现了Advice接口则调用该拦截器的before方法{((Advice) interceptor).before(((ConfigurationImpl) config).getTargetSource(),method, args);}}}return method.invoke(((ConfigurationImpl)config).getTargetSource(), args);//执行目标对象的目标方法}}

XmlContext.java

package com.iss.spring.myadvice;import java.io.IOException;import java.lang.reflect.Proxy;import java.util.ArrayList;import java.util.List;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import org.springframework.core.io.ClassPathResource;/** * 使用Dom4j解析Xml,保存至Configuration中 *  * @author Administrator *  */public class XmlContext{private static final String SPRING_CONFIGURATION_IMPL_CLASS = "com.iss.spring.myadvice.ConfigurationImpl";private String path;private Document doc;private List<Bean> beans;public XmlContext(String path) throws DocumentException, IOException{this.path = path;try{doc = new SAXReader().read(path);}catch (Exception e){doc = new SAXReader().read(new ClassPathResource(path).getFile());// 在path上未找到则在classpath上找}beans = new ArrayList<Bean>();parse();}/** * 在xml中配置的id属性,通过该id找到对应的Bean * @param name bean id * @return */private Bean getBeanById(String name){for (Bean bean : beans){if (bean.getId().equals(name)){return bean;}}return null;}/** * 提供给用户调用的getBean,如果是配置class为代理类则返回$Proxy0代理对象 * 否则返回bean对象本身 * @param name bean id * @return * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException */public Object getBean(String name) throws ClassNotFoundException,InstantiationException, IllegalAccessException{Bean bean = getBeanById(name);if (bean == null)System.out.println(name);if (bean.getClazz().equals(SPRING_CONFIGURATION_IMPL_CLASS)){ConfigurationImpl config = (ConfigurationImpl) bean;List<Class> interfaces = new ArrayList<Class>();for (String i : config.getProxyInterfaces()){interfaces.add(Class.forName(i));}interfaces.add(Configuration.class);config.setInterfaces(interfaces); // 设置所有接口List<Object> interceptors = new ArrayList<Object>();for (String i : config.getInterceptorNames()){interceptors.add(getBean(i));}config.setInterceptor(interceptors); // 设置所有拦截器config.setTargetSource(getBean(config.getTarget())); // 设置目标对象return Proxy.newProxyInstance(config.getClass().getClassLoader(),interfaces.toArray(new Class[0]), new MyHandler(config)); // 返回生成的中间类$Proxy0对象,调用函数会经MyHandler处理}else{// 假设就是一个简单的没有属性的beanreturn Class.forName(getBeanById(name).getClazz()).newInstance();}}/** * 开始解析,获取xml所有配置信息 */private void parse(){if (doc == null)return;Element root = doc.getRootElement();for (Object obj : root.elements()){Element element = (Element) obj;if (element.getName().equals("bean"))parseBean(element);}}/** * 解析单个bean节点 * @param bean */private void parseBean(Element bean){Bean obj = null;if (bean.attributeValue("class").equals(SPRING_CONFIGURATION_IMPL_CLASS)){obj = new ConfigurationImpl();ConfigurationImpl temp = (ConfigurationImpl) obj;for (Object property : bean.elements("property")){parseProperty((Element) property, temp);}}else{obj = new Bean();}obj.setId(bean.attributeValue("id"));obj.setClazz(bean.attributeValue("class"));beans.add(obj);}/** * 解析单个property节点 * @param property * @param config */private void parseProperty(Element property, ConfigurationImpl config){if (property.attributeValue("name").equals("proxyInterfaces")){for (Object value : property.element("list").elements("value")){Element v = (Element) value;config.getProxyInterfaces().add(v.getText());}}else if (property.attributeValue("name").equals("interceptorNames")){for (Object value : property.element("list").elements("value")){Element v = (Element) value;config.getInterceptorNames().add(v.getText());}}else if (property.attributeValue("name").equals("target")){/* * 先找local,如果local为null,则找bean */String bean = property.element("ref").attributeValue("local") == null ? property.element("ref").attributeValue("bean") : property.element("ref").attributeValue("local");config.setTarget(bean);}}}

测试实例:

beans.xml
<bean id="BreadSeller" class="com.iss.spring.advice.BreadSeller"></bean><bean id="MyAdvice" class="com.iss.spring.advice.MyAdvice"></bean><bean id="proxy" class="com.iss.spring.myadvice.ConfigurationImpl"><property name="proxyInterfaces"><list><value>com.iss.spring.advice.Seller</value></list></property><property name="interceptorNames"><list><value>MyAdvice</value></list></property><property name="target"><ref local="BreadSeller" /></property></bean>

MyAdvice.java

public class MyAdvice implements Advice{@Overridepublic void before(Object target, Method method, Object[] args){System.out.println(target+" is say hello to "+args[0] + " and then "+method);}}


main
public static void main(String[] args){try{XmlContext context = new XmlContext("beans.xml");Seller seller = (Seller)context.getBean("proxy");//Seller接口就定义个sell(String)方法seller.sell("Genke");Configuration config = (Configuration)context.getBean("proxy");System.out.println(config.getTarget());}catch (Exception e){e.printStackTrace();}}