当前位置: 首页 > news >正文

spring5(五):AOP操作

spring5(五):AOP操作

  • 前言
  • 一、代理模式
    • 1、场景模拟
    • 2、代理模式
      • 2.1 概念
      • 2.2 静态代理
      • 2.3 动态代理
  • 二、AOP概述
    • 1、什么是 AOP?
    • 2、相关术语
    • 3、作用
  • 三、AOP底层原理
    • 1、AOP 底层使用动态代理
    • 2、AOP(JDK 动态代理)
      • 2.1 编写 JDK 动态代理代码
  • 四、AOP 操作的准备工作
    • 1、AspectJ概述
    • 2、依赖的引入
  • 五、AOP操作(注解版)
    • 1、添加依赖
    • 2、准备被代理的目标资源
    • 3、在Spring的配置文件中配置
    • 4、创建切面类并配置
    • 5、测试
  • 六、重点
    • 1、 切入点表达式
    • 2、重用切入点表达式
    • 3、获取通知的相关信息
    • 4、环绕通知
    • 5、切面的优先级
    • 6、完全使用注解开发
  • 七、AOP 操作(AspectJ 配置文件)
    • 1、创建两个类,增强类和被增强类,创建方法
    • 2、在 spring 配置文件中创建两个类对象
    • 3、在 spring 配置文件中配置切入点



前言

本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远!让我们在成长的道路上互相学习,欢迎关注!

一、代理模式

1、场景模拟

(1)声明计算器接口Calculator,包含加减乘除的抽象方法

public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}

(2)创建实现类
在这里插入图片描述

public class CalculatorPureImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}

(3)创建带日志功能的实现类
在这里插入图片描述

public class CalculatorLogImpl implements Calculator {@Overridepublic int add(int i, int j) {System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);int result = i + j;System.out.println("方法内部 result = " + result);System.out.println("[日志] add 方法结束了,结果是:" + result);return result;}@Overridepublic int sub(int i, int j) {System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);int result = i - j;System.out.println("方法内部 result = " + result);System.out.println("[日志] sub 方法结束了,结果是:" + result);return result;}@Overridepublic int mul(int i, int j) {System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);int result = i * j;System.out.println("方法内部 result = " + result);System.out.println("[日志] mul 方法结束了,结果是:" + result);return result;}@Overridepublic int div(int i, int j) {System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);int result = i / j;System.out.println("方法内部 result = " + result);System.out.println("[日志] div 方法结束了,结果是:" + result);return result;}
}

(4)提出问题

现有代码缺陷:
针对带日志功能的实现类,我们发现有如下缺陷: 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
附加功能分散在各个业务功能方法中,不利于统一维护
解决思路:
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
困难:
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。 所以需要引入新的技术。

2、代理模式

2.1 概念

①介绍

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

⭕ 使用代理前:
在这里插入图片描述

⭕ 使用代理后:
在这里插入图片描述

②生活中的代理

● 广告商找大明星拍广告需要经过经纪人
● 合作伙伴找大老板谈合作要约见面时间需要经过秘书
● 房产中介是买卖双方的代理

③相关术语

● 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。
● 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。

2.2 静态代理

public class CalculatorStaticProxy implements Calculator {// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target = target;}@Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);// 通过目标对象来实现核心业务逻辑int addResult = target.add(i, j);System.out.println("[日志] add 方法结束了,结果是:" + addResult);return addResult;}
}

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来
说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代
码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理
类来实现。这就需要使用动态代理技术了

2.3 动态代理

在这里插入图片描述
生产代理对象的工厂类:

public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy(){/*** newProxyInstance():创建一个代理实例* 其中有三个参数:* 1、classLoader:加载动态生成的代理类的类加载器* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法*/ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** proxy:代理对象* method:代理对象需要实现的方法,即其中需要重写的方法* args:method所对应方法的参数*/Object result = null;try {System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));result = method.invoke(target, args);System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);} catch (Exception e) {e.printStackTrace();System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());} finally {System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");}return result;}};return Proxy.newProxyInstance(classLoader, interfaces,invocationHandler);}
}

测试

@Test
public void testDynamicProxy(){ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());Calculator proxy = (Calculator) factory.getProxy();proxy.div(1,0);//proxy.div(1,1);
}

二、AOP概述

1、什么是 AOP?

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

⭕ 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

2、相关术语

