Spring框架(认识SpringAOP及底层原理)

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

Spring知识

  一丶SpringIOC初步认识↓↓↓
第一篇---->初识Spring
  二丶SpringIOC深入↓↓↓
第二篇---->深入SpringIoC容器(一)
第三篇---->深入SpringIoC容器(二)
  三丶装配SpringBean↓↓↓
第四篇---->依赖注入的方式
第五篇---->基于xml装配Bean
第六篇---->基于注解装配Bean
第七篇---->Spring Bean之间的关系
第八篇---->SpringBean的作用域
第九篇---->Spring 加载属性(properties)文件
第十篇---->Spring表达式(SpEL)
第十一篇---->Spring在xml中配置组件扫描
  四丶面向切面编程↓↓↓
第十二篇—>认识SpringAOP及底层原理
第十三篇—>使用@AspectJ注解开发AOP
第十四篇—>使用xml配置开发AOP
  五丶Spring中数据库编程↓↓↓
第十五篇—>数据库编程JdbcTemplate
  六丶Spring事务管理↓↓↓
第十六篇—>Spring事务管理初识
第十七篇—>编程式事务和声明式事务
第十八篇—>事务ACID特性
第十九篇—>事务传播行为
第二十篇—>事务隔离级别

四丶面向切面编程(AOP)

  • 如果说之前的IoC是Spring的核心,那么面向切面编程就是Spring最为重要的功能之一了,并且AOP原理可是说是Spring技术中最难理解的一个部分了,在数据库事务中切面编程被广泛应用.SpringAOP底层技术核心是—动态代理,明白了动态代理模式,再学习SpringAOP相关知识将容易多.

1 动态代理设计模式

1.1 动态代理在SpringAOP中简要介绍

  • 动态代理设计模式是Spring AOP底层非常重要的一个技术,只有熟练掌握了动态代理模式才能够对SpringAOP的相关知识理解的更加透彻.
  • 动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.

1.2 代理模式介绍

  • 说明动态代理之前,先聊聊代理模式,代理模式的原理实际是:使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上.
  • 比如,在一个软件公司中,有个软件工程师,客户带着需求去找公司显然不会直接和你谈,而是去找商务谈,此时客户就会认为商务就代表公司.
      在这里插入图片描述
  • 那么商务(代理对象)存在的意义是什么呢?商务可以跟客户进行谈判,商定项目的价格等,但是谈判也有可能不成功,这样客户与该公司的合作关系就终结了,这些都不需要软件工程师去处理.
  • 因此代理的作用可以理解为:在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象,该例子中商务控制了客户对软件工程师的访问.
  • 通过上面的描述,我们可以清楚明白,商务和软件工程师是代理和被代理的关系,客户是经过商务去访问软件工程师的.此时客户就是程序中的调用者,商务就是代理对象,软件工程师就是真实对象.我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系
  • 代理必须存在的两个步骤:
      1) 代理对象和真实对象的代理逻辑
      2) 实现代理对象的代理逻辑
  • 在Java中有多种动态代理技术,比如JDK,CGLIB,Javassist,ASM等,其中最常用的动态代理技术有两种:一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,这是第三方提供的一个技术.目前Spring常用JDK和CGLIB,而MyBatis还使用了Javassist,它们的理念都是相似的
  • JDK和CGLIB应用场景
      如果真实对象实现了接口,则一般采用JDK的动态代理,如果真实独享没有实现接口,则只能采用CGLIB动态代理

