手写spring(四)------------------------------------------------------------------aop(jdk动态代理和cglib动态代理)

项目地址:https://github.com/gongxianshengjiadexiaohuihui/noobspring

我们先来理一下思路,aop是需要织入通知,我们是按照动态代理来实现的,目的就是生成一个代理类对象替代ioc容器中原有的对象。 

private void doAspect(){
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始织入通知");
        if(ioc.size() == 0){
            return;
        }
        try {
            for (Object obj : ioc.values()) {
                Class<?> clazz = obj.getClass();
                if (clazz.isAnnotationPresent(NBAspect.class)) {
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(NBPointcut.class)) {
                            logger.debug("切点方法:{}",method.toString());
                            String beanName = StringUtil.getNameByMethod(method.getAnnotation(NBPointcut.class).value());
                            String[] str = beanName.split("\\.");
                            String simpleName = StringUtil.lowerFirstCase(str[str.length - 1]);
                            Class <?>[] interfaces = Class.forName(beanName).getInterfaces();
                            if(interfaces.length == 0) {
                                /**
                                 * 没有接口,所以用cglib动态代理
                                 */
                                CglibProxy cglibProxy = new CglibProxy();
                                ioc.put(simpleName,cglibProxy.getProxy(Class.forName(beanName),clazz.newInstance()));
                            }else {
                                /**
                                 * 有接口,所以用jdk动态代理
                                 */
                                if (ioc.containsKey(simpleName)) {
                                    JDKProxy proxy = new JDKProxy();
                                    ioc.put(simpleName, proxy.bind(Class.forName(beanName).newInstance(), clazz.newInstance()));
                                } else {
                                    /**
                                     * 不包含,说明ioc里的key放的是接口名,因此获取所有接口
                                     */
                                    for (Class<?> cl : interfaces) {
                                        String tampName = StringUtil.lowerFirstCase(cl.getSimpleName());
                                        if (ioc.containsKey(tampName)) {
                                            JDKProxy proxy = new JDKProxy();
                                            ioc.put(tampName, proxy.bind(Class.forName(beanName).newInstance(), clazz.newInstance()));
                                        }
                                    }
                                }
                            }

                        }
                    }


                }

            }
        }catch (Exception e){
            logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>织入通知失败");
            e.printStackTrace();
            throw new RuntimeException("织入通知失败");

        }
        logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>织入通知成功");
    }

第一步,判断ioc容器是否为空,如果为空就直接返回

然后,遍历ioc里面存的values,判断是否被@NBAspect标记

我们看一下例子

package com.ggp.demo.aop;

import com.ggp.framework.annotation.aop.*;
import com.ggp.framework.annotation.mvc.NBComponent;

/**
 * @Author:ggp
 * @Date:2019/1/26 14 54
 * @Description:
 */
@NBAspect
@NBComponent
public class DemoAspect_jdk {

    @NBPointcut(value = "com.ggp.demo.service.impl.DemoServiceImpl.genesis")
    public void print(){

    };
    @NBBefore
    public void before(String key){
        System.out.println("aspect>>>>>>>>before" + key);
    }
    @NBAfter
    public void after(){
        System.out.println("aspect>>>>>>>>after");
    };
    @NBAfterReturn
    public void afterReturning(){
        System.out.println("aspect>>>>>>>>afterReturning");
    }
    @NBAfterThrowing
    public void afterThrowing(){
        System.out.println("aspect>>>>>>>>afterThrowing");
    }
}

我们首先获取它的所有方法,找到被@NBPointcut标记的方法,这里面有我们需要的信息,就是这个切点来自那个类,我们进行处理获取相应的信息,比如上面的例子,我们得到demoServiceImpl,在ioc容器中直接用新生成的代理类对象替换原有的对象,

这里的代理类有两种,如果有接口,使用jdk动态代理

package com.ggp.framework.proxy;