⭕ 横切关注点

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点,比如上例中CalculatorImpl类中的日志代码是非核心代码,我们把这些非核心代码抽取出来,作为一些附加功能,即横切关注点。
附加功能相对于目标类来说是横切关注点,相对于目标类来说是通知。

在这里插入图片描述
⭕ 通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  1. 前置通知:在被代理的目标方法前执行
  2. 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
  3. 异常通知:在被代理的目标方法异常结束后执行(死于非命)
  4. 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
  5. 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

在这里插入图片描述

⭕ 切面

封装通知方法的类。

在这里插入图片描述
⭕目标

被代理的目标对象。

⭕代理

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

⭕连接点

这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。

在这里插入图片描述
⭕切入点

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

3、作用

  1. 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
  2. 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

三、AOP底层原理

1、AOP 底层使用动态代理

有两种情况动态代理:

⭕ 第一种 有接口情况,使用 JDK 动态代理

创建接口实现类代理对象,增强类的方法

在这里插入图片描述

⭕ 第二种 没有接口情况,使用 cglib 动态代理
创建子类的代理对象,增强类的方法

在这里插入图片描述

2、AOP(JDK 动态代理)

① 使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象

在这里插入图片描述

② 调用 newProxyInstance() 方法
方法有三个参数:

  1. 第一参数,类加载器
  2. 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
  3. 第三参数,实现这个接口InvocationHandler,创建代理对象,写增强的部分

在这里插入图片描述

2.1 编写 JDK 动态代理代码

① 创建接口,定义方法


public interface UserDao {public int add(int a,int b);public String update(String id);
}

② 创建接口实现类,实现方法

public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {System.out.println("add方法执行了.....");return a+b;}@Overridepublic String update(String id) {System.out.println("update方法执行了.....");return id;}
}

③ 使用 Proxy 类创建接口代理对象

public class JDKProxy {public static void main(String[] args) {//创建接口实现类代理对象Class[] interfaces = {UserDao.class};
//        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                return null;
//            }
//        });UserDaoImpl userDao = new UserDaoImpl();UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));int result = dao.add(1, 2);System.out.println("result:"+result);}
}//创建代理对象代码
class UserDaoProxy implements InvocationHandler {//1 把创建的是谁的代理对象,把谁传递过来//有参数构造传递private Object obj;public UserDaoProxy(Object obj) {this.obj = obj;}//增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法之前System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));//被增强的方法执行Object res = method.invoke(obj, args);//方法之后System.out.println("方法之后执行...."+obj);return res;}
}

四、AOP 操作的准备工作

1、AspectJ概述

Spring 框架一般都是基于 AspectJ 实现 AOP 操作
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJSpirng 框架一起使用,进行 AOP操作

⭕ 基于 AspectJ 实现 AOP 操作

  1. 基于 xml 配置文件实现
  2. 基于注解方式实现

⭕ 在项目工程里面引入 AOP 相关依赖

2、依赖的引入

五、AOP操作(注解版)

1、添加依赖

IOC所需依赖基础上再加入下面依赖即可:

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version>
</dependency>

2、准备被代理的目标资源

接口:

public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}

实现类:

@Component
public class CalculatorPureImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int sub(int i, int j) {int result = i - j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int mul(int i, int j) {int result = i * j;System.out.println("方法内部 result = " + result);return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);return result;}
}

3、在Spring的配置文件中配置

<!--
基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<context:component-scan base-package="com.atguigu.aop.annotation">
</context:component-scan>
<aop:aspectj-autoproxy />

4、创建切面类并配置

