Spring AOP编程

Spring AOP编程

第一章 静态代理设计模式

1.1 为什么需要代理设计模式

在JavaEE分层开发中,最为重要的是Service层。

  • Service层中包含了哪些代码?
    1. 核心功能:业务运算+DAO调用
    2. 额外功能:不属于业务,可有可无,代码量很小,如:事务、日志、性能等
  • 额外功能书写在Service层中的弊端:
    • 额外功能是可有可无的,书写在Service层中,如需改动则需要修改源码,这会很麻烦。

1.2 代理设计模式

  • 概念:通过代理类,为原始类(目标类)增加额外的功能。

    • 好处:利于原始类(目标类)的维护。
  • 名词解释:

    • 目标类(原始类):指的是 业务类(核心功能 --> 业务运算、DAO调用);

    • 目标方法(原始方法):目标类(原始类)中的方法就是 目标方法(原始方法);

    • 额外功能(附加功能):主要以日志、事务、性能为代表。

1.3 代理开发的核心要素

代理类 = 目标类(原始类)+ 额外功能 + 实现目标类(原始类)相同的接口

1.4 编码

  • 创建原始类(目标类)实现核心功能:

    public class UserServiceImpl implements UserService {
        private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
        @Override
        public void register(User user) {
            userDao.save(user);
        }
    
        @Override
        public void login(String name, String password) {
            userDao.queryUserByUsernameAndPassword(name,password);
        }
    }
    
  • 代理类:

    //实现目标类相同的接口
    public class UserServiceProxy implements UserService{
    
        //目标类
        private UserService userService = new UserServiceImpl();
        @Override
        public void register(User user) {
            //额外功能
            System.out.println("--------log-------");
            //目标类的核心功能
            userService.register(user);
    
        }
    
        @Override
        public void login(String name, String password) {
            //额外功能
            System.out.println("--------log-------");
            //目标类的核心功能
            userService.login(name,password);
    
        }
    }
    

1.5 静态代理的问题

  1. 静态代理的代码量过大,不利于项目管理。
  2. 额外功能的维护性差

第二章 Spring的动态代理开发

  • 概念:通过代理类,为原始类(目标类)增加额外的功能。

    • 好处:利于原始类(目标类)的维护。
  • 搭建开发环境:

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-aopartifactId>
        <version>5.2.9.RELEASEversion>
    dependency>
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.9.5version>
    dependency>
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjrtartifactId>
        <version>1.9.6version>
    dependency>
    

2.1 Spring动态代理的开发步骤

  1. 创建原始类对象(目标类对象)

    public class UserServiceImpl implements UserService {
        private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
        @Override
        public void register(User user) {
            userDao.save(user);
        }
    
        @Override
        public void login(String name, String password) {
            userDao.queryUserByUsernameAndPassword(name,password);
        }
    }
    
    <bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
    
  2. 额外功能

    • 实现MethodBeforeAdvice接口,额外的功能书写在接口的实现中,会在原始方法实现之前运行额外功能。
    public class Before implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("--------log--------");
        }
    }
    
    <bean class="com.itheima.basic.Before" id="before">bean>
    
  3. 定义切入点

    • 切入点:额外功能加入的位置。

    • 目的:程序员根据自己的需要,决定额外功能加入给哪个原始方法。

      
      <aop:config>
          <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
      aop:config>
      
  4. 组装(2、3步骤)

    组装:把 切入点 和 额外功能 进行整合。

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="pt1">aop:advisor>
    aop:config>
    
  5. 调用

    目的:获得Spring工厂创建的动态代理对象,并进行调用。

    ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext3.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.login("aaa","sss");
    //--------log--------
    //query User name = aaa,password = sss
    

    注意:

    1. Spring工厂通过原始对象的id值获得的是代理对象;
    2. 获得代理对象可以通过声明的接口类型,进行对象的存储。

