05月29, 2018

Java 中的AOP代理

Java 中的AOP代理

aop 面向切面编程

在传统 OOP 编程里以对象为核心,整个软件系统由系列相互依赖的对象所组成,而这些对象将被抽象成一个一个的类,并允许使用类继承来管理类与类之间一般到特殊的关系。随着软件规模的增大,应用的逐渐升级,慢慢出现了一些 OOP 很难解决的问题。

比如我们在写CUDR业务时,同一操作也许会设计到对多个记录或者表的变更操作,此时我们需要对其经行事务控制,这样才能保证一个操作的原子性和一致性。此时我们需要在每个操作中不断的开启事务,提交事务,回滚事务。 事务

这样会有很多重复的代码,而且一旦开启回滚事务的方式反生改变时需要更改每一处的代码。这时如果使用AOP的思想就可以解决这类问题。

AOP思想就是把所有重复的代码放到一个地方(切面),然后在需要使用到这些代码的地方(切点)自动调用他们,而且原来的使用方式完全不变。 AOP

静态代理

静态代理实现中,一个委托类对应一个代理类,代理类在编译期间就已经确定。在上面说的事务提交问题中,这里使用静态代理提供AOP解决方案

例子中抽象了一个IAddDao接口代表着所有的添加操作,所有需要添加操作的实现子类UserDaoImpl,ProductDaoImpl可以通过继承IAddDao然后被TransactionManager所代理。这种方式实现很简单,但是很显然不太灵活,一个正常的DAO中往往包括增删该查四种或者更多,而且每个DAO的方法也不相同,这样的时候静态代理就满足不了了。

package us.codecraft.tinyioc.proxy.staticproxy;

/**
 * @author wpy
 * @date 2018/5/29 10:46
 */
public class StaticProxy {
    public static void main(String[] args) {
        TransactionManager userDaoProxy = new TransactionManager(new UserDaoImpl());
        TransactionManager productDaoProxy = new TransactionManager(new ProductDaoImpl());
        userDaoProxy.add("张三");
        productDaoProxy.add("巧克力");
    }

    interface IAddDao {
        boolean add(String name);
    }

    static class UserDaoImpl implements IAddDao {

        @Override
        public boolean add(String name) {
            System.out.println("添加"+name+"成功");
            return true;
        }
    }


    static class ProductDaoImpl implements IAddDao {

        @Override
        public boolean add(String name) {
            System.out.println("添加"+name+"成功");
            return true;
        }
    }


    static class  TransactionManager implements IAddDao {

        private IAddDao IUserDaoImpl;

        public TransactionManager(IAddDao IUserDaoImpl) {
            this.IUserDaoImpl = IUserDaoImpl;
        }

        @Override
        public boolean add(String name) {
            System.out.println("开启事务");
            boolean add;
            try {
                add = IUserDaoImpl.add(name);
            }catch (Exception e){
                System.out.println("回滚事务");
                return false;
            }
            System.out.println("提交事务");
            return add;
        }
    }
}

JDK动态代理

前面说静态代理无法解决复杂场景的问题就需要动态代理来解决。 动态代理即相对于静态代理在编译时就确定,动态代理是在运行时才确定代理对象。也就是在java运行时,生成java代码然后编译再加载。JDK原生就提供了一种基于接口方式的动态代理 现在在有这样一个需求,在一个DAO中有四个方法,除了get之外,其他所有的方法都需要事务。


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

/**
 * @author wpy
 * @date 2018/5/29 14:19
 */
public class JdkProxy {

    public static void main(String[] args) {
        UserDaoImpl userDaoImpl = new UserDaoImpl();

        TransactionInvocationHandler invocationHandler = new TransactionInvocationHandler(userDaoImpl);
        IUserDao proxy = (IUserDao)invocationHandler.getProxy();

        proxy.add("张三");
        proxy.delete("张三");
        proxy.update("张三");

        System.out.println(proxy.get());
    }

    interface IUserDao{
        boolean add(String name);
        boolean update(String name);
        boolean delete(String name);
        String get();
    }

    static class UserDaoImpl implements IUserDao{

        @Override
        public boolean add(String name) {
            System.out.println("添加"+name+"成功");
            return true;
        }

