Spring——AOP

Posted Vodka~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring——AOP相关的知识,希望对你有一定的参考价值。

一.

  • AOP (Aspect Oriented Programming): 面向切面编程,主要用于提取同一层次对象的公用行为进行建模,提高程序可重用性,降低各个模块间的耦合度,高内聚低耦合,提高系统扩展性,主要用于日志记录,性能统计,安全控制,事务处理等等,底层实现是——动态代理(JDK+CGLIB)。

1.代理模式: 为某一个对象(委托类)提供一个代理类,用于控制请求对象对目标对象的访问,委托类和代理类有一个共同的父类或父接口,代理类会对请求做预处理,过滤,或者将请求分配给指定对象。
2. 委托类和代理类有一个共同的父类或父接口,代理类会对请求做预处理,过滤,将请求分配给指定对象。
3. 生活中常见的代理模式:明星经纪人,房东及中介,婚庆公司等
4. 代理模式设计原则:
4.1 委托类和代理类有共同行为
4.2 代理类能够增强委托类的行为
5.常见的代理模式:
5.1: 静态代理和动态代理
5.2: 静态代理的特点:
-目标角色固定
-在应用程序之前就得知目标角色
-代理对象会增强目标对象的行为
-当需要对新的目标对象进行代理时,代理对象的数量过多,程序冗余,耦合度高
5.3: 动态代理:
- 可以利用java的反射机制,在程序运行期间动态地为目标对象生成所需的代理对象
- JDK动态代理 (目标对象必须有接口实现) , CGLIB 动态代理
- 动态代理的特点:
-目标对象不固定
-在应用程序执行时动态创建目标对象
-代理对象会增强目标对象的行为
5.4: JDK动态代理所需实现的接口:
- Proxy 类:
一个专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类
-public static object newProxyInstance( ClassLoader loader,
Class<?> [] interfaces,
InvocationHandler h)
-loader: 一个ClassLoader 对象, 定义了由哪个ClassLoader对象来生成的代理对象进行加载
-interfaces: 一个Interface 对象的数组,给需要代理的对象提供了一组相应的接口,若提供了接口,则代理对 象实现了该接口(多态),就能调用接口中的方法了。
-h: 一个InvocationHandler接口,用于代理实例调用处理程序实现的接口,每个代理实例都有关联的调用处理程序,对代理实例调用方法时,将相应方法进行编码,指派到它的调用处理程序的 invoke 方法 (传入InvocationHandler接口的子类或者实现该接口的类本身)

                    -!!!!!转型:  接口作为引用类型来使用,任何实现该接口的类的实例都可以存储在该接口类型的变量中,通过这些变量可以访问类中所实现的接口中的方法,Java 运行时系统会动态地确定应该使用哪个类中的方法,实际上是调用相应的实现类的方法
   package com.vodka.Proxy;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Calendar;

/**
 * @author Vodka
 * @date 2022/01//10:37
 */
@Service
public class DynamicProxyHandler implements InvocationHandler 
//    代理的目标对象
    private  Object target;

//   通过构造函数设置目标对象
    public  DynamicProxyHandler(Object ob)
        this.target = ob;
    

//    获取相应目标对象的代理类
    public Object getProxy()
        Object ob = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
        return ob;
    

//   重写InvocationHandler接口的invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] MethodArgs) throws Throwable 
//         用户行为增强
        Calendar cl = Calendar.getInstance();
        System.out.println("记录日志——开始操作:"  +  cl.getTime());
        //        返回某个接口的方法列表中,某个方法的返回值
        Object object = method.invoke(target,MethodArgs);
//        用户增强行为
        System.out.println("记录日志——结束操作:"  +  cl.getTime());
        return object;
    


                                      proxy:   调用该方法的代理实例
                                      method: 代理实例的目标对象的方法
                                      args:目标对象的方法列表,用于反射调用


!!!由于Proxy.newProxyInstance() 方法返回值是接口类型,所以接收类型也必须是接口,而不是接口实现类
    推测:  接口类型的变量可以存储接口实现类的实例,在调用方法时向下转型,就可以调用接口实现类的实例的所有方法了
public class App 
    public static void main(String[] args) 
//        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("AutoInsert.xml");
        IndividualsGoingMarry individualsGoingMarry = new IndividualsGoingMarry("Vodka","Rose");
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(individualsGoingMarry);
        Marry marry = (Marry) dynamicProxyHandler.getProxy();
        marry.ArrangeMarry();
    



