一看完就会的spring核心AOP(动态代理...)

2.AOP面向切面编程

AOP:(Aspect Oriented Programming)面向切面编程;

OOP:(Object Oriented Programming )面向对象编程;

面向切面编程:基于OOP基础之上新的编程思想;指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程; 场景:计算器运行计算方法的时候进行日志记录;加日志记录:

1.面向对象(OOP)

  • 直接编写在方法内部;不推荐,修改维护麻烦;

  • 日志记录:系统的辅助功能;

  • 业务逻辑:(核心功能) 耦合高;

2)、我们希望的是 (AOP)

  • 业务逻辑:(核心功能;

  • 日志模块;在核心功能运行期间,自己动态的加上;

  • 运行的时候,日志功能可以加上;

1.jdk的动态代理

jdk动态代理:

  • 1)、写起来难;

  • 2)、jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的;

 

Spring实现了AOP功能底层就是动态代理

  1. 可以利用Spring一句代码都不写的去创建动态代理 实现简单,而且没有强制要求目标对象必须实现接口;将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法(加减乘除)的指定位置(方法的开始、结束、异常。。。)进行运行的这种编程方式(Spring简化了面向切面编程)

实现oop模拟计算器动态代理

创建接口

package com.aop.inter;
​
/**
 * @author 谭铖
 * @date 2021/6/12 13:46
 */
public interface number {
        public int add(int i,int j);
        public int sub(int i,int j);
        public int mul(int i,int j);
        public int div(int i,int j);
}

实现类

package com.aop.impl;
import com.aop.inter.number;
​
/**
 * @author 谭铖
 * @date 2021/6/12 13:46
 */
public class numberimpl implements number {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
​
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
​
    @Override
    public int mul(int i, int j) {
        //方法的兼容性;
        int result = i * j;
        return result;
    }
​
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

代理类

package com.aop.proxy;
​
import com.aop.impl.numberimpl;
import com.aop.inter.number;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
​
/**
 * @author 谭铖
 * @date 2021/6/12 13:48
 */
public class numberImplProxy  {
//    private number number1;
//
//    public numberImplProxy(number number1) {
//        this.number1 = number1;
//    }
//
//    @Override
//    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        System.out.println("这是动态代理将要帮你执行方法...");
//        Object result = null;
//        System.out.println(method.getName() + "方法执行参数为[" + Arrays.asList(args) + "]");
//        result = method.invoke(number1, args);
//        System.out.println(method.getName()+"方法执行完毕值为:"+result+"");
//        return result;
//
//    }
    /**
     * 为传入的参数对象创建一个动态代理对象
     * jdk层面动态代理
     * @param number1 接口
     * @return 计算的值
     */
    public static number getProxy(number number1) {
        //方法执行器。帮我们目标对象执行目标方法
        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * Object proxy:代理对象;给jdk使用,任何时候都不要动这个对象
             * Method method:当前将要执行的目标对象的方法
             * Object[] args:这个方法调用时外界传入的参数值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 利用反射执行目标方法
                //目标方法执行后的返回值
                System.out.println("这是动态代理将要帮你执行方法...");
                Object result = null;
                System.out.println(method.getName()+"方法执行参数为["+ Arrays.asList(args) +"]");
                result = method.invoke(number1, args);
                System.out.println(method.getName()+"方法执行完毕值为:"+result+"");
                return result;
            }
        };
//    获取全部接口
        Class<?>[] interfaces = number1.getClass().getInterfaces();
//    获取类加载器
        ClassLoader classLoader = number1.getClass().getClassLoader();
//Proxy为目标对象创建代理对象;
        Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return (com.aop.inter.number) proxyInstance;
    }
}

测试类

package com.aop.junit;
​
import com.aop.impl.numberimpl;
import com.aop.inter.number;
import com.aop.proxy.numberImplProxy;
import org.junit.Test;
​
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
​
/**
 * @author 谭铖
 * @date 2021/6/12 14:06
 */
public class numberTest {
    @Test
    public void Test01() {
        number number = new numberimpl();
//        InvocationHandler handler = new numberImplProxy(number);
//        number newProxyInstance = (com.aop.inter.number) Proxy.newProxyInstance(number.getClass().getClassLoader(), number.getClass().getInterfaces()
//                , handler);
//        newProxyInstance.add(1,2);
        number proxy = numberImplProxy.getProxy(number);
//      com.sun.proxy.$Proxy2也是实现了Calculator接口
//      代理对象和被代理对象唯一能产生的关联就是实现了同一个接口
        System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
        proxy.add(12, 2);
        proxy.sub(2, 4);
    }
}

2.AOP动态代理

  • 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

  • 通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

  • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

 

  • 5个通知注解

  • @Before:在目标方法之前运行; 前置通知

  • @After:在目标方法结束之后 后置通知

  • @AfterReturning:在目标方法正常返回之后 返回通知

  • @AfterThrowing:在目标方法抛出异常之后运行 异常通知

  • @Around:环绕 环绕通知