package com.reds.AOPTest;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** Date:2022/7/4* Author:ybc* Description:* 1、在切面中,需要通过指定的注解将方法标识为通知方法* @Before:前置通知,在目标对象方法执行之前执行* @After:后置通知,在目标对象方法的finally字句中执行* @AfterReturning:返回通知,在目标对象方法返回值之后执行* @AfterThrowing:异常通知,在目标对象方法的catch字句中执行*** 2、切入点表达式:设置在标识通知的注解的value属性中* execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int)* execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..)* 第一个*表示任意的访问修饰符和返回值类型* 第二个*表示类中任意的方法* ..表示任意的参数列表* 类的地方也可以使用*,表示包下所有的类* 3、重用切入点表达式* //@Pointcut声明一个公共的切入点表达式* @Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")* public void pointCut(){}* 使用方式:@Before("pointCut()")** 4、获取连接点的信息* 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息* //获取连接点所对应方法的签名信息* Signature signature = joinPoint.getSignature();* //获取连接点所对应方法的参数* Object[] args = joinPoint.getArgs();** 5、切面的优先级* 可以通过@Order注解的value属性设置优先级,默认值Integer的最大值* @Order注解的value属性值越小,优先级越高**/
@Component
@Aspect //将当前组件标识为切面
public class LoggerAspect {@Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")public void pointCut(){}//@Before("execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int))")//@Before("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")@Before("pointCut()")public void beforeAdviceMethod(JoinPoint joinPoint) {//获取连接点所对应方法的签名信息Signature signature = joinPoint.getSignature();//获取连接点所对应方法的参数Object[] args = joinPoint.getArgs();System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));}@After("pointCut()")public void afterAdviceMethod(JoinPoint joinPoint){//获取连接点所对应方法的签名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",执行完毕");}/*** 在返回通知中若要获取目标对象方法的返回值* 只需要通过@AfterReturning注解的returning属性* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数*/@AfterReturning(value = "pointCut()", returning = "result")public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){//获取连接点所对应方法的签名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);}/*** 在异常通知中若要获取目标对象方法的异常* 只需要通过AfterThrowing注解的throwing属性* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数*/@AfterThrowing(value = "pointCut()", throwing = "ex")public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){//获取连接点所对应方法的签名信息Signature signature = joinPoint.getSignature();System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);}@Around("pointCut()")//环绕通知的方法的返回值一定要和目标对象方法的返回值一致public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){Object result = null;try {System.out.println("环绕通知-->前置通知");//表示目标对象方法的执行result = joinPoint.proceed();System.out.println("环绕通知-->返回通知");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知-->异常通知");} finally {System.out.println("环绕通知-->后置通知");}return result;}}

5、测试

public class AOPByAnnotationTest {@Test
public void testAOPByAnnotation(){ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");//Calculator :代理对象和目标对象共同实现的接口Calculator calculator = ioc.getBean(Calculator.class);calculator.div(10, 1);
}

如果直接获取目标类,此时会报无法找到实例的错误。
原因是:
我们为目标对象创建了代理对象,因此我们每次想访问目标对象中的方法时都不能直接通过目标对象去访问,必须通过代理对象来间接访问目标对象,进而去访问目标对象中的方法,所以我们下一步应该是去获取代理对象,可是我们并不知道代理对象是什么,但是我们知道代理对象跟我们的目标对象实现的是相同的接口,我们之前通过IOC获取某个bean的时候,我们不需要通过某个具体的类型来获取,我们可以用它所继承的父类或者所实现的接口,都可以获取到bean。

六、重点

1、 切入点表达式

作用

知道对哪个类里面的哪个方法进行增强

在这里插入图片描述

语法结构

execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )

为了方便,权限修饰符一般使用* 代替 ,返回类型一般省略不写

⭕ 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello

⭕ 在包名的部分,使用“*..”表示包名任意、包的层次深度任意

⭕ 在类名的部分,类名部分整体用*号代替,表示类名任意

⭕ 在类名的部分,可以使用*号代替类名的一部分
例如:*Service匹配所有名称以Service结尾的类或接口

⭕ 在方法名部分,可以使用*号表示方法名任意

⭕ 在方法名部分,可以使用*号代替方法名的一部分
例如:*Operation匹配所有方法名以Operation结尾的方法

⭕ 在方法参数列表部分,使用(..)表示参数列表任意

⭕ 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头

⭕ 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的

⭕ 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
例如:execution(public int ..Service.*(.., int)) 正确
例如:execution(* int ..Service.*(.., int)) 错误


举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(*com.atguigu.dao.BookDao.add(..))
举例 2:对 com.atguigu.dao.BookDao类里面的所有的方法进行增强 execution(* com.atguigu.dao.BookDao.* (..))
举例 3:对com.atguigu.dao 包里面所有类,类里面所有方法进行增强 execution(* com.atguigu.dao.*.* (..))

在这里插入图片描述

2、重用切入点表达式

①声明

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
public void pointCut(){}

②在同一个切面中使用

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③在不同切面中使用

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

3、获取通知的相关信息

①获取连接点信息

