zoukankan      html  css  js  c++  java
  • 【Spring 从0开始】什么是 AOP?底层原理是?

    一、什么是 AOP

    AOP 就是面向切面编程,是 OOP(面向对象编程)的延续。

    利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序可用性,同时也提高了开发效率。

    通俗一点说,不用修改原代码,可以给原代码增加新的功能。

    二、AOP 底层原理

    AOP 底层原理是使用动态代理。

    那代理是什么?有动态代理,那是不是还有静态代理?

    1. 什么是代理?

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

    比如目标对象 A,代理对象是 B。

    • 那么现在 B 对 A 进行引用,可以实现 A 有的功能。
    • 另外,B 还可以在自身进行一些新功能,最终不需要修改目标对象 A 。

    而代理分为静态代理动态代理区别是:

    • 静态代理有真实的代理类存在,就是我们会代码中创建一个代理类,并在代理类的方法中调用目标对象的方法,以此来完成代理的工作。
    • 动态代理的代理类没有在代码中创建一个代理类,而是在运行时在JVM里面创建代理对象。

    2. 什么是静态代理

    静态代理是有实实在在的代理类存在,并且和目标类实现相同的接口。

    比如,有一个转账业务,现在希望给它增加功能,使在转账之前确认转账人身份,以及转账之后通知收款人。

    (1) 接口 AccountServiceDao :

    package com.pingguo.spring5.dao;
    
    public interface AccountServiceDao {
        // 主业务逻辑,转账
        void transfer();
    }
    
    

    (2) 接口 AccountServiceDao 的实现类:

    package com.pingguo.spring5.dao;
    
    public class AccountServiceImpl implements AccountServiceDao {
        @Override
        public void transfer() {
            System.out.println("调用dao层,完成转账主业务.");
        }
    }
    
    

    (3) 代理类 AccountProxy :

    package com.pingguo.spring5.proxy;
    
    import com.pingguo.spring5.dao.AccountServiceDao;
    
    public class AccountProxy implements AccountServiceDao {
        // 目标对象
        private AccountServiceDao target;
    
        public AccountProxy(AccountServiceDao target) {
            this.target = target;
        }
    
        /**
         *  代理方法,实现对目标方法的增强
         */
        @Override
        public void transfer() {
            before();
            target.transfer();
            after();
        }
    
        /**
         *  增强的功能,转账之前使用
         */
        private void before() {
            System.out.println("对转账人身份进行验证.");
        }
    
        /**
         *  增强的功能,转账之后使用
         */
        private void after() {
            System.out.println("转账完成,已通知收款人.");
        }
    
    }
    
    

    在代理类中:

    • 添加了添加了目标对象,并且有参构造方法里需要传入目标对象。
    • 代理方法里,调用了目标对象里的转账方法 target.transfer()。
    • before() 和 after() 则是 2个增强的方法,分别作用于 target.transfer() 的前面和后面。

    (4) 运行测试
    新建一个测试方法,运行看下结果:

        @Test
        public void testProxy() {
            // 创建目标对象
            AccountServiceDao target = new AccountServiceImpl();
    
            // 创建代理对象
            AccountProxy proxy = new AccountProxy(target);
            proxy.transfer();
        }
    
    • 这里先创建了目标对象
    • 再创建代理对象,并且把目标对象传入
    • 最后调用代理对象里的,被增强过的方法 transfer()。

    结果:

    对转账人身份进行验证.
    调用dao层,完成转账主业务.
    转账完成,已通知收款人.
    
    Process finished with exit code 0
    

    优点

    • 效率高,因为所有的类都是已经编写完成的,使用的时候只需要取得代理对象并且执行即可。
    • 同时也可以实现对目标对象中指定的方法进行增强。

    缺点

    • 与目标类实现相同的接口代码,冗余。
    • 如果接口发生改变,代理类中的方法也要修改。
    • 代理类服务于一种类型的对象,如果要服务多类型的对象,那么要为每种类型的对象都生成代理类。

    3. 什么是动态代理

    与静态代理的硬编码方式相比,动态代理支持运行时动态生成代理对象这种方式。换句话说,动态代理并不存在代理类,代理对象直接由代理生成工具动态生成。

    优点

    • 用很少的代码对一个类的所有方法实现一样的增强效果。
    • 在编码时,代理逻辑与业务逻辑互相独立,各不影响,减少侵入,降低耦合。

    缺点

    • 相对于静态代理,它不能增强其中的某一个方法。

    对于动态代理,针对于是否存在接口的情况下,又分为 2 种:

    • 有接口的情况下,使用 JDK 动态代理。
    • 无接口的情况下,使用 CGLIB 动态代理。

    使用 JDK 动态代理

    使用 JDK 动态代理,创建的是接口实现类的代理对象,以此来实现功能增强。

    现在不需要上面创建过的实际代理类了 。

    接口,为了后面的一些知识点的说明,里面加个参数,转账的金额:

    package com.pingguo.spring5.dao;
    
    public interface AccountServiceDao {
        // 主业务逻辑,转账
        void transfer(int amount);
    }
    

    实现类:

    package com.pingguo.spring5.dao;
    
    public class AccountServiceImpl implements AccountServiceDao {
        @Override
        public void transfer(int amount) {
            System.out.println("调用dao层,完成转账主业务.金额:" + amount);
        }
    }
    

    在测试方法里,直接使用动态代理:

        @Test
        public void testDynamicProxy() {
            // 创建目标对象
            AccountServiceDao target = new AccountServiceImpl();
    
            // 创建代理对象
            AccountServiceDao proxy = (AccountServiceDao) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),  // 目标类使用的类加载器
                    target.getClass().getInterfaces(),  // 目标类实现的接口
                    new InvocationHandler() {  // 调用处理器
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("对转账人身份进行验证.");
                            Object res =  method.invoke(target, args);
                            System.out.println("转账完成,已通知收款人.");
                            return res;
                        }
                    }
            );
            // 让代理工作
            proxy.transfer(10000);
        }
    

    运行结果:

    对转账人身份进行验证.
    调用dao层,完成转账主业务.金额:10000
    转账完成,已通知收款人.
    
    Process finished with exit code 0
    

    动态代理的过程:

    • 创建处理器 InvocationHandler实例。
    • 在调用目标对象时,会调用代理对象。
    • 代理对象去请求目标对象。invoke 方法就是调用目标对象的方法生成代理对象的过程。
    • 同时,在 invoke 方法中进行功能增强。

    对于 invoke 中的 3 个参数,分别是:

    • Object proxy:代理对象,一般不会使用。
    • Method method:外面的代理对象调用的方法引用,这里引用的就是 transfer()
    • Object[] args:外面的代理对象调用的方法里面的参数,这里就是参数 amount。

    使用 CGLIB 动态代理

    CGLIB动态代理的原理是生成目标类的子类,这个子类对象就是代理对象,代理对象是被增强过的。

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

    示例就暂时不放了,因为我本地环境问题,有个报错始终未解决,后续再说,不影响继续学习 spring。

    --不要用肉体的勤奋,去掩盖思考的懒惰--
  • 相关阅读:
    laravel 5.5 仓库模式 文件之间接口与实现操作
    php 无线分类 根据子级找父级
    php 无限极分类,根据父级 找子级
    laravel5.4 中 dd和dump的区别。
    laravel hash密码生成和密码验证
    oracle建表详细信息
    关于组件的认识
    java的Thread Dump诊断工具
    weblogic连接池
    详解Oracle数据字典
  • 原文地址:https://www.cnblogs.com/pingguo-softwaretesting/p/15087832.html
Copyright © 2011-2022 走看看