1.3 动态代理技术之JDK动态代理

  • JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能生成代理对象.
  • 先定义一个接口,并提供其实现类
      在这里插入图片描述
  • 现在就可以进行动态代理了.按照之前提到的步骤,先要建立代理对象和真实对象的关系,然后实现代理对象的代理逻辑.共分为两个步骤.
  • 在JDK动态代理中,代理类必须实现java.lang.reflect.InvocationHandler接口,它里面定义了一个invoke方法.
      在这里插入图片描述
  • 第1步,建立代理对象和真实对象的关系,这里使用了bind方法去完成,方法里面首先用类的属性target保存了真实对象,然后通过下面代码建立并生成了代理对象
      在这里插入图片描述
  • 其中newProxyInstance方法包含3个参数:
      - 第一个是类加载器,采用了target本身的类加载器
      - 第二个是把生成的动态代理对象下挂在哪些接口下面,该写法就是将生成的代理对象放在target真实对象所实现的所有接口下.HelloWorldImpl对象的接口显然是HelloWorld,代理对象可以这样声明:HelloWorld proxy =xxxx;
      第三个是定义实现方法逻辑的代理类,this表示当前对象,它`必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法.
  • 第2步,实现代理逻辑方法.invoke方法可以实现代理逻辑,invoke方法的3个参数含义如下:
      proxy:代理对象,就是bind方法生成的对象
      method,当前调度的方法
      args:调度方法的参数
  • 当我们使用代理对象调度方法时,它就会进入到invoke方法里面,下面这行代理相当于调度真实对象的方法,方式是通过反射.(得到的method对象已经包含了要调用目标方法的信息,target表示调用方法的目标真实对象,args就是要调用方法的实参)
      在这里插入图片描述
  • 再次结合一开始给出的案例,proxy相当于商务对象,target相当于软件工程师对象,bind方法就是建立商务和软件工程师代理关系的方法.而invoke就是商务逻辑,它将控制软件工程师的访问.
  • 测试代码
      在这里插入图片描述
  • 此时,大家可以在代码中关注到,在调用sayHelloWorld()方法前后都可以自定义相关业务逻辑,甚至可以不调用sayHelloWolrd方法
  • 最后,我们通过打断点,详细看一下执行流程,断点如下:
      在这里插入图片描述
      在这里插入图片描述
  • 执行流程
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • 这就是JDK的动态代理,它是一种最常用的动态代理,非常重要.

1.4 动态代理技术之CGLIB动态代理

  • JDK动态代理必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他第三方技术,比如CGLIB动态代理.它的优势在于不需要接口,只要一个非抽象类就能实现动态代理
  • 需要导入jar包(cglib,asm,后者是因为用到了Cglib的Enhancer类),链接:链接:https://pan.baidu.com/s/1fRyftNKs7_rwx3S6a8VKhg
    提取码:vch1
  • 创建一个非抽象类作为真实对象
      在这里插入图片描述
  • 创建CGLIB动态代理类
public class CglibProxy implements MethodInterceptor {

    /**
     * 生成CGLIB代理对象
     * @param cls  Class类
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls){
        // 1. CGLIB enhancer 增强类对象
        Enhancer enhancer = new Enhancer();
        //2. 设置增强类型
        enhancer.setSuperclass(cls);
        //3. 定义代理对象为当前对象,要求当前对象实现MethodInterceptor接口
        enhancer.setCallback(this);
        //4. 生成并返回代理对象
        return enhancer.create();
    }

    /**
     * 代理逻辑方法
     * @param proxy 代理对象
     * @param method    方法
     * @param args  方法参数
     * @param methodProxy   方法代理
     * @return  代理结果返回,实际为真实对象中调用方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method,
                            Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        System.out.println("调用真实对象前");
        // CGLIB反射调用真实对象方法
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("调用真实对象后");
        return result;
    }
}
  • 这里使用了CGLIB的加强这Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置哪个类为它的代理类.其中参数this就意味着当前对象为代理类,那么this就`必须实现接口MethodInterceptor,并重写其中intercept方法,最后返回代理对象.
  • 当前类的intercept方法就是代理逻辑方法.CGLIB就是通过如下代码实现的
      在这里插入图片描述
  • 测试效果
      在这里插入图片描述
  • 最后,我们通过打断点,详细看一下执行流程,断点如下:
      在这里插入图片描述
      在这里插入图片描述
  • 执行流程:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • JDK动态代理和CGLIB动态代理是非常相似的,都通过某个方法生成代理对象,然后代理类分别要实现接口,而实现的接口中所定义的方法就是代理对象的逻辑方法,它可以控制对真实对象的访问.