6.Idea 中JDK动态代理文件的生成
-1 : 必须在main方法中执行,无法再junit的test方法调用无法生成
-2: 在main方法最前面增加配置,这样会输出代理 class 文件
System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,“true”);
-3: 代理class 的生成路径 是在 idea 的工作目录下的 com\\sun\\proxy 目录中
-4: $Proxy0.class 文件和源代码不在同一个目录中

  1. CGLIB: JDK动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK 的动态代理, cglib 是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,由于采用的是继承,所以不能对final修饰的类进行代理。
    -在 pom.xml 文件中引入 cglib 的相关依赖:
       <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
    -原理:   

package com.vodka.Proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author Vodka
 * @date 2022/02//23:04
 */
public class CglibInterceptor implements MethodInterceptor 

//    目标对象
    private  Object target;
//    通过构造器传入目标对象
    public CglibInterceptor(Object ob)
        this.target = ob;
    

    public Object GetProxy()
//        通过 Enhancer对象中的create()方法生成一个类,用于生成代理对象
        Enhancer enhancer = new Enhancer();
//         将目标类设置为代理类的父类
        enhancer.setSuperclass(target.getClass());
//        设置拦截器,回调对象为本身
        enhancer.setCallback(this);
//        生成代理对象
        return enhancer.create();
    

/*
*      拦截器:
*          1.目标对象(被代理对象)的方法调用
*          2.行为增强
*          3.param:
*              - Object o : cglib动态生成的代理类的实例
*              - method:  实体类被代理并调用的方法的引用
*              - objects:   参数列表
*              - methodProxy:  代理类对方法的代理引用
* */

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable 
//        增强行为
        System.out.println("方法调用前!");

        Object object = methodProxy.invoke(target,objects);

        //        增强行为
        System.out.println("方法调用后!");

        return object;
    


8.JDK代理与CGLIB代理的区别:
-JDK动态代理实现接口,Cglib 动态代理继承思想
-JDK动态代理(目标对象存在接口时):执行效率高于Cglib
-如果目标对象有接口实现,就选择JDK代理,没有接口实现,则选择Cglib代理.

9.AOP基本概念:
-JoinPoint: 被拦截到的每个点,spring 中指被拦截到的每个方法,spring aop 一个连接点代表一个方法的执行。
-Poingcut: 对连接点进行拦截的定义(匹配规则,规定拦截哪些方法,对哪些方法进行处理),spring 有专门的表达式语言定义。
-Advice:
拦截到每一个点后所要做的操作:
-前置通知(前置增强): before() 执行方法前的通知
-返回通知(返回增强): afterReturn 方法正常结束返回后的通知
-异常抛出通知(异常抛出增强): afterThrow()
-最终通知 —— after 无论方法是否发生异常,均会执行该通知。
-环绕通知—— around 包围一个连接点的通知,如方法调用。作为最强大的一种通知类型,环绕通知可以在方
法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
- Aspect:
切入点与通知结合,决定了切面的定义,定义了要拦截哪些方法,通知则定义了拦截过方法后要进行哪种行为, 切面则是横切关注点的抽象,与类类似,类是对物体特征的抽象,切面则是横切关注点的抽象。
- Target:
被代理的目标对象
- Weave (织入):
将切面应用到目标对象并生成代理对象的这个过程即为织入。
- Introduction (引入):
在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或字段

10.Spring AOP 环境搭建:

<!-- Spring AOP-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.7</version>
    </dependency>


<!-- Spring.xml -->
<!--添加命名空间 -->
xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans/spring-beans.xsd

<!--       配置AOP代理-->
       <aop:aspectj-autoproxy/>
package com.vodka.Aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author Vodka
 * @date 2022/02//22:05
 */
@Component   //将该类的对象实例化交给IOC容器处理
@Aspect  //声明当前类为一个切面(定义切入点和通知)
public class LogCut 
    
    /*
    *   切入点:
    *        - 具体拦截哪些类的方法
    *        -匹配规则,拦截什么方法
    *    定义切入点:
    *         @Pointcut("匹配规则")
    *     切入点表达式:
    *          -表达式中第一个 * 号表示方法的修饰范围 (public , private , protected) , 如果取值是 * ,则表示所有范围
    *          -执行所有公共方法: execution(public *(..))
    *          -执行任意的set方法: execution( * set*(..))  //这里的第二个星号可以表示为 setId, setName ,等等;
    *          -设置指定包下的任意类的任意方法:   execution(* com.vodka.service.*.*(..))
    *          -设置指定包及子包下的任意类的任意方法:   execution(* com.vodka.service..*.*(..))
    *
    * */
    @Pointcut("execution(* com.vodka.Service.*.*(..)))")
    public void cut()
        System.out.println("当前切入点");
    

