自己实现Spring AOP(二)JDK代理实现AOP

前言

上一篇文章自己实现Spring AOP(一)环境搭建及知识准备,我搭建好了项目也写了一个例子,接下来我就要实现这个例子中的AOP功能。

在Spring中如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,如果目标对象没有实现接口,必须采用CGLib(Code Generation Library)方式,下面我就先用JDK的动态代理实现一下。

准备工作

在实现AOP功能前,先准备好要测试的相关类

准备好目标对象相关类和接口

UserDao接口

package edu.jyu.dao;

public interface UserDao {
    public void add(String user);

    public String getUser(String id);
}

UserDaoImpl类,实现了UserDao接口

package edu.jyu.dao;

public class UserDaoImpl implements UserDao {

    @Override
    public void add(String user) {
        System.out.println("add " + user);
    }

    @Override
    public String getUser(String id) {
        System.out.println("getUser " + id);
        return id + ":Jason";
    }
}

前置通知

还是以前置通知为例,我先仿造Spring弄个前置通知接口MethodBeforeAdvice,要自定义前置通知就必须实现它,它里面有个before方法,就是在目标方法执行前执行的

package edu.jyu.aop;

import java.lang.reflect.Method;

/**
 * 前置通知接口
 * 
 * @author Jason
 */
public interface MethodBeforeAdvice {

    /**
     * 
     * @param method
     *            目标方法
     * @param args
     *            目标方法所需的参数
     * @param target
     *            目标对象
     */
    void before(Method method, Object[] args, Object target);

}

然后自定义一个前置通知类MyBeforeAdvice,它实现了MethodBeforeAdvice接口

package edu.jyu.advice;

import java.lang.reflect.Method;

import edu.jyu.aop.MethodBeforeAdvice;

/**
 * 自定义前置通知类
 * 
 * @author Jason
 */
public class MyBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) {
        System.out.println("前置通知");
    }

}

ProxyFactoryBean

还记得上一章我用的那个例子嘛,配置生成代理对象时需要指定的class
org.springframework.aop.framework.ProxyFactoryBean

id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">

所以我也来定义一个ProxyFactoryBean类来专门生成的代理对象,现在还没有任何方法

package edu.jyu.aop;
/**
 * 用于生产代理对象的类
* @author Jason
 */
public class ProxyFactoryBean {

}

配置

实现AOP相关的类和接口都准备好了,现在要在applicationContext.xml文件中配置生成代理对象了


<beans>
    
    <bean name="userDao" class="edu.jyu.dao.UserDaoImpl">bean>

    
    <bean name="beforeAdvice" class="edu.jyu.advice.MyBeforeAdvice">bean>

    
    <bean name="userDaoProxy" class="edu.jyu.aop.ProxyFactoryBean">

        
        <property name="target" ref="userDao" />

        
        <property name="proxyInterface" value="edu.jyu.dao.UserDao" />

        
        <property name="interceptor" ref="beforeAdvice" />
    bean>
beans>

这个配置文件和上一章的例子中的很像,比较多的区别在于配置生成代理对象,在配置织入目标通知那里,Spring中的nameinterceptorNames,我的是
interceptor,为了简单,我只打算让一个目标对象只能有一个通知去增强。

那么根据这个配置文件ProxyFactoryBean文件就应该修改成如下

package edu.jyu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用于生产代理对象的类
 * 
 * @author Jason
 */
public class ProxyFactoryBean {
    // 目标对象
    private Object target;
    // 通知
    private Object interceptor;
    // 代理实现的接口
    private String proxyInterface;

    // 提供setter让容器注入属性

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setInterceptor(Object interceptor) {
        this.interceptor = interceptor;
    }

    public void setProxyInterface(String proxyInterface) {
        this.proxyInterface = proxyInterface;
    }
}

开发功能

现在创建对象的类edu.jyu.core.ClassPathXmlApplicationContext就需要进行修改了,因为它创建对象时得分两种情况,一种是创建一般对象,另外一种是创建代理对象。我们只需要修改它的createBeanByConfig方法

/**
 * 根据bean的配置信息创建bean对象
 * 
 * @param bean
 * @return
 */
private Object createBeanByConfig(Bean bean) {
    // 根据bean信息创建对象
    Class clazz = null;
    Object beanObj = null;
    try {
        clazz = Class.forName(bean.getClassName());
        // 创建bean对象
        beanObj = clazz.newInstance();
        // 获取bean对象中的property配置
        List properties = bean.getProperties();
        // 遍历bean对象中的property配置,并将对应的value或者ref注入到bean对象中
        for (Property prop : properties) {
            Map params = new HashMap<>();
            if (prop.getValue() != null) {
                params.put(prop.getName(), prop.getValue());
                // 将value值注入到bean对象中
                BeanUtils.populate(beanObj, params);
            } else if (prop.getRef() != null) {
                Object ref = context.get(prop.getRef());
                // 如果依赖对象还未被加载则递归创建依赖的对象
                if (ref == null) {
                    ref = createBeanByConfig(config.get(prop.getRef()));
                }
                params.put(prop.getName(), ref);
                // 将ref对象注入bean对象中
                BeanUtils.populate(beanObj, params);
            }
        }

        // 说明是要创建代理对象
        if (clazz.equals(ProxyFactoryBean.class)) {
            ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
            // 创建代理对象
            beanObj = factoryBean.createProxy();
        }

    } catch (Exception e1) {
        e1.printStackTrace();
        throw new RuntimeException("创建" + bean.getClassName() + "对象失败");
    }
    return beanObj;
}

也没改什么代码,只是新增了下面几句代码