1.5 拦截器

  • 由于动态代理一般比较难理解,JDK中提供了一个拦截器接口供我们使用,我们只需要知道拦截器接口的方法,含义和作用即可,无须知道动态代理怎么实现的.
  • 现在用JDK动态代理实现一个拦截器的逻辑,先定义一个拦截器接口Interceptor
     &emspp;在这里插入图片描述
  • 该拦截器接口中定义了3个方法:before,around,after方法,分别对这些方法进行一些逻辑定义
      3个方法参数为:proxy代理对象,target真实对象,method方法,args运行方法参数
      before方法返回boolean值,它在真实对象前调用.当返回true时,则反射真实对象的方法,当返回false时,则调用around方法
      在before方法返回false的情况下,调用around方法
      在反射真实对象方法或者around方法执行结束后,调用after方法
  • 实现该接口的实现类–MyInterceptor
      在这里插入图片描述
  • 该实现类实现了所有的Interceptor接口方法,使用JDK动态代理,就可以去实现这些方法在适当的时候调用逻辑了.
  • 创建一个真实对象HelloWorld接口和HelloWorldImpl
      在这里插入图片描述
  • 创建JDK代理类
public class JdkProxyInterceptor implements InvocationHandler {

    private Object target;//真实对象
    private String interceptorClass =null;//拦截器全限定名