2.2 动态代理细节分析

  1. Spring创建的动态代理类在哪里?

    • Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等待程序结束后,会和JVM一起消失。
  2. 什么叫动态字节码技术?

    • 通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象。当虚拟机关闭,动态字节码即消失。
  3. 动态代理技术的好处:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理类文件数量过多,影响项目管理的问题。

  4. 动态代理编程会简化代理的开发。

    • 在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定目标(原始)对象即可。
  5. 动态代理的额外功能的可维护性大大增强。

第三章 Spring动态代理的详解

3.1 额外功能的详解

  • MethodBeforeAdvice接口的分析:
public class Before implements MethodBeforeAdvice {
    /**
     作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中

     方法的参数:
     method:额外功能所增加给的那个原始方法。
     objects:额外功能所增加给的那个原始方法参数数组。
     o:代表原始对象。
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("--------log--------");
    }
}
  • before方法的三个参数如何使用?
    • before方法的参数会根据需要使用。(基本不会使用)
  • MethodInterceptor接口(方法拦截)的分析:
public class MyInterceptor implements MethodInterceptor {
    /*
    invoke方法的作用:额外功能书写在invoke方法中,可以执行在原始方法之前、之后、或者环绕都可以

    参数:
    MethodInvocation:额外功能要增加的那个原始方法。
    - 使用methodInvocation.proceed()使原始方法运行.

    返回值:原始方法的返回值
    - methodInvocation.proceed()的返回值就是原始方法的返回值

     */

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object object = null;
        try{
            System.out.println("前置方法");
            object = methodInvocation.proceed();
            System.out.println("后置方法");

        }catch (Exception e){
            System.out.println("原始方法抛出异常时需要执行的方法");
            e.printStackTrace();
        }

        return object;
    }
}
  • MethodInterceptor影响原始方法的返回值:
    1. 原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值。
    2. MethodInterceptor若想影响原始方法的返回值,不要直接返回原始方法的返回值即可。

3.2 切入点详解

切入点决定了额外功能加入的位置(方法)。

<aop:pointcut id="pt1" expression="execution(* *(..))"/>

3.2.1 切入点表达式
* *(..) --> 所有方法
含义:
* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型都行)
  • 定义login方法作为切入点:

    <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.login(String,String))"/>
    
    注意:
    1. 对于非java.lang包下的类,必须要写全限定类名;
    2. ..是可以和具体的参数类型联用的。
    
  • 类切入点表达式:

    • 指定特定类作为额外功能的切入点,自然这个类中的所有方法,都会加上对应的额外方法。
    语法1:定义com.itheima.basic包下的UserServiceImpl这个类的所有方法的切入点:
    <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.UserServiceImpl.*(..))"/>
    
    语法2:定义任意包下的UserServiceImpl类的所有方法的切入点:
    <aop:pointcut id="pt1" expression="execution(* *..UserServiceImpl.*(..))"/>
    注意:切入点表达式中要用*..来表示多级包
    
  • 包切入点表达式(更具实战价值):

    • 指定特定包下的所有类的所有方法,都加上对应的额外方法:
    切入点包中的所有类,必须在basic包中,不能在basic包的子包中:
    <aop:pointcut id="pt1" expression="execution(* com.itheima.basic.*.*(..))"/>
        
    切入点当前包及其子包都生效:
    <aop:pointcut id="pt1" expression="execution(* com.itheima.baisc..*.*(..))"/>
    
3.2.2 切入点函数