// 说明是要创建代理对象
if (clazz.equals(ProxyFactoryBean.class)) {
    ProxyFactoryBean factoryBean = (ProxyFactoryBean) beanObj;
    // 创建代理对象
    beanObj = factoryBean.createProxy();
}

就是判断一下创建好的对象的类是否为ProxyFactoryBean,如果是的话,就将这个对象强转成ProxyFactoryBean类型对象,然后调用它的createProxy()方法来创建一个代理对象,并将这个代理对象作为最终结果beanObj

此时ProxyFactoryBean还没有createProxy()方法,所以现在就来创建这个方法并且实现它要完成的功能。代码如下

/**
 * 创建代理对象
 * 
 * @return
 */
public Object createProxy() {
    // 判断有没有指定proxyInterface,没有指定就用CGLib方式
    if (proxyInterface == null || proxyInterface.trim().length() == 0)
        return createCGLibProxy();
    // 使用JDK中的代理
    return createJDKProxy();
}

这个方法很简单,就是根据有没有指定proxyInterface来判断使用哪种动态代理方式去生成代理对象,没有接口的那就用CGLib,有的话就用JDK中的代理。

那现在用CGLib创建代理对象的createCGLibProxy()方法和用JDK创建代理对象的createJDKProxy()方法都还没有,我们要先创建这两个方法,createCGLibProxy()方法留到下一章再实现,现在先来实现一下createJDKProxy()方法

/**
 * JDK方式创建代理对象
 * 
 * @return
 */
private Object createJDKProxy() {
    Class clazz = null;
    try {
        clazz = Class.forName(proxyInterface);// 实现的接口
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        throw new RuntimeException(proxyInterface + "找不到,请注意填写正确");
    }
    // JDK方式生成的代理对象
    Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Object result = null;

                    // 要先判断interceptor是哪种通知类型,以决定执行目标方法的位置
                    // 判断interceptor是否为前置通知类型
                    if (interceptor instanceof MethodBeforeAdvice) {
                        MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
                        // 在目标方法执行前执行前置通知代码
                        advice.before(method, args, target);
                        // 执行目标方法
                        result = method.invoke(target, args);
                    }
                    return result;
                }
            });
    return proxyInstance;
}

使用JDK创建代理对象的方法也不难,主要关注在InvocationHandler那里,我先要判断一下interceptor是属于哪种通知类型,因为不同的通知类型在于目标方法的执行顺序上有所不同,比如说前置通知在目标方法前执行,后置通知在目标方法后执行,现在的实现其实是很有问题的,比如说我新加了一个后置通知,那么我就要在加一条判断分支判断interceptor是否为后置通知。这个问题我会在下一章我解决。

我再把ProxyFactoryBean整个类的代码贴上来吧

package edu.jyu.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用于生产代理对象的类
 * 
 * @author Jason
 */
public class ProxyFactoryBean {
    // 目标对象
    private Object target;
    // 通知
    private Object interceptor;
    // 代理实现的接口
    private String proxyInterface;

    // 提供setter让容器注入属性

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setInterceptor(Object interceptor) {
        this.interceptor = interceptor;
    }

    public void setProxyInterface(String proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    /**
     * 创建代理对象
     * 
     * @return
     */
    public Object createProxy() {
        // 判断有没有指定proxyInterface,没有指定就用CGLib方式
        if (proxyInterface == null || proxyInterface.trim().length() == 0)
            return createCGLibProxy();
        // 使用JDK中的代理
        return createJDKProxy();
    }

    /**
     * JDK方式创建代理对象
     * 
     * @return
     */
    private Object createJDKProxy() {
        Class clazz = null;
        try {
            clazz = Class.forName(proxyInterface);// 实现的接口
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException(proxyInterface + "找不到,请注意填写正确");
        }
        // JDK方式生成的代理对象
        Object proxyInstance = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;

                        // 要先判断interceptor是哪种通知类型,以决定执行目标方法的位置
                        // 判断interceptor是否为前置通知类型
                        if (interceptor instanceof MethodBeforeAdvice) {
                            MethodBeforeAdvice advice = (MethodBeforeAdvice) interceptor;
                            // 在目标方法执行前执行前置通知代码
                            advice.before(method, args, target);
                            // 执行目标方法
                            result = method.invoke(target, args);
                        }
                        return result;
                    }
                });
        return proxyInstance;
    }

    /**
     * CGLib方式创建代理对象
     * 
     * @return
     */
    private Object createCGLibProxy() {
        return null;
    }

}

测试

到此,JDK方式实现AOP就已经能行了,现在就可以写个测试类测试一下

package edu.jyu.aop;

import org.junit.Test;

import edu.jyu.core.BeanFactory;
import edu.jyu.core.ClassPathXmlApplicationContext;
import edu.jyu.dao.UserDao;

public class TestProxy {

    @Test
    public void testJDKProxy(){
        BeanFactory ac = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserDao userDao = (UserDao) ac.getBean("userDaoProxy");
        System.out.println(userDao.getClass());
        userDao.add("Jason");
        String user = userDao.getUser("132");
        System.out.println(user);
    }
}

输出结果

class com.sun.proxy.$Proxy4
前置通知
add Jason
前置通知
getUser 132
132:Jason

第一行输出可以看到确实是JDK生成的代理对象,然后再执行userDaoaddgetUser方法前也执行了前置通知的方法,最后的getUser的结果也没有错


现在,整个项目就已经完成一半了,另外一半就是增加CGLib方式创建代理对象还有优化一下架构。

JSpring AOP项目已经上传到Github上
https://github.com/HuangFromJYU/JSpring-AOP

你可能感兴趣的:(spring,JSpring,AOP)