    // 构造方法
    public JdkProxyInterceptor(Object target, String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /**
     * 绑定委托对象,并返回一个代理对象
     * @param target    真实对象
     * @param interceptorClass  拦截器全类名
     * @return  返回代理对象
     */
    public static Object bind(Object target,String interceptorClass){
        //生成代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkProxyInterceptor(target, interceptorClass));
    }

    /**
     * 通过代理对象调用方法
     * @param proxy 代理对象
     * @param method    被调用方法
     * @param args 方法参数
     * @return  返回调用方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (interceptorClass==null){
            //没有设置拦截器则直接反射被调用方法
            return method.invoke(target, args);
        }
        Object result=null;
        //通过反射生成拦截器
        MyInterceptor interceptor = (MyInterceptor) Class.forName(interceptorClass).newInstance();
        //调用前置方法
        if (interceptor.before(proxy, target, method, args)){
            //反射被调用方法
            result = method.invoke(target, args);
        }else{
            interceptor.around(proxy, target, method, args);
        }
        //调用后置方法
        interceptor.after(proxy, target, method, args);
        return result;
    }
}
  • 代理类中的构造器有两个属性,一个是target表示真实对象,另一个是字符串interceptorClass表示拦截器实现类的全限定名.
  • 执行步骤
      第一步,在bind方法中用JDK动态代理绑定了一个对象,然后返回代理对象
      第二步,如果没有设置拦截器,则直接反射真实对象的方法,然后结束,否则进行第三步
      第三步,通过反射生成拦截器,并准备使用拦截器
      第四步,调用拦截器before方法,如果返回true,反射原来的方法,否则运行拦截器的around方法
      第五步,调用拦截器的after方法
      第六步,返回结果
  • 这样的话,作为开发者只需要熟悉拦截器作用,并编写好拦截器,剩下的动态代理实现交给精通的人员去做就可以了
  • 创建测试类
      在这里插入图片描述

2 算术运算案例引入

  • 案例介绍,存在一个进行算术运算(+,-,*,/)的接口以及对应的实现类,需求是在使用该程序进行算术运算时,要模拟输出日志信息,打印程序正在执行的过程
  • 首先创建一个用于算术运算的接口及其实现类
      在这里插入图片描述
  • 下面来分析第一个需求:程序执行各种运算过程前后可以观测到具体活动,这个很简单,直接在进行算术运算前后输出一条关于计算的相关信息即可
      在这里插入图片描述
  • 通过上面代码,我们可以很明显看出一些很明显的缺点
      1) 代码混乱:实现类中我们最主要的业务需求就是加减乘除,在四个方法中肯定以往以一种最简单明了的方法实现该逻辑,但是越来越多的非业务需求(打印日志等)加入在核心方法中后,原先的业务代码变得急剧膨胀,每个方法在处理核心逻辑的同时还必须兼容其他多个关注点
      2) 代码分散:以刚刚的日志需求为例,只为了满足这一个单一需求,就不得不在多个模块中多次使用重复相同的日志打印代码,如果日志需求发生了变化,那么岂不是所有的模块都必须要修改,这存在很大的
  • 下面创建一个测试类,简单测试一下
      在这里插入图片描述
  • 下面我们通过JDK的动态代理来解决实现类中暴露的缺点问题,动态代理中的真实对象就是CalculatorImpl,下面编写代理类
      在这里插入图片描述
  • 通过动态代理中的代理逻辑方法,我们可以获取到调用方法的名称和参数,这样就可以在实际反射调用真实对象的方法前,实现日志相关逻辑,我们现在就可以将CalculatorImpl中的输出日志相关代码删除掉了
      在这里插入图片描述
  • 执行测试
      在这里插入图片描述
  • 通过使用动态代理模式,我们将核心业务逻辑进行了极大的精简,并且在日志需求方面也并没有使用太多重复代码,极大提高了代码的可读性.
  • 我们继续引入拦截器,将代码重构,将前置before日志打印和后置日志after打印放入拦截器中,并扩展两个方法:异常日志打印和afterRunning(异常出现后不执行)
      在这里插入图片描述
      在这里插入图片描述
  • 如果你曾经使用过AP,你就会发现上述定义和SpringAOP中定义的通知非常详尽.
      在这里插入图片描述
      在这里插入图片描述
  • 下面给出一个日志逻辑的流程图
      在这里插入图片描述
  • 总结:
      1) 在invoke方法中,代码的运行逻辑就是按照流程图中的方式,其中设置了异常标志,能够判断反射原有对象方法的时候是否发生了异常
      2) 通过上述对代码的修改,我们核心业务逻辑的类InterceptorImpl变得更加简洁,也容易维护,主要都集中在业务处理上,而不是日志管理上,这就是AOP的魅力.
      3) AOP可以对我们的代码流程进行一定的封装,通过动态代理技术,将代码织入到对应的流程环节中.
      4) AOP通过动态代理模式,来管控各个对象操作的切面环境,管理包括日志,数据库事务等操作,让我们可以拥有在反射原有对象前后进行自定义逻辑操作,异常出现后的逻辑代码,甚至可以取代原始方法.

3 SpringAOP基本介绍

  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中.所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事务,日志等
  • 若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起.这样,会使主业务逻辑变的混杂不清,并且不利于后期代码的维护.
  • AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
  • AOP 的主要编程对象是切面(aspect),在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
  • AOP 的好处:每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级,业务模块更简洁, 只包含核心业务代码.

4 Spring AOP的基本概念

4.1 面向切面编程术语

  • 相信你通过上面的计算案例已经对AOP大概要做的工作有了一个初步的认识,现在让我们正式走进AOP,对其基本概念(术语)进行理解,同时,该部分在说明概念的同时会结合计算案例中的对应部分,方便大家容易理解.
  • 切面(Aspect)
      切面就是在一个怎么样的环境中工作.比如算术运算案例中,日志管理直接贯穿整个代码层面,这就是一个切面.它能够在被代理对象前后,产生异常或者正常返回切入你的代码,甚至代替原来被代理对象的方法,在动态代理中可以把它理解成一个拦截器,比如算术运算案例中InterceptorImpl就是一个切面类.简单来讲就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间耦合度.
  • 通知(Adice)
      通知是切面开启后,切面中的方法.它根据在代理真实对象方法调用前,后的顺序和逻辑区分分成几类
      1) 前置通知(before):在动态代理反射原有对象方法前执行的通知功能,无论是否抛出异常,都会执行.
      2) 后置通知(after):在动态代理反射原有对象方法执行的通知功能,无论是否抛出异常,它都会执行.
      3) 返回通知(afterRunning):在动态代理反射原有对象方法执行,如果出现了异常就不会执行
      4) 异常通知(afterThrowing):在动态代理反射原有对象方法产生异常后执行的通知功能.
      5) 环绕通知(around):在动态代理中,他可以取代当前被拦截对象的方法,通过参数或反射调用被拦截对象的方法.该通知也是最强大的通知.
  • 增强
       增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
  • 引入(Introduction)
      是一种特殊的增强,引入允许我们在现有的类里添加自定义的类和方法
  • 连接点(join point)
      它是程序中切面可以织入的位置,例如类中的一个方法.类或代码中拥有一些具有边界性质的特定点,这些点就可以被称为连接点.Spring仅支持方法的连接点,即可以在方法执行前,方法调用后,方法抛出异常等执行进行增强.通俗说法就是:类里面哪些方法可以被增强,这些方法都可以叫做连接点
  • 切点(Pointcut)
      切入点可以指定某个通知类型要在哪个连接点被织入到程序中,也就是切面实际织入的位置.每个类中都可以拥有多个连接点,如一个拥有两个方法的类,这两个方法都可以是连接点,即连接点是程序中客观存在的事务.AOP通过"切点"定位特定的连接点.切点和连接点不是一对一的关系,一个切点可以匹配多个连接点.通俗说法就是,类中可以有很多方法被增强,但是实际开发中,可能只对某些方法进行增强,那么实际被增强的方法就称为切入点.在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件.
  • PS:看完了上面的连接点和切点的描述,你可能还有点蒙蒙的,下面我用一种通俗的说法再说一下,看能不能帮到你,如果还是不能理解,请继续往后看,在AOP的代码应用部分,你可能就会豁然开朗
      比如开车经过一条高速公路,这条高速公路上有很多个出口(连接点),但是我们不会每个出口都会出去,只会选择我们需要的那个出口(切点)开出去。简单可以理解为,每个出口都是连接点,但是我们使用的那个出口才是切点。每个方法有多个位置适合织入通知,这些位置都是连接点。但是只有我们选择的那个具体的位置才是切点。
  • 织入(Weaving)
      织入是一个生成代理对象的过程.实际代理的方法分为静态代理和动态代理.静态代理是在编译class文件时生成的代码逻辑,但是在Spring中并不使用这种方式.另外一种是通过ClassLoader(类加载器),也就是在类加载的时候生成的代码逻辑,但是它在应用程序代码运行前就生成对应的逻辑.还有一种是运行期,动态生成代码的方式,这就是SpringAOP采用的方式,Spring是以JDK和CGLIB动态代理来生成代理对象的.正如算术运算案例中的JdkProxy,就是通过JDK动态代理生成代理对象的.
  • AOP的术语确实很难理解,我也是学习了几遍才算是理解的比较清晰了,下面给出AOP工作流程图,相信这种流程图形式对你对AOP的理解会有所帮助
      在这里插入图片描述

4.2 核心关注点和横切关注点

  • 横切的含义是剖析封装的对象内部,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为Aspect,即切面.
  • 使用横切技术,AOP可以把软件分成两个部分:核心关注点和横切关注点.业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点.横切关注点的一个特点是,他们经常发生在核心关注点的多处且非常相似,如日志,权限等.AOP的作用恰恰在此,分离系统中的各种关注点,将核心关注点和横切关注点分离开.

4.3 Spring对AOP的支持

  • AOP框架并不是Spring框架所特有的,Spring仅是支持AOP编程的框架之一.每一个框架对AOP的支持各有特点,有些AOP能够对方法的参数进行拦截,有些AOP能对方法进行拦截
  • SpringAOP是一种基于方法拦截的AOP.换句话说Spring只能支持方法拦截的AOP.
  • 在Spring中有4中方式实现AOP的拦截功能
      1) 使用ProxyFactoryBean和对应的接口实现AOP
      2) 使用xml配置AOP
      3) 使用@AspectJ注解驱动切面
      4) 使用AspectJ注入切面
  • 在SpringAOP的拦截方式中,最常用的是@AspectJ注解的方式实现切面,其次是xml配置有一定的辅助作用,剩下两种用的太少,不作介绍.
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值