获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){//获取连接点的签名信息String methodName = joinPoint.getSignature().getName();//获取目标方法到的实参信息String args = Arrays.toString(joinPoint.getArgs());System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

②获取目标方法的返回值

@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

@AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③获取目标方法的异常

@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

@AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName = joinPoint.getSignature().getName();System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

4、环绕通知

@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());Object result = null;try {System.out.println("环绕通知-->目标对象方法执行之前");//目标方法的执行,目标方法的返回值一定要返回给外界调用者result = joinPoint.proceed();System.out.println("环绕通知-->目标对象方法返回值之后");} catch (Throwable throwable) {throwable.printStackTrace();System.out.println("环绕通知-->目标对象方法出现异常时");} finally {System.out.println("环绕通知-->目标对象方法执行完毕");}return result;
}

5、切面的优先级

⭕ 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  1. 优先级高的切面:外面
  2. 优先级低的切面:里面

⭕ 使用@Order注解可以控制切面的优先级:

  1. @Order(较小的数):优先级高
  2. @Order(较大的数):优先级低

在这里插入图片描述

6、完全使用注解开发

创建配置类,不需要创建 xml 配置文件

@Configuration
@ComponentScan(basePackages = {"aopanno"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {}

七、AOP 操作(AspectJ 配置文件)

1、创建两个类,增强类和被增强类,创建方法

Book类

public class Book {public void buy() {System.out.println("buy.............");}
}

BookProxy类

public class BookProxy {public void before() {System.out.println("before.........");}
}

2、在 spring 配置文件中创建两个类对象

 <!--创建对象--><bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean><bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>

3、在 spring 配置文件中配置切入点

<!--配置aop增强--><aop:config><!--切入点--><aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/><!--配置切面--><aop:aspect ref="bookProxy"><!--增强作用在具体的方法上--><aop:before method="before" pointcut-ref="p"/></aop:aspect></aop:config>

相关文章:

spring5(五):AOP操作

spring5&#xff08;五&#xff09;&#xff1a;AOP操作前言一、代理模式1、场景模拟2、代理模式2.1 概念2.2 静态代理2.3 动态代理二、AOP概述1、什么是 AOP?2、相关术语3、作用三、AOP底层原理1、AOP 底层使用动态代理2、AOP&#xff08;JDK 动态代理&#xff09;2.1 编写 J…...

functional.partial

functional.partial__slots____new__中的cls, /是什么意思&#xff1f;functools.partial这个partial类有什么作用类中没有__init__函数Python 内置的 functools.partial 类的实现。这个类可以用来创建一个新的函数对象&#xff0c;该对象是对一个原有函数的参数进行了部分应用…...

C#缩放PDF文件

项目上有个功能需求&#xff1a;将原PDF进行缩放至原先的90%大小。 使用的是spire.pdf插件&#xff0c;但是官方文档上的缩放只是改变显示&#xff0c;最终文件其实没有缩放成功。遂找到了另外的方式进行重绘。 上代码&#xff1a; using Spire.Pdf; using Spire.Pdf.Graphi…...

【Java面试八股文宝典之MySQL篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day20

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三即将实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…...

Nsight System的安装和使用

本地安装 官方网站&#xff0c;需要登录 选择Windows Host下载安装 服务器安装 选择Linux CLI .deb下载&#xff0c;上传到服务器之后&#xff0c;执行以下命令&#xff0c;默认会安装在/opt/nvidia/nsight-systems-cli/2023.2.1/target-linux-x64/&#xff0c;nsys在/usr/lo…...

Spring销毁的几种实现

有这3种方法&#xff0c;但是程序执行完成并没有打印出来。一定要手动close.手动执行后会调用如下逻辑&#xff1a;org.springframework.context.support.AbstractApplicationContext#doCloseorg.springframework.context.support.AbstractApplicationContext#destroyBeansorg.…...

【 Spring 核⼼与设计思想 】

文章目录一、Spring 是什么1.1 什么是容器1.2 什么是 IoC二、开发案例对比2.1 传统程序开发2.2 控制反转式程序开发2.3 对⽐总结规律三、理解 Spring IoC四、DI 概念说明五、总结一、Spring 是什么 我们通常所说的 Spring 指的是 Spring Framework&#xff08;Spring 框架&…...

Arrays.sort()——逆序

package utils;import java.util.*;class ComparatorInteger implements Comparator<Integer> {Override //使得逆序 o1比o2小&#xff0c;返回正数——需要调换位置public int compare(Integer o1, Integer o2) {return o1 < o2 ? 1 : -1;} }class Comparato…...

测试2年遇到瓶颈,如何跨过这个坎,实现涨薪5k?

最近和字节跳动的一个老朋友闲聊&#xff0c;感触颇深&#xff0c;据他说公司近期招聘的测试工程师&#xff0c;大多数候选人都有一个“通病”&#xff1a;在工作2-3年的时候遇到瓶颈&#xff0c;而且是一道很难跨越的坎。为什么会遇到这种情况&#xff1f;因为大部分测试工程师…...

骑行团队怎样才能健康运行?

随着生活水平的提高&#xff0c;自行车运动在国内逐渐被人们所接受&#xff0c;也有越来越多的人加入到骑行的行列中。特别是现在骑行团队的兴起&#xff0c;不仅带动了自行车运动的发展&#xff0c;也带动了整个自行车行业的发展。骑行队就是由一群志同道合的车友组成&#xf…...

动力节点王鹤SpringBoot3学习笔记——第四章 访问数据库

目录 第四章 访问数据库 4.1 DataSource 4.2 轻量的JdbcTemplate 4.2.1 准备环境 4.2.1.1 准备数据库和表脚本 4.2.1.2 创建Spring Boot工程 4.2.2 JdbcTemplate访问MySQL 4.2.3 NamedParameterJdbcTemplate 4.2.4 多表查询 4.3 MyBatis 4.3.1 单表CRUD 4.3…...

segno.helpers.make_mecard(Python)

制作名片二维码的&#xff0c;浅浅的mark一下参数的东西。 官方文档是这么写的&#xff1a; segno.helpers.make_mecard(name, readingNone, emailNone, phoneNone, videophoneNone, memoNone, nicknameNone, birthdayNone, urlNone, poboxNone, roomnoNone, housenoNone, ci…...

OBCP第八章 OB运维、监控与异常处理-日常运维操作

白屏&#xff1a; 集群、Zone、Observer 常用运维操作 常用运维操作 运维场景步骤时钟同步 OceanBase从Partition的多个副本中选出主对外提供服务。为避免Paxos的活锁问题&#xff0c;OceanBase 采用一种基于时钟的选举算法选主 检查 NTP 状态&#xff1a;运行 ntpstat 检查 N…...

springboot-gateway注册nacos失败,控制台没有报错

目录 前言现象描述前言 最近springboot的gateway注册到nacos上,没有注册成功 现象描述 我是在common里面引入了nacos的依赖,依赖如下: <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-confi…...

CLIP:语言-图像表示之间的桥梁

最近GPT4的火爆覆盖了一个新闻&#xff1a;midjourney v5发布&#xff0c;DALLE2&#xff0c;midjourney都可以从文本中生成图像&#xff0c;这种模型要求人工智能同时理解语言和图像数据。 传统的基于人工智能的模型很难同时理解语言和图像。因为自然语言处理和计算机视觉一直…...

failed: open /etc/resolv.conf: no such file or directory“ cause k8s init failed

kubeadm init报错 kubeadm init --config /etc/kubernetes/kubeadm.conf -v 4 --skip-phasesaddon/kube-proxyThis can take up to 4m0s", “[kubelet-check] Initial timeout of 40s passed.”, “”, “\tUnfortunately, an error has occurred:”, “\t\ttimed out wa…...

「科普」如何评价供应商的MES系统

随着制造业的数字化转型&#xff0c;MES系统作为生产信息化的重要组成部分&#xff0c;正在被越来越多的企业采用。然而&#xff0c;在选择供应商时&#xff0c;如何评价供应商的MES系统&#xff0c;成为了制造企业需要面对的一个难题。 首先&#xff0c;评价供应商的MES系统需…...

海康3D轮廓仪调试详细步骤

激光三角测量法 3D激光轮廓仪是基于激光三角测量法(laser triangulation)来重建三维场景。向被测物表面投射激光平面(光片&#xff0c;sheet of light) &#xff0c;通过使用CMOS相机接收其反射光的变化&#xff0c;可以非接触方式测量高度、高度差、宽度等轮廓&#xff08;截面…...

【Linux】PCB(进程控制块)

进程控制块PBC-描述进程号进程状态内存指针PBC-描述 我们知道&#xff0c;进程就是运行起来的代码&#xff0c;而操作系统就是通过对进程进行描述&#xff0c;然后将所有的进程使用双向链表串联到一起&#xff0c;实现对计算机软硬件资源的管理的。 那么&#xff0c;PCB到底是…...

风电的Weibull分布及光电的Beta分布组合研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...