        @Override
        public boolean update(String name) {
            System.out.println("更新"+name+"成功");
            return true;
        }

        @Override
        public boolean delete(String name) {
            System.out.println("删除"+name+"失败");
            return false;
        }

        @Override
        public String get() {
            return "张三";
        }
    }

    static class TransactionInvocationHandler implements InvocationHandler{

        private Object proxy;

        public TransactionInvocationHandler(Object object) {
            this.proxy = object;
        }

        public Object getProxy() {
            Object instance = Proxy.newProxyInstance(proxy.getClass().getClassLoader(),proxy.getClass().getInterfaces(), this);
            return instance;
        }

        /**
         * 切面方法
         * @param proxy  代理对象
         * @param method 切点方法
         * @param args 方法的参数
         * @return 方法的返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if(method.getName().contains("get")){
                return method.invoke(this.proxy, args);
            }

            System.out.println("=======开始事务=======");
            //这里要使用 this.proxy 使用 proxy 会导致循环调用
            boolean invoke =(Boolean) method.invoke(this.proxy, args);
            if(invoke){
                System.out.println("=======提交事务=======\n");
            }else{
                System.out.println("=======回滚事务=======\n");
            }
            return invoke;
        }
    }
}

这里涉及到了一个接口InvocationHandler和一个类Proxy

InvocationHandler代表着切面,它的invoke方法即代表着切面要执行的代码,我们这里指的开启,提交或者回滚事务。

Proxy.newProxyInstance有三个参数,第一个是类加载器,第二个是需要代理的接口数组,这些接口中所有的方法都会被代理,而且这个方法返回的代理对象会实现这里所有的接口。第三个参数就是切面。

JDK提供的动态代理相对静态代理无疑灵活了不少,但是任然有一个问题,就是所有的代理都是基于接口实现的,如果UserDaoImpl没有实现接口时这个方法

cglib 动态代理

使用cglib生成代理对象时不需要提供接口,它是创建一个子类,这个子类继承自被代理的类。cglib的使用方式和JDK提供的类似

Enhancer作用和Proxy类似,用于生成代理的类对象。 MethodInterceptorInvocationHandler一样的作用

package us.codecraft.tinyioc.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author wpy
 * @date 2018/5/29 15:07
 */
public class CglibProxy {
    public static void main(String[] args) {
        UserDaoImpl userDaoImpl = new UserDaoImpl();
        UserDaoImpl proxy = (UserDaoImpl) new TransactionInvocationHandler(userDaoImpl).getProxy();

        proxy.add("张三");
        proxy.delete("张三");
        proxy.update("张三");

        System.out.println(proxy.get());
    }

    static class UserDaoImpl{

        public boolean add(String name) {
            System.out.println("添加"+name+"成功");
            return true;
        }

        public boolean update(String name) {
            System.out.println("更新"+name+"成功");
            return true;
        }

        public boolean delete(String name) {
            System.out.println("删除"+name+"失败");
            return false;
        }

        public String get() {
            return "张三";
        }
    }

    static class TransactionInvocationHandler implements MethodInterceptor {

        private Object proxy;

        public Object getProxy() {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(proxy.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }

        public TransactionInvocationHandler(Object object) {
            this.proxy = object;
        }

        /**
         * 切面方法
         * @param proxy 代理对象
         * @param method 切点方法
         * @param args 方法的参数
         * @param methodProxy 代理的方法对象
         * @return 方法的返回值
         */
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            if(method.getName().contains("get")){
                return method.invoke(this.proxy, args);
            }

            System.out.println("=======开始事务=======");
            //这里要使用 this.proxy 使用 proxy 会导致循环调用
            boolean invoke =(Boolean) method.invoke(this.proxy, args);
            if(invoke){
                System.out.println("=======提交事务=======\n");
            }else{
                System.out.println("=======回滚事务=======\n");
            }
            return invoke;
        }
    }
}

Spring 中的AOP

Spring的AOP使用的是Aspect风格的AOP并没有使用Aspect去静态生成代理,如果注入的类型是接口类型时,Spring会默认使用JDK的代理来生成代理,如果注入的是类时,Spring会使用cglib来生成代理对象。

本文链接:https://www.qiangshuidiyu.xin/post/java-aop.html

-- EOF --

Comments