Spring学习-02 AOP

AOP(面向切面编程)

1、什么是AOP?

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,通过不修改源代码的方式,来进行功能的扩展(开闭原则的体现)

2、AOP的好处?

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

3、AOP的实现分类

按照 AOP 框架修改源代码的时机,可以将其分为两类:

  1. 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  2. 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

4、AOP和OOP

  1. OOP:纵向抽取
    • 提高重用性
    • 提高可扩展性
    • 多态
    • 基于类级别
  2. AOP:横向抽取
    • 提高可重用性
    • 提高可扩展和可维护性
    • 基于方法级别

4、AOP底层原理(重要)

代理模式(重点)

  • 什么是代理

    代理,就是你委托别人帮你办事,所以代理模式也有人称作委托模式的。

  • 代理模式的目的

    在不修改原有代码的条件下,对目标方法进行功能增强。

  • 代理模式的分类

    静态代理和动态代理

    静态代理:代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用,这样,当我们需要调用被代理类的方法时,可以通过调用代理类的方法来做到。

    动态代理:动态代理实现的目的和静态代理一样,都是对目标方法进行增强,而且让增强的动作和目标动作分开,达到解耦的目的。

  • 静态代理的实现示例

    1、定义接口,让被代理类实现接口

    public interface PersonService {
        void savePerson();
    }
    
    public class PersonServiceImpl  implements PersonService {
        @Override
        public void savePerson() {
            System.out.println("添加人");
        }
    }
    

    2、定义代理类,添加增强功能,并实现和被代理类相同的接口

    public class MyTransaction {
        public void beginTransaction(){
            System.out.println("开启事务 ");
        }
    }
    
    public class PersonServiceProxy implements PersonService {
        //目标类
        private PersonService personService;
    
        //增强类
        private MyTransaction transaction;
    
        public PersonServiceProxy(PersonService personService, MyTransaction transaction) {
            this.personService = personService;
            this.transaction = transaction;
        }
    
        @Override
        public void savePerson() {
            transaction.beginTransaction();
            personService.savePerson();
        }
    }
    
  • 静态代理的特点

    1. 由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
    2. 代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用。
  • 静态代理的缺点

    1、假设一个系统中有100个Service,则需要创建100个代理对象

    2、如果一个Service中有很多方法需要事务(增强动作),发现代理对象的方法中还是有很多重复的代码

    3、由第一点和第二点可以得出:静态代理的重用性不强

  • 动态代理

  • 1)AOP 底层使用了动态代理

​ 有两种情况下的动态代理

第一种,有接口情况 ————> JDK动态代理 :创建接口实现类的代理对象,然后增强类的方法

第二种,没接口情况 ————> CGLIB动态代理:创建当前类子类的代理对象,然后增强类的方法

  • 2)JDK动态代理
    • 1、**使用JDK动态代理,使用Proxy类里面的newProxyInstance静态方法创建代理对象 **
    • 2、Proxy类中newProxyInstance方法,该方法有三个参数,第一个参数是:类加载器第二个参数是:增强方法所在的类,这个实现的接口,支持多个接口第三个参数是:实现InvocationHandler接口的类。创建代理对象,写增强方法
  • 3)CGLIB动态代理:需要代理类实现MethodInterceptor接口

JDK动态代理示例
UserDaoImpl对象(被代理类对象)

package all.spring3.demo;
import all.spring3.dao.UserDao;

public class UserDaoImpl implements UserDao {
   @Override
   public int add(int a, int b) {
      System.out.println("add 执行...");
      return a + b;
   }

   @Override
   public void update(String id) {
      System.out.println("update 执行....");
      System.out.println(id);
   }
}

UserDaoProxy对象(代理类对象)

package all.spring3.demo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;

//创建代理对象代码
public class UserDaoProxy implements InvocationHandler {

   private Object obj;

   //创建的是谁的代理对象,就把谁传过来
   public UserDaoProxy(Object userDao) {
      this.obj = userDao;
   }


   //增强的逻辑
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //方法之前
      System.out.println("方法之前:" + method.getName() + "传递的参数" + Arrays.toString(args));

      Object res = null;

      if (method.getName().equals("add")) {
         //被增强的方法的执行
         res = method.invoke(obj, args);
         //方法之后
         System.out.println("方法之后:" + obj);
      }

      if (method.getName().equals("update")) {
         //被增强的方法的执行
         res = method.invoke(obj, args);
         //方法之后
         System.out.println("方法之后:" + obj);
      }

      return res;
   }
}

CGLIB动态代理示例

  • 导入依赖

    cglibcglib-nodep3.3.0
  • 定义一个被代理类

    public class TeacherService {
        public void addTeacher(){
            System.out.println("添加老师");
        }
    }
    
  • 定义增强类

    public class MyTransaction {
        public void beginTransaction(){
            System.out.println("开启事务 ");
        }
    }    
    
  • 定义一个代理类,实现MethodInterceptor接口

    public class MyMethodInterceptor implements MethodInterceptor {
        // 增强类
        private MyTransaction transaction;
    
        public MyMethodInterceptor(MyTransaction transaction) {
            this.transaction = transaction;
        }
    
        /**
         *
         * @param o  cglib生成的代理对象
         * @param method  被代理对象方法
         * @param objects   方法入参
         * @param methodProxy   代理方法
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            transaction.beginTransaction();
            Object object = methodProxy.invokeSuper(o, objects);
            return object;
        }
    }
    
  • 定义生成代理类的工具类

    public class CGProxy  {
        public <T> T createProxy(T t,MyTransaction obj){
            // 通过CGLIB动态代理获取代理对象的过程
            Enhancer enhancer = new Enhancer();
            // 设置enhancer对象的父类
            enhancer.setSuperclass(t.getClass());
            // 设置enhancer的回调对象
            enhancer.setCallback(new MyMethodInterceptor(obj));
            // 创建代理对象
            T proxy= (T)enhancer.create();
            return proxy;
        }
    }
    
  • 测试

    @Test
    public void test3(){
            CGProxy proxy=new CGProxy();
            // 代理类
            TeacherService teacherService=new TeacherService();
            // 增强类
            MyTransaction myTransaction = new MyTransaction();
    
            TeacherService t=  proxy.createProxy(teacherService,myTransaction);
            t.addTeacher();
    }
    
JDK动态代理和CGLib的区别
1)JDK和CGLib的区别
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)

2)Spring在选择用JDK还是CGLib的依据
当Bean实现接口时,Spring就会用JDK的动态代理
当Bean没有实现接口时,Spring使用CGLib来实现 
可以强制使用CGLib(在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)

3)JDK和CGLib的性能对比
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理
上一篇:什么是aop?


下一篇:使用aop扫描不到对应的包解决办法