(2)Spring————AOP详解

1,AOP的前奏

2,AOP概述

3,AOP细节

4,以XML方式配置切面

AOP的前奏

1,动态代理

  • 动态代理的原理

    使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

  • 动态代理的方式

    基于接口实现动态代理: JDK动态代理

    基于继承实现动态代理: Cglib、Javassist动态代理

  • 示例代码:

  • IProduct接口:

public interface IProduct {

    /**
     * 销售
     * @param money
     */
    void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
    void afterService(float money);
}
  • Product类:
public class Product implements IProduct {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,得到钱"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,拿到钱"+money);
    }
}
  • 测试:
@Test
public void demo01() {
    final Product product = new Product();

    /**
     *  基于接口实现动态代理
     * ClassLoader:类加载器,用于加载字节码的,和被代理对象使用相同的类加载器,固定写法
     * Class[]:字节码数组,用于让代理对象和被代理对象有相同的方法。固定写法。
     *  InvocationHandler:用于提供增强的代码,是让我们如何写代理,我们一般是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的,此接口的实现类都是谁用谁写
     */
    IProduct proxyInstance = (IProduct)Proxy.newProxyInstance(product.getClass().getClassLoader(), product.getClass().getInterfaces(), new InvocationHandler() {
        /**
         * 作用:执行被代理对象的任何接口方法都会经过该方法
         * @param proxy  代理对象的引用
         * @param method 当前执行的方法
         * @param args   当前执行方法所需的参数
         * @return 和被代理对象方法有相同的返回值
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //执行增强代码
            Object returnVaule = null;
            //1,获取方法执行的参数
            Float money = (Float) args[0];
            //2,判断当前方法是不是销售
            if ("saleProduct".equals(method.getName())) {
                returnVaule = method.invoke(product, money * 0.4f);
            }
            return returnVaule;
        }
    });
    proxyInstance.saleProduct(100000f);
}
AOP概述

1, AOP概述

①,AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。

  • 面向对象 纵向继承机制(功能的添加)

  • 面向切面 横向抽取机制(功能的抽取)

②,AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)。

③,在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。

④,AOP的好处:

  • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级

  • 业务模块更简洁,只包含核心业务代码

  • AOP图解
    在这里插入图片描述

2, AOP术语

  • 横切关注点

    从每个方法中抽取出来的同一类非核心业务。

  • 切面(Aspect)

    封装横切关注点信息的类,每个关注点体现为一个通知方法。

  • 通知(Advice)

    切面必须要完成的各个具体工作

  • 目标(Target)

    被通知的对象

  • 代理(Proxy)

    向目标对象应用通知之后创建的代理对象

  • 连接点(Joinpoint)

    横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。
    在这里插入图片描述

  • 切入点(pointcut)

    定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

  • 图解

    在这里插入图片描述

3, AspectJ

  • 简介

    AspectJ:Java社区里最完整最流行的AOP框架。

    在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

  • 用AspectJ注解声明切面

    ①,要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。

    ②,当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。

    ③,在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。

    ④,通知是标注有某种注解的简单的Java方法。

    ⑤,AspectJ支持5种类型的通知注解:

    • @Before:前置通知,在方法执行之前执行
    • @After:后置通知,在方法执行之后执行
    • @AfterRunning:返回通知,在方法返回结果之后执行
    • @AfterThrowing:异常通知,在方法抛出异常之后执行
    • @Around:环绕通知,围绕着方法执行
AOP细节

1, 切入点表达式

  • 作用:

    通过表达式的方式定位一个或多个具体的连接点。

  • 语法细节:

    切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

  • 举例说明:

表达式含义
execution(* com.atguigu.spring.ArithmeticCalculator.*(…))ArithmeticCalculator接口中声明的所有方法。第一个“”代表任意修饰符及任意返回值。第二个“”代表任意方法。“…”匹配任意数量、任意类型的参数。若目标类、接口与该切面类在同一个包中可以省略包名。
  • 在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
表达式含义
execution (* .add(int,…)) || execution( *.sub(int,…))任意类中第一个参数为int类型的add方法或sub方法
!execution (* *.add(int,…))匹配不是任意类中第一个参数为int类型的add方法
  • 切入点表达式应用到实际的切面类中
    在这里插入图片描述

2,当前连接点细节

  • 概述

    切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中。

  • JoinPoint
    在这里插入图片描述

3,通知

  • 概述

    ①,在具体的连接点上要执行的操作。

    ②,一个切面可以包括一个或者多个通知。

    ③,通知所使用的注解的值往往是切入点表达式。

  • 前置通知

    在方法执行之前执行的通知,使用@Before注解

  • 后置通知

    后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,使用@After注解

  • 返回通知

    ①,无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。

    ②,使用@AfterReturning注解,在返回通知中访问连接点的返回值

    • 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
    • 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
    • 原始的切点表达式需要出现在pointcut属性中
  • 异常通知

    ①,只在连接点抛出异常时才执行异常通知

    ②,将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

    ③,如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

  • 环绕通知

    ①,环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

    ②,对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

    ③,在环绕通知中需要明确调用ProceedingJoinPointproceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

    ④,注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

4,重用切入点定义

①,在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。

②,在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。

③,切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。

④,其他通知可以通过方法名称引入该切入点

在这里插入图片描述

5,指定切面的优先级

①,在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

②,切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

③,实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

④,若使用@Order注解,序号出现在注解中

  • 示例代码:
  • 导入jar包
    在这里插入图片描述
    在这里插入图片描述
  • aop.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:context="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">

    <context:component-scan base-package="com.aop"></context:component-scan>
    <!-- 开启aspectJ的自动代理功能 -->
    <aop:aspectj-autoproxy />
</beans>
  • MathI接口:
public interface MathI {

    int add(int i, int j);

    int sub(int i, int j);

    int div(int i, int j);
}
  • MathImpl类:
@Component
public class MathImpl implements MathI {


    @Override
    public int add(int i, int j) {
        return i +j;
    }

    @Override
    public int sub(int i, int j) {
        return i - j;
    }

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}
  • MyLoggerAspect类:
@Component
@Aspect//标注当前类为切面
@Order(1)//定义切面作用的优先级,值越小优先级越高,默认值为int的最大值2^31-1
public class MyLoggerAspect {

    @Pointcut(value = "execution(* com.aop.*.*(..))")
    public void pointcutTest(){
    }

    /**
     * @Before:将方法指定为前置通知,作用于方法执行之前
     * 必须设置value,其值为切入点表达式
     */
//    @Before(value = "execution(public int com.aop.MathImpl.add(int, int))")
//    @Before(value = "execution(* com.aop.*.*(..))")
    @Before(value = "pointcutTest()")
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();//获取方法的参数
        String methodName = joinPoint.getSignature().getName();//获取方法名
        System.out.println("方法名:"+methodName+"方法的参数:"+ Arrays.toString(args));
    }

    /**
     * @After:将方法标注为后置通知
     * 后置通知:作用于方法的finally语句块,即不管有没有异常都会执行
     */
    @After(value = "execution(* com.aop.*.*(..))")
    public void aferMethod(){
        System.out.println("后置通知");
    }

    /**
     * @AfterReturning:将方法标注为返回通知,用于方法执行之后
     * 可通过returning设置接收方法返回值的变量名
     * 要想在方法中使用,必须在方法的形参中设置和变量名相同的参数名的参数
     */
    @AfterReturning(value = "execution(* com.aop.*.*(..))", returning = "result")
    public void aferReturning(JoinPoint joinPoint, Object result){
        String method = joinPoint.getSignature().getName();
        System.out.println("返回通知,方法名"+method+"结果"+result);
    }

    /**
     * @AfterThrowing:将方法标注为异常通知(例外通知),作用于方法抛出异常
     * 可通过throwing设置接收方法返回的异常信息
     * 在参数列表中可通过具体的异常类型,来对指定的异常信息进行操作
     */
    @AfterThrowing(value = "execution(* com.aop.*.*(..))",throwing = "ex")
    public void aferThrowing(Exception ex){
        System.out.println("异常信息"+ex);
    }

    @Around(value = "execution(* com.aop.*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            //前置通知
            System.out.println("===前置通知===");
            result = joinPoint.proceed();
            //返回通知
            System.out.println("===返回通知===");
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            System.out.println("===异常通知===");
        }finally {
            //后置通知
            System.out.println("===后置通知===");
        }
        return -1;
    }

}
  • AopTest 类:
@Component
public class AopTest {

    public void test(){
        System.out.println("来测试一下");
    }
}
  • 切面类AspectTest:
@Component
@Aspect
@Order(200)
public class AspectTest {

    @Before(value = "execution(* com.aop.*.*(..))")
    public void before(){
        System.out.println("AspectTest ==== 前置通知");
    }
}
  • 测试类DemoTest :
public class DemoTest {

    @Test
    public void demo01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
        MathI mathImpl = ac.getBean("mathImpl", MathI.class);
        System.out.println(mathImpl.add(1,2));
        System.out.println(mathImpl.div(1,0));
        ac.getBean("aopTest",AopTest.class).test();
    }
}
以XML方式配置切面

1,概述

  • 除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。正常情况下,基于注解的声明要优先于基于XML的声明。通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的。由于AspectJ得到越来越多的 AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会。

2,配置细节

  • 在bean配置文件中,所有的Spring AOP配置都必须定义在<aop:config>元素内部。对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端bean实例。

  • 切面bean必须有一个标识符,供<aop:aspect>元素引用。
    在这里插入图片描述
    在这里插入图片描述

3,声明切入点

①,切入点使用<aop:pointcut>元素声明。

②,切入点必须定义在<aop:aspect>元素下,或者直接定义在<aop:config>元素下。

  • 定义在<aop:aspect>元素下:只对当前切面有效

  • 定义在<aop:config>元素下:对所有切面都有效

③,基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点。

在这里插入图片描述

4,声明通知

①,在aop名称空间中,每种通知类型都对应一个特定的XML元素。

②,通知元素需要使用<pointcut-ref>来引用切入点,或用<pointcut>直接嵌入切入点表达式。

③,method属性指定切面类中通知方法的名称
在这里插入图片描述


下一章,(3)Spring————Jdbc操作和声明式事务管理

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值