//    前置通知
    @Before(value = "cut()")
    public void BeforeCut()
        cut();
        System.out.println("前置通知");
    

    //    声明返回通知,目标类方法无异常后,执行该通知
    @AfterReturning(value = "cut()")
    public void AfterReturn()
        System.out.println("声明返回通知");
    

//  后置通知,有无异常都会执行
    @After(value = "cut()")
    public void AfterCut()
        System.out.println("最终通知");
    

    //  异常通知,出现异常就会执行
    @AfterThrowing(value = "cut()" , throwing = "e")
    public void AfterThrow(Exception e)
        System.out.println("异常通知: " + e.getMessage());
    

    /*环绕通知,有返回值,目标类具体方法的执行前后都可通过环绕通知定义响应处理,
    需要通过显示调用的方法,否则无法访问指定方法 pjp.proceed();
     */
    @Around(value =  "cut()")
    public Object  AroundCut(ProceedingJoinPoint pjp)
        System.out.println("环绕通知——前置通知");
        Object ob = null;

        try 
            //显示调用对应的方法,这里是实例cut()
            ob = pjp.proceed();
            System.out.println("目标对象: " + pjp.getTarget());
            System.out.println("环绕通知——返回通知");
        catch (Throwable throwable) 
            System.out.println("环绕通知的异常!");
            throwable.printStackTrace();
        

        System.out.println("环绕通知——最终通知");
        return ob;
    

AOPXML配置方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

<!--      设置自动化扫描范围  -->
       <context:component-scan base-package="com.vodka" />

<!--      通过 XML 配置aop切面  -->
       <aop:config>
              <aop:aspect ref="logCut">
                     <aop:pointcut id="cut" expression="execution(* com.vodka.Service.*.*(..))"></aop:pointcut>
                     <aop:before method="BeforeCut" pointcut-ref="cut" ></aop:before>
                     <aop:after-returning method="AfterReturn" pointcut-ref="cut"></aop:after-returning>
                     <aop:after-throwing method="AfterThrow" pointcut-ref="cut" throwing="e"></aop:after-throwing>
                     <aop:around method="AroundCut" pointcut-ref="cut"></aop:around>
                     <aop:after method="AfterCut" pointcut-ref="cut"></aop:after>
              </aop:aspect>
       </aop:config>
</beans>
@Component   //将该类的对象实例化交给IOC容器处理
public class LogCut 

    /*
    *   切入点:
    *        - 具体拦截哪些类的方法
    *        -匹配规则,拦截什么方法
    *    定义切入点:
    *         @Pointcut("匹配规则")
    *     切入点表达式:
    *          -表达式中第一个 * 号表示方法的修饰范围 (public , private , protected) , 如果取值是 * ,则表示所有范围
    *          -执行所有公共方法: execution(public *(..))
    *          -执行任意的set方法: execution( * set*(..))  //这里的第二个星号可以表示为 setId, setName ,等等;
    *          -设置指定包下的任意类的任意方法:   execution(* com.vodka.service.*.*(..))
    *          -设置指定包及子包下的任意类的任意方法:   execution(* com.vodka.service..*.*(..))
    *
    * */

    public void cut()
        System.out.println("当前切入点");
    

//    前置通知
    public void BeforeCut()
        cut();
        System.out.println("前置通知");
    

    //    声明返回通知,目标类方法无异常后,执行该通知
    public void AfterReturn()
        System.out.println("声明返回通知");
    

//  后置通知,有无异常都会执行
    public void AfterCut()
        System.out.println("最终通知");
    

    //  异常通知,出现异常就会执行
    public void AfterThrow(Exception e)
        System.out.println("异常通知: " + e.getMessage());
    

    /*环绕通知,有返回值,目标类具体方法的执行前后都可通过环绕通知定义响应处理,
    需要通过显示调用的方法,否则无法访问指定方法 pjp.proceed();
     */
    public Object  AroundCut(ProceedingJoinPoint pjp)
        System.out.println("环绕通知——前置通知");
        Object ob = null;

        try 
            //显示调用对应的方法,这里是实例cut()
            ob = pjp.proceed();
            System.out.println("目标对象: " + pjp.getTarget());
            System.out.println("环绕通知——返回通知");
        catch (Throwable throwable) 
            System.out.println("环绕通知的异常!");
            throwable.printStackTrace();
        

        System.out.println("环绕通知——最终通知");
        return ob;
    

以上是关于Spring——AOP的主要内容,如果未能解决你的问题,请参考以下文章

spring面试题

必备技能:spring aop 切入点表达式,你都会么?

spring aop切面表达式详解及例子

Spring AOP

spring-AOP动态代理

AOP