切入点函数:用于执行切入点表达式。

  • execution:最为重要的切入点函数,功能最完整;

    • 可以执行:方法切入点表达式、类切入点表达式、包切入点表达式。
    • 弊端:execution在执行切入点表达式时,书写麻烦。
  • args:主要用于函数或者方法参数的匹配;

    • 比如:方法参数必须得是2个字符串类型的参数:

      <aop:pointcut id="pt1" expression="args(String,String)"/>
      
  • within:主要用于进行类或者包切入点表达式的匹配;

    • 比如:UserServiceImpl这个类的所有方法:

      <aop:pointcut id="pt1" expression="within(*..UserServiceImpl)"/>
      
    • 比如指定basic包下的所有方法:

      <aop:pointcut id="pt1" expression="within(com.itheima.basic..*)"/>
      
  • @annotation:为具有特殊注解的方法加入额外功能;

    • 创建自定义注解:

      package com.itheima.basic;
      
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface Log {
      }
      
    • 在要增加额外功能的方法上加上该注解。

    • 在切入点函数中指定@annotation的切入点表达式:

      <aop:pointcut id="pt1" expression="@annotation(com.itheima.basic.Log)"/>
      
  • 切入点函数的逻辑运算:整合多个切入点函数一起工作,进而完成更为复杂的需求。

    • and与操作

      案例:login方法 同时要求有两个字符串参数
      execution(* login(String,String))
      
      使用and与 -->
      execution(* login(..)) and args(String,String)
      

      注意:与操作不能用于同类型的切入点函数。

    • or或操作

      案例:register方法 与 login方法 作为切入点
      execution(* login(..)) or execution(* register(..))
      

第四章 AOP编程

4.1 AOP编程的概念

AOP(Aspect Oriented Programing),面向切面编程:以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。切面 = 切入点 + 额外功能。

AOP的概念:本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。

好处:利于原始类的维护。

注意:AOP不能取代OOP,只是对OOP编程进行有利的补充。

4.2 AOP编程的开发步骤

  1. 原始对象;
  2. 额外功能(实现MethodInterceptor接口,重写invoke方法);
  3. 切入点;
  4. 组装切面。

4.3 切面的名词解释

切面:是由切入点+额外功能组成的。

第五章:AOP的底层实现原理

5.1 核心问题

  • AOP如何创建动态代理类(动态字节码技术)?
  • Spring工厂是如何加工创建代理对象的?

5.2 动态代理类的创建

5.2.1 JDK的动态代理

JDK的动态代理是基于接口创建动态代理对象的,当原始类实现了某接口,那么就可以使用 JDK的动态代理创建动态代理对象:

public class TestJDKProxy {

    public static void main(String[] args) {
        //创建原始对象
        UserService userService = new UserServiceImpl();
        //JDK创建动态代理对象
       UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
            	//参数1:任意借用一个类加载器,使得代理类加载进JVM
                TestJDKProxy.class.getClassLoader()
            	//参数2:原始对象所实现的接口
                , userService.getClass().getInterfaces()
           	 	//参数3:额外功能的方法
                , new InvocationHandler() 
                {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //参数1:Object proxy,代表代理对象,基本不用
                        //参数2:Method method,代表原始方法
                        //参数3: Object[] args,代表原始方法的参数
                        //返回值:Object,原始方法的返回值

                        //增加额外功能
                        System.out.println("-------log--------");
                        //实现原始对象的方法
                        Object ret = method.invoke(userService, args);
                        return ret;
                    }
                });
        userServiceProxy.login("suns","123456");
    }
}
5.2.2 cglib的动态代理

当要增加额外功能的原始类没有实现任何接口,那就可以使用cglib的动态代理(基于子类)创建动态代理对象。

  • cglib创建动态代理的原理:父子继承关系创建动态代理对象,原始类作为父类,代理类作为子类,这样既可以2者保证方法一致,也可以在代理类中提供新的实现。(额外功能+原始方法)

  • 代码实现:

    • 首先创建一个没有实现任何接口的原始类:
    public class UserService  {
        private UserDao userDao =(UserDao) BeanFactory.getBean("userDao");
    
    
        public void register(User user) {
            userDao.save(user);
        }
    
    
        public void login(String name, String password) {
            userDao.queryUserByUsernameAndPassword(name,password);
        }
    }
    
    • 在测试类中使用cglib创建代理类对象,并测试:
    public class TestCglibProxy {
    
        public static void main(String[] args) {
            //1.创建原始对象
            UserService userService = new UserService();
            //2.通过cglib的方式创建动态代理对象
            //cglib提供了一个Enhancer类
            Enhancer enhancer = new Enhancer();
            //需要设置Enhancer类对象的三个属性
            //设置代理类的父类
            enhancer.setSuperclass(UserService.class);
            //设置代理类字节码的类加载器
            enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
            //设置代理类增加的额外功能:
            //一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法
            MethodInterceptor methodInterceptor = new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("----------proxy log-----------");
                    method.invoke(userService,objects);
                    return null;
                }
            };
            enhancer.setCallback(methodInterceptor);
            //使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService
            UserService userServiceProxy = (UserService)enhancer.create();
    
            userServiceProxy.login("aaa","sss");
        }
    }
    