1.AOP动态代理使用测试

准备工作

AOP使用步骤:

1)、导包

    commons-logging-1.1.3.jar
    spring-aop-4.0.0.RELEASE.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE.jar

 

Spring支持面向切面编程的包是:

基础版

spring-aspects-4.0.0.RELEASE.jar:

加强版的面向切面编程(即使目标对象没有实现任何接口也能创建动态代理)

com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

2)、写配置

  • 将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中

  • 还应该告诉Spring到底哪个是切面类@Aspect

  • 告诉Spring,切面类里面的每一个方法,都是何时何地运行;

实现类

package com.tc.impl;
​
import com.tc.inter.number;
import org.springframework.stereotype.Service;
​
/**
 * @author 谭铖
 * @date 2021/6/12 13:46
 */
@Service
public class numberimpl  {
    //@Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }
​
    //@Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
​
   // @Override
    public int mul(int i, int j) {
        //方法的兼容性;
        int result = i * j;
        return result;
    }
​
   // @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}

AOP动态代理类

package com.tc.utils;
​
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
​
import java.lang.reflect.Method;
import java.util.Arrays;
​
/**
 * @author 谭铖
 * @date 2021/6/12 16:54
 * 如何将这个类(切面类)中的这些方法(通知方法)动态的在目标方法运行的各个位置切入
 */
//通用的ioc依赖注解
@Component
//切面
@Aspect
public class LogUtils {
    /**
     *  5个通知注解
     *  @Before:在目标方法之前运行;                 前置通知
     *  @After:在目标方法结束之后                  后置通知
     *     @AfterReturning:在目标方法正常返回之后          返回通知
     *  @AfterThrowing:在目标方法抛出异常之后运行      异常通知
     *     @Around:环绕                           环绕通知
     */
    @Before(value = "execution(public int com.tc.impl.num*.*(..))")
//    JoinPoint:连接点 获取封装了当前目标方法的详细信息
    public static void logStart(JoinPoint joinPoint){
//        获取方法传入参数
        Object[] args = joinPoint.getArgs();
//        获取到方法名
        String name = joinPoint.getSignature().getName();
        System.out.println("【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
    }
    /**
     * 切入点表达式的写法;
     * 固定格式: execution(访问权限符  返回值类型  方法全类名(参数表))
     *
     * 通配符:
     *        *:
     *           1)匹配一个或者多个字符:execution(public int com.atguigu.impl.MyMath*r.*(int, int))
     *           2)匹配任意一个参数:第一个是int类型,第二个参数任意类型;(匹配两个参数)
     *              execution(public int com.atguigu.impl.MyMath*.*(int, *))
     *           3)只能匹配一层路径
     *           4)权限位置*不能;权限位置不写就行;public【可选的】
     *        ..:
     *           1)匹配任意多个参数,任意类型参数
     *           2)匹配任意多层路径:
     *              execution(public int com.atguigu..MyMath*.*(..));
     *
     * 记住两种;
     * 最精确的:execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
     * 最模糊的:execution(* *.*(..)):千万别写;
     *
     * &&”、“||”、“!
     *
     * &&:我们要切入的位置满足这两个表达式
     *     MyMathCalculator.add(int,double)
     * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     *
     * ||:满足任意一个表达式即可
     * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     * !:只要不是这个位置都切入
     * !execution(public int com.atguigu..MyMath*.*(..))
     *
     * 告诉Spring这个result用来接收返回值:
     *     returning="result";
     */
    @AfterReturning(value = "execution(public int com.tc.impl.numberimpl.*(..))",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result){
        System.out.println("【"+joinPoint.getSignature().getName()+"】方法正常执行完成,计算结果是:"+result+"");
    }
    @AfterThrowing(value = "execution(public int com.tc.impl.numberimpl.*(..))",throwing = "exception")
    public static void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("【】方法执行出现异常了,异常信息是:"+exception+";这个异常已经通知测试小组进行排查");
    }
    @After("execution(public int com.tc.impl.numberimpl.*(int ,int ))")
    public static void logEnd(JoinPoint joinPoint) {
        System.out.println("【"+joinPoint.getSignature().getName()+"】方法最终结束了");
    }
}

xml配置依赖注入和AOP自动配置

<?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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    依赖注入-->
    <Context:component-scan base-package="com.tc">
​
    </Context:component-scan>
<!--    AOP自动配置-->
    <aop:aspectj-autoproxy>
    </aop:aspectj-autoproxy>
</beans>

test测试类

package com.tc.junit;
​
import com.tc.impl.numberimpl;
import com.tc.inter.number;
​
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
​
​
/**
 * @author 谭铖
 * @date 2021/6/12 14:06
 */
/**
 commons-logging-1.1.3.jar
 spring-aop-4.0.0.RELEASE.jar
 spring-beans-4.0.0.RELEASE.jar
 spring-context-4.0.0.RELEASE.jar
 spring-core-4.0.0.RELEASE.jar
 spring-expression-4.0.0.RELEASE.jar
 spring-aspects-4.0.0.RELEASE.jar:基础版
​
 加强版的面向切面编程(即使目标对象没有实现任何接口也能创建动态代理)
 com.springsource.net.sf.cglib-2.2.0.jar
 com.springsource.org.aopalliance-1.0.0.jar
 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
​
 *
 */
public class numberTest {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("conf/AOP.xml");
    @Test
    public void Test01() {
        //1、从ioc容器中拿到目标对象;注意:如果想要用类型,一定用 他的接口类型,不要用它本类
        //细节一:com.atguigu.impl.MyMathCalculator@6c64cb25
        //class com.sun.proxy.$Proxy12
        //AOP的底层就是动态代理,容器中保存的组件是他的代理对象;$Proxy12。当然不是本类的类型
        number bean = ioc.getBean(number.class);
        bean.div(1,2);
        System.out.println(bean);
        System.out.println(bean.getClass());
    }
    @Test
    public void Test02() {
        /**
         * 正常执行:  @Before(前置通知)=====@After(后置通知)====@AfterReturning(正常返回);
         * 异常执行: @Before(前置通知)=====@After(后置通知)===@AfterThrowing(方法异常)
         */
        //没有接口就是本类类型 有接口就本类类型
        //class com.atguigu.impl.MyMathCalculator$$EnhancerByCGLIB$$fe279f42
        //cglib帮我们创建好的代理对象
//        没有实现接口就直接用本身类
//        numberimpl bean = (numberimpl) ioc.getBean("numberimpl"); 根据id
//        根据类名
        numberimpl bean = ioc.getBean(numberimpl.class);
        bean.div(1,1);
        System.out.println(bean);
    }
}

切入点(Pointcut):