import com.ggp.demo.aop.DemoAspect_jdk;
import com.ggp.demo.service.DemoService;
import com.ggp.demo.service.impl.DemoServiceImpl;
import com.ggp.framework.annotation.aop.*;
import com.ggp.framework.common.util.StringUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.HashSet;

/**
 * @Author:ggp
 * @Date:2019/1/26 15 50
 * @Description:
 */
public class JDKProxy implements InvocationHandler {
    /**
     * 真实对象
     */
    private Object target;
    /**
     * 切面配置
     */
    private Object aspect;
    /**
     * 保存切点
     */
    private HashSet points ;
    /**
     * 插入方法
     */
    private Method before;
    private Method after;
    private Method afterReturn;
    private Method afterThrowing;
    /**
     * 返回代理对象
     * @param target
     * @param aspect
     * @return
     */
    public Object bind(Object target, Object aspect){
        this.target = target;
        this.aspect = aspect;
        points = new HashSet();
        Method[] methods = aspect.getClass().getMethods();
        for(Method method : methods){
            if(method.isAnnotationPresent(NBPointcut.class)){
              String tampName =  StringUtil.getMethodName(method.getAnnotation(NBPointcut.class).value());
              points = StringUtil.setPoints(tampName, points);

            } else if(method.isAnnotationPresent(NBBefore.class)){
                this.before = method;
            } else if(method.isAnnotationPresent(NBAfter.class)){
                this.after = method;
            } else if(method.isAnnotationPresent(NBAfterReturn.class)){
                this.afterReturn = method;
            } else if(method.isAnnotationPresent(NBAfterThrowing.class)){
                this.afterThrowing = method;
            } else {continue;}
        }
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         *保存拦截方法的形参列表,判断有无参数
         */
        Parameter[] parameters;
        String methodName = StringUtil.getMethodName(method.toString());
        Object obj;
        /**
         * 如果不是切点,不织入通知
         */
        if(!points.contains(methodName)){
            obj = method.invoke(target,args);
            /**
             * 粗心在这里忘记返回了,导致逻辑继续执行
             */
            return  obj;
        }

        if(before != null){
                parameters = before.getParameters();
                before.invoke(aspect, parameters.length == 0?null:args);

        }
        try{
             obj = method.invoke(target,args);
        }catch (Exception e){
            if(afterThrowing != null){
                parameters = afterThrowing.getParameters();
                afterThrowing.invoke(aspect, parameters.length == 0?null:args);
            }
            throw e;
        }finally {
            if(after != null){
                parameters = after.getParameters();
                after.invoke(aspect, parameters.length == 0?null:args);
            }
        }
        if(afterReturn != null){
            parameters = afterReturn.getParameters();
            afterReturn.invoke(aspect, parameters.length == 0?null:args);
        }
        return obj;
    }

    public static void main(String[] args) {
        JDKProxy proxy = new JDKProxy();
        DemoService demoService = (DemoService)proxy.bind(new DemoServiceImpl(),new DemoAspect_jdk());
        demoService.genesis("ggp");
    }

}

jdk动态代理实现的是InvocationHandler接口,需要实现invoke方法。

这里的bind方法相当于初始化函数,里面传入的是需要被代理的真实对象和切面对象,初始化步骤中有一步是设置切点,因为如果是接口的话,切点可能不止一个,我们回忆一下ioc在实例化@Service标记的类时,如果它有接口,遍历所有接口,存入对应的值,所以我们在这里应该存入所有接口对应的方法,