5.2.3 动态代理底层创建的总结
  • JDK提供的动态代理:

    Proxy.newProxyInstance():通过实现接口来创建代理的实现类
    
  • Cglib动态代理:

    Enhancer:通过继承父类来实现的代理类
    

5.3 Spring工厂如何加工创建代理对象

Spring创建代理对象,是通过BeanPostProcessor指定的:

  • 创建BeanPostProcessor的实现类:
public class MyAOP implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean instanceof OrderService){
            //创建该类的代理对象
            OrderService proxyInstance =(OrderService) Proxy.newProxyInstance(MyAOP.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("增加的额外功能!");
                    Object ret = method.invoke(bean, args);
                    return ret;
                }
            });
            return proxyInstance;
        }
        return bean;
    }
}
  • 在配置文件中配置:
<bean id="aop" class="com.itheima.basic.aop.MyAOP">bean>
<bean class="com.itheima.basic.aop.OrderServiceImpl" id="orderService">bean>

第六章 基于注解的AOP编程

6.1 基于注解的AOP编程开发步骤

  1. 原始对象;
  2. 额外功能;
  3. 切入点;
  4. 组装切面。
  • 原始对象:

    
    <bean class="com.itheima.basic.aspect.OrderServiceImpl" id="orderService">bean>
    
  • 定义切入点及额外方法:

    /**
     * 切面都得有两个固定的组成:
     * 1.切入点
     * 2.额外功能
     */
    @Aspect//表示该类为切面类
    public class MyAspect {
        //将切入点定义为切面类的方法,实现切入点复用
        //切入点表达式之间用||、&&进行与或运算
        @Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))")
        public void pt1(){
        }
    
        //定义前置额外功能
        @Before("pt1()")
        public void before(){
            System.out.println("前置方法!");
        }
    
        //定义后置方法
        @After("pt1()")
        public void after(){
            System.out.println("后置方法!");
        }
    
        //定义异常后抛出的额外功能方法
        @AfterThrowing("pt1()")
        public void afterThrowing(){
            System.out.println("异常后执行");
        }
    
        //最终方法(finally中执行的方法)
        @AfterReturning("pt1()")
        public void afterReturning(){
            System.out.println("最终方法");
        }
    
        //环绕方法
        @Around("pt1()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint){
            Object proceed = null;
            try {
                System.out.println("前置方法!");
                Object[] args = proceedingJoinPoint.getArgs();
               proceed = proceedingJoinPoint.proceed(args);
                System.out.println("后置方法!");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                System.out.println("异常时额外方法");
            }finally {
                System.out.println("最终方法!");
            }
            return proceed;
        }
    
    }
    
  • 在配置文件中配置该类,告知Spring基于注解进行aop开发:

    <bean class="com.itheima.basic.aspect.MyAspect" id="aspect">bean>
    
    <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
    

6.2 Spring对于JDK与Cglib之间的切换

Spring中AOP编程默认使用的是JDK的动态代理模式,若要切换成Cglib动态代理模式,则需要在配置文件对proxy-target-class进行设置:

  • 对于传统的配置文件进行aop开发:

    <aop:config proxy-target-class="true">
        ...
    aop:config>
    
  • 对于基于注解的方式进行aop开发:

    <aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
    

第七章 AOP开发中的一个坑

在同一个业务类中,尽心业务方法的相互调用。只有最外层的方法,才是加入了额外功能的(内部的方法,通过普通的方式调用,都是调用的原始方法)。如果想让内层的方法也调用代理对象的方法,可以通过让业务类实现ApplicationContextAware接口,实现其方法,获得ApplicationContext对象,以此来获得业务类的代理对象,将内层方法替换为代理类的方法,就可以获得加入额外功能的方法。