 /**
     * 切入点(Pointcut):抽取可重复利用的切入点表达式
     * 可以配置好然后调用
     */
    @Pointcut("execution(public int  com.tc.impl.num*.*(..))")
    public void myPointcut(){}
​
//  @Before:在目标方法之前运行;                      前置通知
    @Before(value = "myPointcut()")
//    JoinPoint:连接点 获取封装了当前目标方法的详细信息
    public static void logStart(JoinPoint joinPoint){
//        获取方法传入参数
        Object[] args = joinPoint.getArgs();
//        获取到方法名
        String name = joinPoint.getSignature().getName();
        System.out.println("【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
    }

环绕通知:spring最强大

/**
     * 环绕通知:Around(切入点表达式execution)
     * @param point
     * @return
     */
    @Around("myPointcut()")
    public  Object myAround(ProceedingJoinPoint point){
        Object pro = null;
//        获取方法名
        String name = point.getSignature().getName();
//        方法参数
        Object[] args = point.getArgs();
        try {
//       前置通知@before
            System.out.println("环绕通知运行"+name+"方法{"+Arrays.asList(args)+"}");
            pro = point.proceed();
//       @afterReturning
            System.out.println("环绕通知运行:"+pro);
        } catch (Throwable throwable) {
//       @AfterThrowing
            System.out.println("环绕通知运行发生了异常:"+throwable);
            throw new  RuntimeException(throwable);
        }finally {
//      @After
            System.out.println("环绕通知运行over");
        }
        return pro;
    }

多个切面

  1. 多个切面的话按照压栈一样

  2. 通过@Order注解可以控制切面执行顺序(数字越小优先级越高)

  3.  

3.配置XML的切面

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:config="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--       配置ioc依赖注入-->
    <bean class="com.aopXML.impl.numberimpl" id="numberimpl"/>
    <bean class="com.aopXML.utils.LogUtils" id="logUtils"/>
    <bean class="com.aopXML.utils.myUtils" id="myUtils"/>
<!--    配置AOP的切面-->
    <aop:config>
<!--            配置重用的切入点表达式-->
        <aop:pointcut id="myPoint" expression="execution(public int  com.aopXML.impl.num*.*(..))"/>
<!--        配置切面-->
        <aop:aspect ref="myUtils" order="2">
<!--            before-->
            <aop:before method="logStart" pointcut-ref="myPoint"/>
<!--         after-throwing   -->
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="exception"/>
<!--         after-returning-->
            <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
<!--         after-->
            <aop:after method="logEnd" pointcut-ref="myPoint"/>
        </aop:aspect>
​
        <aop:aspect ref="logUtils" order="1">
            <aop:around method="myAround" pointcut-ref="myPoint"/>
            <aop:before method="logStart" pointcut-ref="myPoint"/>
            <aop:after-returning method="logReturn" pointcut-ref="myPoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="exception"/>
            <aop:after method="logEnd" pointcut-ref="myPoint"/>
        </aop:aspect>
    </aop:config>
</beans>

版权声明:本文为qq_46341024原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。