美文网首页我爱编程
Spring的AOP原理以及简单实现可配置的AOP

Spring的AOP原理以及简单实现可配置的AOP

作者: 纳米君 | 来源:发表于2018-05-22 23:35 被阅读49次

    首先,什么是AOP?
    AOP(Aspect Oriented Programming面向切面编程),它是对OOP(面向对象编程)的一种补充。在OOP中,我们把事物纵向抽象成一个个对象,而往往需要对这些对象进行一些横向操作(比如事务管理、日志记录、权限等)时,就需要AOP了。

    Spring的AOP是基于JDK的动态代理cglib动态代理实现的。JDK的动态代理是代理那些实现接口的类,而cglib动态代理是代理那些继承父类的类。

    所谓的切面aspect,是由切入点pointcut和通知advice组成。pointcut是指需要拦截哪些类的哪些方法,advice是指在这些方法执行前后可以进行额外的操作。

    先看看JDK的动态代理:
    因为是代理实现接口的类,所以先定义一个接口和实现类:

    public interface Hello {
    
        String sayHello(String name);
    }
    
    public class HelloImpl implements Hello {
    
        @Override
        public String sayHello(String name) {
            return name + "2333";
        }
    }
    

    然后定义一个类,需要实现接口InvocationHandler,而且持有代理的目标类的引用target,如下:

    public class HelloProxy implements InvocationHandler {
    
        private Object target;
    
        public HelloProxy(Object target) {
            this.target = target;
        }
    
        public Object getProxyBean() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
            Object value = method.invoke(target, args);
            return value;
        }
    }
    

    最后写个测试类测试一下:

    public class DynamicProxyTest {
    
        public static void main(String[] args) {
            Hello proxy = (Hello) new HelloProxy(new HelloImpl()).getProxyBean();
            System.out.println(proxy.sayHello("Fuck"));
        }
    }
    
    打印结果:Fuck2333
    

    Java会动态生成一个代理类com.sun.proxy.$Proxy0,通过反编译,可以看到$Proxy0实现了Hello接口,持有HelloProxy的实例对象引用handler,而且会通过反射获取接口Hello的方法和参数,所以当执行proxy.sayHello("Fuck")这行代码时,实际上调用的是$Proxy0类中的sayHello方法,该方法代码如下:

    public final String sayHello(String string) {
            try {
                return (String)this.handler.invoke((Object)this, "sayHello", new Object[]{string});
            }
            catch (Error | RuntimeException v0) {
                throw v0;
            }
            catch (Throwable var2_2) {
                throw new UndeclaredThrowableException(var2_2);
            }
        }
    

    所以最终调用的是HelloProxy中的invoke方法。

    了解了动态代理之后,我们就可以实现一个简单的AOP工具了。

    1. 我们得明确切入点和通知,切入点就是测试类中bean调用的方法,通知如下:
    public interface Advice {
    
        void beforeMethod(Method method);
    
        void afterMethod(Method method);
    
    }
    
    public class MyAdvice implements Advice {
    
        @Override
        public void beforeMethod(Method method) {
            System.out.println("beforeMethod");
        }
    
        @Override
        public void afterMethod(Method method) {
            System.out.println("afterMethod");
        }
    }
    
    1. 创建一个代理工厂类,需持有代理目标类和通知Advice引用,对外提供getProxyBean方法获取代理类实例,如下:
    public class ProxyBeanFactory implements InvocationHandler {
    
        private Object target;
        private Advice advice;
    
        public Object getProxyBean() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //invoke方法执行前
            advice.beforeMethod(method);
            Object value = method.invoke(target, args);
            //invoke方法执行后
            advice.afterMethod(method);
            return value;
        }
    
        public Object getTarget() {
            return target;
        }
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        public Advice getAdvice() {
            return advice;
        }
    
        public void setAdvice(Advice advice) {
            this.advice = advice;
        }
    }
    
    1. 创建一个BeanFactory类,对外提供getBean方法获取bean实例,配置的bean不一定是代理工厂类。
    public class BeanFactory {
    
        static Properties props = new Properties();
    
        public BeanFactory(InputStream is) {
            try {
                props.load(is);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public Object getBean(String name) {
            Object bean = null;
            try {
                bean = Class.forName(props.get(name).toString()).newInstance();
                if (bean instanceof ProxyBeanFactory) {
                    // 是代理工厂类
                    ProxyBeanFactory proxyBeanFactory = (ProxyBeanFactory) bean;
                    Object target = Class.forName(props.get(name + ".target").toString()).newInstance();
                    Advice advice = (Advice) Class.forName(props.get(name + ".advice").toString()).newInstance();
                    proxyBeanFactory.setTarget(target);
                    proxyBeanFactory.setAdvice(advice);
                    // 获取代理实例
                    bean = proxyBeanFactory.getProxyBean();
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            return bean;
        }
    
    }
    
    1. 创建一个config.properties配置文件,配置Bean类、代理目标类和通知类。可以配置proxy来决定是否走代理。
    proxy=cn.tl.aop.ProxyBeanFactory
    #proxy=java.util.ArrayList
    proxy.target=java.util.ArrayList
    proxy.advice=cn.tl.aop.MyAdvice
    

    测试类如下:

    public class AopTest {
    
        public static void main(String[] args) {
            InputStream is = AopTest.class.getResourceAsStream("config.properties");
            BeanFactory beanFactory = new BeanFactory(is);
            Object bean = beanFactory.getBean("proxy");
            List list = (List) bean;
            list.add(233);
        }
    }
    

    打印结果:

    beforeMethod
    afterMethod
    

    说明通知Advice起作用了,当然这是一种很简略的实现方式。
    以上。

    相关文章

      网友评论

        本文标题:Spring的AOP原理以及简单实现可配置的AOP

        本文链接:https://www.haomeiwen.com/subject/tckwdftx.html