public static HashSet setPoints(String name, HashSet points){
        /**
         * 把本身的作为切点方法添加进去
         */
        points.add(name);
        String className = StringUtil.getNameByMethod(name);
        String[] str = name.split("\\.");
        String  simpleName = str[str.length - 1];
        try {
            Class[] interfaces = Class.forName(className).getInterfaces();
            if(interfaces.length == 0){
                return points;
            }
            /**
             * 如果存在接口,就把接口相同的方法也添加到切点方法集合中
             */
            for(Class cl : interfaces){
                Method[] methods = cl.getMethods();
                for(Method method : methods){
                    if(StringUtil.getSimpleMethodName(method.toString()).equals(simpleName)){
                        points.add(StringUtil.getMethodName(method.toString()));
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return points;
    }

如过被@NBPointcut修饰的方法获取的信息的类没有接口,就要使用cglib动态代理内容和jdk相似,底层实现原理不同

package com.ggp.framework.proxy;

import com.ggp.demo.aop.DemoAspect_cglib;
import com.ggp.demo.service.impl.CglibTest;
import com.ggp.framework.annotation.aop.*;
import com.ggp.framework.common.util.StringUtil;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashSet;
import java.util.Set;

/**
 * @Author:ggp
 * @Date:2019/1/28 09 43
 * @Description:
 */
public class CglibProxy implements MethodInterceptor {
    /**
     * 切面配置
     */
    private Object aspect;
    /**
     * 保存切点
     */
    private Set points ;
    /**
     * 插入方法
     */
    private Method before;
    private Method after;
    private Method afterReturn;
    private Method afterThrowing;

    /**
     * 生成cglib代理对象
     * @param cl
     * @param aspect
     * @return
     */
    public Object getProxy(Class cl, Object aspect){
        /**
         * 初始化通知
         */
        this.aspect = aspect;
        points = new HashSet();
        Method[] methods = aspect.getClass().getMethods();
        for(Method method : methods){
            if(method.isAnnotationPresent(NBPointcut.class)){
                points.add(StringUtil.getMethodName(method.getAnnotation(NBPointcut.class).value()));
            } else if(method.isAnnotationPresent(NBBefore.class)){
                this.before = method;
            } else if(method.isAnnotationPresent(NBAfter.class)){
                this.after = method;
            } else if(method.isAnnotationPresent(NBAfterReturn.class)){
                this.afterReturn = method;
            } else if(method.isAnnotationPresent(NBAfterThrowing.class)){
                this.afterThrowing = method;
            } else {continue;}
        }
        /**
         * 创建增强类对象
         */
        Enhancer enhancer = new Enhancer();
        /**
         * 设置超类(让代理类成为目标类的子类)
         */
        enhancer.setSuperclass(cl);
        /**
         * 定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor接口
         */
        enhancer.setCallback(this);
        /**
         * 生成并返回代理对象
         */
        return enhancer.create();
    }

    /**
     * 代理逻辑方法
     * @param o 代理对象
     * @param method 方法
     * @param objects 方法参数
     * @param methodProxy 方法代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        /**
         *保存拦截方法的形参列表,判断有无参数
         */
        Parameter[] parameters;
        String methodName = StringUtil.getMethodName(method.toString());
        Object obj;
        /**
         * 如果不是切点,不织入通知
         */
        if(!points.contains(methodName)){
            obj = methodProxy.invokeSuper(o,objects);
            /**
             * 粗心在这里忘记返回了,导致逻辑继续执行
             */
            return  obj;
        }

        if(before != null){
            parameters = before.getParameters();
            before.invoke(aspect, parameters.length == 0?null:objects);

        }
        try{
            obj = methodProxy.invokeSuper(o,objects);
        }catch (Exception e){
            if(afterThrowing != null){
                parameters = afterThrowing.getParameters();
                afterThrowing.invoke(aspect, parameters.length == 0?null:objects);
            }
            throw e;
        }finally {
            if(after != null){
                parameters = after.getParameters();
                after.invoke(aspect, parameters.length == 0?null:objects);
            }
        }
        if(afterReturn != null){
            parameters = afterReturn.getParameters();
            afterReturn.invoke(aspect, parameters.length == 0?null:objects);
        }
        return obj;

    }

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        CglibTest cglibTest = (CglibTest) cglibProxy.getProxy(CglibTest.class,new DemoAspect_cglib());
        cglibTest.hi("ggp");

    }

}

cglib动态代理实现的是MethodInterceptor接口

猜你喜欢

转载自blog.csdn.net/qq_33543634/article/details/87076990