• Spring AOP原理之动态代理


    一、什么是代理?

    指为一个目标对象提供一个代理对象, 并由代理对象控制对目标对象的引用. 使用代理对象, 是为了在不修改目标对象的基础上,增强目标对象的业务逻辑.

    在这里插入图片描述

    1、静态代理

    静态代理的特点是, 为每一个业务增强都提供一个代理类, 由代理类来创建代理对象. 下面我们通过静态代理来实现对转账业务进行身份验证.

    (1) 转账业务

    
    public interface IAccountService {
        //主业务逻辑: 转账
        void transfer();
    }
    
    public class AccountServiceImpl implements IAccountService {
        @Override
        public void transfer() {
            System.out.println("调用dao层,完成转账主业务.");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2) 代理类

    public class AccountProxy implements IAccountService {
        //目标对象
        private IAccountService target;
    
        public AccountProxy(IAccountService target) {
            this.target = target;
        }
    
        /**
         * 代理方法,实现对目标方法的功能增强
         */
        @Override
        public void transfer() {
            before();
            target.transfer();
        }
    
        /**
         * 前置增强
         */
        private void before() {
            System.out.println("对转账人身份进行验证.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    (3) 测试

    public class Client {
        public static void main(String[] args) {
            //创建目标对象
            IAccountService target = new AccountServiceImpl();
            //创建代理对象
            AccountProxy proxy = new AccountProxy(target);
            proxy.transfer();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    结果: 
    对转账人身份进行验证.
    调用dao层,完成转账主业务.
    
    • 1
    • 2
    • 3

    2、动态代理

    静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象, 而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成.

    2.1、JDK动态代理

    JDK动态代理是使用 java.lang.reflect 包下的代理类来实现. JDK动态代理动态代理必须要有接口.

    (1) 转账业务

    public interface IAccountService {
        //主业务逻辑: 转账
        void transfer();
    }
    public class AccountServiceImpl implements IAccountService {
        @Override
        public void transfer() {
            System.out.println("调用dao层,完成转账主业务.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2) 增强

    因为这里没有配置切入点, 称为切面会有点奇怪, 所以称为增强.

    public class AccountAdvice implements InvocationHandler {
        //目标对象
        private IAccountService target;
    
        public AccountAdvice(IAccountService target) {
            this.target = target;
        }
    
        /**
         * 代理方法, 每次调用目标方法时都会进到这里
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            return method.invoke(target, args);
        }
    
        /**
         * 前置增强
         */
        private void before() {
            System.out.println("对转账人身份进行验证.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    (3) 测试

    public class Client {
        public static void main(String[] args) {
            //创建目标对象
            IAccountService target = new AccountServiceImpl();
            //创建代理对象
            IAccountService proxy = (IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new AccountAdvice(target)
            );
            proxy.transfer();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    结果: 
    对转账人身份进行验证.
    调用dao层,完成转账主业务.
    
    • 1
    • 2
    • 3

    2.2、 CGLIB动态代理

    JDK动态代理必须要有接口, 但如果要代理一个没有接口的类该怎么办呢? 这时我们可以使用CGLIB动态代理. CGLIB动态代理的原理是生成目标类的子类, 这个子类对象就是代理对象, 代理对象是被增强过的.

    注意: 不管有没有接口都可以使用CGLIB动态代理, 而不是只有在无接口的情况下才能使用.

    (1) 转账业务

    public class AccountService {
        public void transfer() {
            System.out.println("调用dao层,完成转账主业务.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2) 增强

    因为这里没有配置切入点, 称为切面会有点奇怪, 所以称为增强.

    public class AccountAdvice implements MethodInterceptor {
        /**
         * 代理方法, 每次调用目标方法时都会进到这里
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            before();
            return methodProxy.invokeSuper(obj, args);
            //        return method.invoke(obj, args);  这种也行
        }
    
        /**
         * 前置增强
         */
        private void before() {
            System.out.println("对转账人身份进行验证.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (3) 测试

    public class Client {
        public static void main(String[] args) {
            //创建目标对象
            AccountService target = new AccountService();
            //
            //创建代理对象
            AccountService proxy = (AccountService) Enhancer.create(target.getClass(),
                    new AccountAdvice());
            proxy.transfer();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    结果: 
    对转账人身份进行验证.
    调用dao层,完成转账主业务.
    
    • 1
    • 2
    • 3

    二、模拟Spring AOP场景

    了解了动态代理后, 我们就可以自己来实现Spring AOP功能了, 所以下面我们来模拟下Spring AOP场景.

    (1) 转账业务

    public interface IAccountService {
        //主业务逻辑: 转账
        void transfer();
    }
    public class AccountServiceImpl implements IAccountService {
        @Override
        public void transfer() {
            System.out.println("调用dao层,完成转账主业务.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2) 切面抽象类

    定义一个切面抽象类, 该类使用了模板方法的设计模式, 为开始, 结束, 异常, 前置增强, 后置增强提供了默认实现, 当我们定义切面类时只需要按需重写它们就行. isIntercept() 方法用来判断切入点是否正确, 切面类需要重写这个方法.

    
    public abstract class BaseAspect implements MethodInterceptor {
        private static final Logger logger = LoggerFactory.getLogger(BaseAspect.class);
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object result = null;
    
            begin();
            try {
                if (isIntercept(method, args)) {
                    before();
                    result = methodProxy.invokeSuper(obj, args);
                    after();
                } else {
                    result = methodProxy.invokeSuper(obj,args);
                }
            } catch (Exception e) {
                logger.error("proxy failure", e);
                error(e);
                throw e;
            } finally {
                end();
            }
            return result;
        }
    
        /**
         * 开始增强
         */
        public void begin() {
        }
    
        /**
         * 切入点判断
         */
        public boolean isIntercept(Method method, Object[] args) throws Throwable {
            return true;
        }
    
        /**
         * 前置增强
         */
        public void before() throws Throwable {
        }
    
        /**
         * 后置增强
         */
        public void after() throws Throwable {
        }
    
        /**
         * 异常增强
         */
        public void error(Throwable e) {
        }
    
        /**
         * 最终增强
         */
        public void end() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    (3) 切面类

    创建一个切面类, 类中配置切入点和增强.

    public class AccountAspect extends BaseAspect {
    
        /**
         * 切入点
         */
        public boolean isIntercept(Method method, Object[] args) throws Throwable {
            return method.getName().equals("transfer");
        }
    
        /**
         * 前置增强
         */
        public void before() throws Throwable {
            System.out.println("对转账人身份进行验证.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (4) 代理工厂类

    定义一个工厂类来创建代理, 其实不创建这个类也行, 但为了模仿Spring还是创建了. @SuppressWarnings是为了抑制警告, 就是编译器上面的黄线.

    public class ProxyFactory {
    
        @SuppressWarnings("unchecked")
        public static  T createProxy(final Class targetClass, final MethodInterceptor methodInterceptor) {
            return (T) Enhancer.create(targetClass,methodInterceptor);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (5) 测试

    public class Client {
        public static void main(String[] args) {
            //创建目标对象
            IAccountService target = new AccountServiceImpl();
            //切面
            BaseAspect accountAspect = new AccountAspect();
            //创建代理对象
            IAccountService proxy = (IAccountService) ProxyFactory.createProxy(target.getClass(), accountAspect);
            proxy.transfer();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    结果:
    对转账人身份进行验证.
    调用dao层,完成转账主业务.
    
    • 1
    • 2
    • 3
  • 相关阅读:
    【Mysql】表的增删查改
    20分钟---Vue2->Vue3
    sourcetree这是一个无效的路径/url, mac版本
    python设计模式11:观察者模式
    Java 版 spring cloud 工程系统管理 +二次开发 工程项目管理系统源码
    cognex Insight相机与机器人配合标定的方法(转载不易)
    GitHub 官宣:弃用 trending 热榜,开发者炸锅了
    K8S+KubeSphere+DevOps
    Java多关键词分级搜索实现
    gRPC 协议缓冲区
  • 原文地址:https://blog.csdn.net/GoodburghCottage/article/details/126931618