public class OrderServiceImpl implements OrderService, ApplicationContextAware {

    private ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public void save() {
            System.out.println("保存对象!");
    }

    @Override
    public void login() {
        context.getBean("orderService",OrderService.class).save();
    }
}

第八章 AOP阶段知识总结

  • AOP编程(Spring的动态代理开发)

    • 概念:通过代理类为原始类增加额外功能
    • 好处:利于原始类的维护
  • AOP编程的开发步骤(动态代理的开发步骤):

    1. 原始对象;
    2. 额外功能;
    3. 切入点;
    4. 组装切面。
    • 基于XML方式的AOP开发:

      • 在Spring配置文件中创建原始对象:

        <bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
        
      • 额外功能:

        创建类实现MethodInterceptor接口,重写invoke方法,在其中增加额外功能
        并在配置文件中配置该类:
        <bean class="com.itheima.basic.MyInterceptor" id="before">bean>
        
      • 切入点:

        <aop:pointcut id="pt1" expression="execution(* login(..)) and args(String) and within(*..UserServiceImpl)"/>
        
      • 组装切面

        <aop:config proxy-target-class="true">
            <aop:advisor advice-ref="before" pointcut-ref="pt1">aop:advisor>
        aop:config>
        
    • 基于注解方式的AOP开发

      • 在Spring配置文件中创建原始对象:

        <bean class="com.itheima.basic.UserServiceImpl" id="userService">bean>
        
      • 定义切面类,并在切面类中定义切入点,额外功能,组装切面:

        @Aspect//表示该类为切面类
        public class MyAspect {
            //将切入点定义为切面类的方法
            @Pointcut(value = "execution(* com.itheima.basic.aspect.OrderServiceImpl.*(..)) || execution(* *(..))")
            public void pt1(){
            }
        
            //环绕方法
            @Around("pt1()")
            public Object around(ProceedingJoinPoint proceedingJoinPoint){
                Object proceed = null;
                try {
                    System.out.println("前置方法!");
                    Object[] args = proceedingJoinPoint.getArgs();
                   proceed = proceedingJoinPoint.proceed(args);
                    System.out.println("后置方法!");
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                    System.out.println("异常时额外方法");
                }finally {
                    System.out.println("最终方法!");
                }
                return proceed;
            }
        
        }
        
      • 在配置文件中配置切面类,并指定利用注解开发AOP:

        <bean class="com.itheima.basic.aspect.MyAspect" id="aspect">bean>
        
        <aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
        
  • AOP的底层实现

    • JDK动态代理(Spring默认方式):基于原始类的接口 创建代理对象的实现

      Proxy.newProxyInstance(classLoader,interfaces,invokeHandler)
      
    • Cglib动态代理:把原始类作为代理类的父类 通过继承的关系创建代理对象

      Enhancer enhancer = new Enhancer();
      //需要设置Enhancer类对象的三个属性
      //设置代理类的父类
      enhancer.setSuperclass(UserService.class);
      //设置代理类字节码的类加载器
      enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
      //设置代理类增加的额外功能:
      //一般在CallBack接口的子接口MethodInterceptor的内部实现类中定义额外方法
      MethodInterceptor methodInterceptor = new MethodInterceptor() {
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy 
                 ...
          }
      };
      enhancer.setCallback(methodInterceptor);
      //使用Enhancer类对象的create方法创建代理类,由于代理类是UserService的子类,所以类型也可以声明为UserService
      UserService userServiceProxy = (UserService)enhancer.create();                    
      
    • 两种动态代理的切换:

      基于xml:
      <aop:config proxy-target-class="true">
      	...	
      aop:config>
      
      基于注解:
      <aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
      
    • Spring底层基于BeanPostProcessor完成对象的加工,创建动态代理对象。

你可能感兴趣的:(Spring,spring,aop,java,编程语言)