Spring-AOP(面向切面编程)

news/2025/1/22 20:26:47/文章来源:https://www.cnblogs.com/shenStudy/p/18686727

Spring-AOP(面向切面编程)

面向切面编程(Aspect Oriented Programming-AOP)是面向对象编程(Object Oriented Programming-OOP)的一种补充,二者是互补的编程范式。在OOP中,关键单元是类,而在AOP中关键单元则是横切关注点。面向对象编程关注于将现实世界中的实体抽象为对象,并通过对象之间的交互来模拟现实世界的复杂关系。面向切面关注于将横切关注点(如日志、安全、事务管理等)从核心业务逻辑中分离出来。

AOP框架是Spring的关键组件之一,虽然Spring IoC容器不依赖于AOP(这意味着如果不想使用AOP则不需要AOP相关依赖),但是AOP补充了Spring IoC,提供了一个非常强大的中间件解决方案。

问题引入

当前业务需要实现一个计算器功能的接口,并且在此接口的基础上具备打印计算前后的日志功能。

计算机接口

/*** 计算器接口*/
public interface Calculator {/*** 加** @param i 整数* @param j 整数* @return 计算结果*/int add(int i, int j);/*** 减** @param i 整数* @param j 整数* @return 计算结果*/int sub(int i, int j);/*** 乘** @param i 整数* @param j 整数* @return 计算结果*/int mul(int i, int j);/*** 除** @param i 整数* @param j 整数* @return 计算结果*/int div(int i, int j);
}

计算机核心功能(计算功能)实现类

/*** 计算器接口实现类*/
public class CalculatorImpl 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;}
}

计算器核心功能附加日志功能实现类

/*** 带日志的计算器接口实现*/
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;}
}

分析问题

针对附加日志功能的实现类中,能够发现以下问题:
1.日志功能对核心业务功能有干扰,会导致程员在开发核心业务功能需要考虑日志功能的开发而分散了精力。
2.附加的日志功能分散在每个业务功能方法中,不利于统一维护。针对这两个问题,最主要的解决方式就是解耦,将附加日志功能从核心业务功能代码中抽离出来。面向切面编程(AOP)正式解决这种问题而诞生的,在OOP(面向对象编程)中开发者常常会遇到一些横切关注点(Cross Cutting Concerns),比如日志、安全、事务管理等,这些关注点分布在多个类和方法中,导致代码重复、耦合度高,且难以维护。为了解决这些问题AOP应运而生。AOP的核心思想就是将这些横切关注点从核心业务逻辑中分离出来,形成独立的模块,然后在需要的时候通过"编织"(weaving)的方式动态地注入到核心业务逻辑中。这样就key实现关注点的分离,提高代码可读性、可维护性、可重用性。

代理模式(Proxy)

由于Spring-AOP是基于动态代理实现的,这里先讲解一下代理模式的基本概念。

概念

代理模式是一种设计模式,它提供了对目标对象的代理,以控制对目标对象的方法。代理对象通常具有与目标对象相同的接口,以便可以在任何需要目标对象的地方使用代理对象。

代理模式的主要角色:

  1. 主体(Subject):定义了实际主体与代理人的共同接口,以便在任何需要实际主体时使用代理人。

  2. 实际主体(Real Subject): 实现了主体中定义的接口,相当于目标对象。

  3. 代理人(Proxy): 实现了主体中定义的接口,内部会有实际主体的引用,在代理人不能处理时,使用实际主体进行处理,其余均有代理人处理。

代理模式的主要种类:

  1. 静态代理: 代理类在编译时就已经确定,通常需要为每一个实际主体类编写代理类。
  2. 动态代理: 代理类在运行时动态生成,不需要为每个实际主体类编写代理类。动态代理提供了更高的灵活度。常见的动态代理的实现方式有JDK动态代理和CGLIB。

静态代理示例

主体类

/*** 计算器接口*/
public interface Calculator {/*** 加** @param i 整数* @param j 整数* @return 计算结果*/int add(int i, int j);/*** 减** @param i 整数* @param j 整数* @return 计算结果*/int sub(int i, int j);/*** 乘** @param i 整数* @param j 整数* @return 计算结果*/int mul(int i, int j);/*** 除** @param i 整数* @param j 整数* @return 计算结果*/int div(int i, int j);
}

实际主体类

/*** 计算器接口实现类*/
public class CalculatorImpl 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;}
}

代理类

/*** 计算器静态代理类*/
public class CalculatorStaticProxy implements Calculator {private static final Logger logger = LoggerFactory.getLogger(CalculatorStaticProxy.class);//被代理的目标传进来private Calculator calculator;public CalculatorStaticProxy(Calculator calculator) {this.calculator = calculator;}@Overridepublic int add(int i, int j) {//输出日志logger.info("[日志]: add方法开始了,参数为[{}]和[{}]", i, j);int addResult = calculator.add(i, j);logger.info("[日志]: add方法结束了,结果为:[{}]", addResult);return addResult;}@Overridepublic int sub(int i, int j) {return 0;}@Overridepublic int mul(int i, int j) {return 0;}@Overridepublic int div(int i, int j) {return 0;}
}

测试类

/*** 测试静态代理*/
public class TestStaticProxy {public static void main(String[] args) {Calculator calculator = new CalculatorImpl();CalculatorStaticProxy calculatorStaticProxy = new CalculatorStaticProxy(calculator);calculatorStaticProxy.add(1, 2);}
}

动态代理示例

主体类与实际主体类与静态代理示例中的类一致,这里就只展示代理类和测试类

代理类

/*** 动态代理类*/
public class ProxyFactory {private static final Logger logger = LoggerFactory.getLogger(ProxyFactory.class);//传入目标对象private Object target;public ProxyFactory(Object target) {this.target = target;}//返回代理对象public Object getProxy() {/***Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)* 获取代理对象的方法* 三个传参* loader: 类加载器* interfaces: 目标类对象实现的所有接口* h: 是一个接口,需要实现其方法。设置代理对象实现目标对象方法的过程* Object invoke(Object proxy, Method method, Object[] args)** proxy 代理对象* method 需要重写目标对象的方法* args 对应目标对象方法的参数*/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 {logger.info("[动态代理:日志]: {} 方法,参数为:{}", method.getName(), Arrays.toString(args));//调用目标方法Object result = method.invoke(target, args);logger.info("[动态代理:日志]: {} 方法,执行结果为:{}", method.getName(), result.toString());return result;}};return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);}
}

测试类

/*** 测试动态代理类*/
public class TestProxyFactory {public static void main(String[] args) {Calculator calculator = new CalculatorImpl();ProxyFactory proxyFactory = new ProxyFactory(calculator);Calculator calculatorProxy = (Calculator) proxyFactory.getProxy();calculatorProxy.add(1, 2);calculatorProxy.mul(2, 5);}
}

AOP的概念

AOP(面向切面编程)是一种编程范式,旨在通过分离横切关注点来增加模块化。它允许开发者定义跨多个对象的横切行为(或称为切面),从而避免将这些行为重复编写到每个对象中。

Aspect(切面)

定义横切关注点的模块。包含切入点(Pointcut)和通知(Advice)。
在Spring AOP中切面是用@Aspect注释(AspectJ风格)的常规类实现的。

Join point(连接点)

程序执行过程中的一个点,如执行方法或处理异常。
在Spring AOP中,连接点总是表示一个方法的执行。

Advice(通知)

切面在特定连接点采取的操作。类型包括:前置通知(Before)、后置通知(After)、返回通知(After Returning)、异常通知(After Throwing)、环绕通知(Around)。
很多AOP框架(包括Spring)将通知建模为拦截器,并在连接点周围维护一系列拦截器。

Pointcut(切入点)

指定在哪些连接点上执行切面逻辑。通知与切入点表达式相关联,并在切入点匹配的任何连接点上运行。
切入点表达式匹配连接点的概念是AOP的核心。
Spring默认使用AspectJ切入点表达式语言。

Target object(目标对象)

被切面逻辑所增强的对象。被一个或多个切面通知(Advice)的对象,也成为通知对象。
因为Spring AOP是通过动态代理实现,所以这个对象总是一个被代理的对象。

Proxy object(代理对象)

包含目标对象和切面逻辑的对象。AOP框架为了实现切面概念(通知方法执行等)而创建的对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。

Weaving(织入)

将切面逻辑插入到目标对象的过程。可以在编译时、加载时、运行时进行。
Spring AOP是在运行时执行织入操作。

基于注解实现AOP

Spring AOP使用的两种动态代理-JDK动态代理与CGLIB动态代理

Spring框架默认情况下优先使用JDK动态代理,但在代理类没有实现接口时,会自动切换到CGLIB动态代理。

JDK动态代理:

  • 基于Java的反射机制实现。
  • 只能代理实现了接口的类,适用于被代理类有实现接口的情况。
  • 生成的代理类实现了被代理类所实现的接口。
  • 性能相对较低,因为基于反射调用方法。

CGLIB动态代理:

  • 基于ASM字节码生成库实现。
  • 可以代理没有接口的类,适用于被代理类没有实现接口与有实现接口的情况。
  • 生成的代理类是继承自被代理类的子类。
  • 性能相对较高,因为直接操作字节码生成代理类。

引入Spring AOP的相关依赖

        <!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.11</version></dependency><!--log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.11</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.11</version></dependency>

开启AspectJ自动代理(为目标对象自动生成代理)

有两种开启AspectJ自动代理的方式:Java配置类和XML配置文件,开启后就可以通过@Aspect注解声明切面类了。

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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--    开启组件扫描--><context:component-scan base-package="com.shen.springaop.annoaop"></context:component-scan><!--    开启aspectj自动代理,为目标对象生成代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

Java配置类方式开启

@Configuration
@ComponentScan("com.shen.springaop.annoaop")
@EnableAspectJAutoProxy
public class Spring6Config {
}

切面优先级

在同一个目标方法上可能出现多个切面的情况,对于这种情况,切面优先级为外高内低,外面的切面优先级高,里面的切面优先级低。除此之外也可以通过@Order注解控制切面优先级,@Order的value值越小优先级越高。

通知与切入点表达式的使用

由于注解形式,通常是通知注解中包含切入点表达式,因此这两个一起讲。

通知注解

作用于方法上,value存放切入点表达式

通知注解源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {/*** @return the pointcut expression where to bind the advice*/String value();/*** When compiling without debug info, or when interpreting pointcuts at runtime,* the names of any arguments used in the advice declaration are not available.* Under these circumstances only, it is necessary to provide the arg names in* the annotation - these MUST duplicate the names used in the annotated method.* Format is a simple comma-separated list.** @return the argument names (should match the annotated method parameter names)*/String argNames() default "";}
五种通知注解
通知类型 通知注解 说明
前置通知 @Before 在目标方法执行之前执行
返回通知 @AfterReturning 在目标方法正常执行并返回之后执行,此注解可以指定一个参数用于接收返回值
异常通知 @AfterThrowing 在目标方法抛出异常,此注解可以指定一个参数用于接收异常
后置通知 @After 在目标方法执行之后执行,无论目标方法是否抛出异常
环绕通知 @Around 在目标方法执行前后都可以执行,可以控制目标方法的执行

切入点表达式

切入点表达式是在通知注解中,下图是对于切入点表达式的格式讲解。

复用切入点表达式

被@Pointcut注解声明的公共无参无返回值的方法可以实现切入表达式的复用

@Pointcut是用于定义切点(Pointcut)的注解,用于指定在哪些连接点(Join point)上应用通知(Advice),即定义哪些方法执行时会被拦截。被@Pointcut声明的方法配合通知注解就可以实现切入点表达式的复用,需要注解的时@Pointcut声明的方法必须是公共的,无参数的,无返回值的。
    @Before("com.shen.springaop.annoaop.LogAspect.pointCut()")public void beforeMethod() {logger.info("前置通知==============");}@Pointcut(value = "execution(* com.shen.springaop.annoaop.CalculatorImpl.div(..))")public void pointCut() {}

基于注解实现AOP的示例

主体类

/*** 计算器接口*/
public interface Calculator {/*** 加** @param i 整数* @param j 整数* @return 计算结果*/int add(int i, int j);/*** 减** @param i 整数* @param j 整数* @return 计算结果*/int sub(int i, int j);/*** 乘** @param i 整数* @param j 整数* @return 计算结果*/int mul(int i, int j);/*** 除** @param i 整数* @param j 整数* @return 计算结果*/int div(int i, int j);
}

实际主体类(被代理类)

/*** 计算器接口实现类*/
@Service
public class CalculatorImpl 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);//为了测试模拟异常int a = 1 / 0;return result;}@Overridepublic int div(int i, int j) {int result = i / j;System.out.println("方法内部 result = " + result);//为了测试模拟异常
//        int a = 1 / 0;return result;}
}

开启AspectJ自动代理的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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--    开启组件扫描--><context:component-scan base-package="com.shen.springaop.annoaop"></context:component-scan><!--    开启aspectj自动代理,为目标对象生成代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

代理类(切面类)

/*** 切面类*/
@Aspect //声明为切面类
@Order
@Component //ioc容器管理
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);//通知类型: 前置,返回,异常,后置,环绕//前置 @Before()//返回 @AfterReturning//异常 @AfterThrowing//后置 @After()//环绕 @Around()//设置切入点与通知类型//切入点表达式: execution(访问修饰符 增强方法返回类型 增强方法所在类的全类名.方法名称(参数列表))//前置  @Before(value = "切入点表达式配置切入点")@Before(value = "execution(public int com.shen.springaop.annoaop.CalculatorImpl.add(int,int))")public void beforeMethod() {logger.info("前置通知==============");}@After(value = "execution(* com.shen.springaop.annoaop.*.sub(..))")public void afterMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();logger.info("后置通知========,方法名为:{},传参为:{}", methodName, Arrays.asList(args));}@AfterReturning(value = "execution(public int com.shen.springaop.annoaop.CalculatorImpl.*(int,int)))", returning = "result")public void afterReturnMethod(JoinPoint joinPoint, Object result) {String methodName = joinPoint.getSignature().getName();logger.info("返回通知========,方法名为:{},返回结果为:{}", methodName, result.toString());}@AfterThrowing(value = "execution(public int com.shen.springaop.annoaop.CalculatorImpl.mul(..))", throwing = "throwable")public void afterThrowingMethod(JoinPoint joinPoint, Throwable throwable) {String methodName = joinPoint.getSignature().getName();logger.info("异常通知========,方法名为:{},异常为:{}", methodName, throwable);}@Around("com.shen.springaop.annoaop.LogAspect.pointCut()")public Object aroundMethod(ProceedingJoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();String args = Arrays.toString(joinPoint.getArgs());Object object = null;try {logger.info("环绕通知===,目标方法执行前");object = joinPoint.proceed();logger.info("环绕通知===,目标方法返回值之后");} catch (Throwable throwable) {logger.info("环绕通知===,目标方法出现异常:", throwable);} finally {logger.info("环绕通知===,目标方法执行完毕");}return object;}@Pointcut(value = "execution(* com.shen.springaop.annoaop.CalculatorImpl.div(..))")public void pointCut() {}
}

测试类

/*** 测试配置切面类后的方法执行结果*/
public class TestLogAspect {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");Calculator calculator = context.getBean(Calculator.class);
//        calculator.add(1, 2);
//        calculator.sub(2,3);
//        calculator.mul(1,2);calculator.div(2, 1);}
}

基于XML实现AOP(不常用)

基于注解形式的AOP在应用性、可维护性、灵活性和开发效率等方面都优于XML形式,因此在现代Java开发中基于注解形式的AOP更为常用。在某些特定场景或遗留系统中,XML形式的AOP仍然有其应用价值,这里只显示XML形式实现AOP的示例。

示例

由于主体类、实际主体类与注解形式实现AOP的示例一致,这里就只展示代理类、测试类、XML配置文件

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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--    开启组件扫描--><context:component-scan base-package="com.shen.springaop.xmlaop"></context:component-scan><!--    配置aop五种通知类型--><aop:config><!--        配置切面类--><aop:aspect ref="logAspect"><!--配置切入点--><aop:pointcut id="pointcut" expression="execution(* com.shen.springaop.xmlaop.CalculatorImpl.div(..))"/><!--            前置通知--><aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before><!--            后置通知--><aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after><!--            返回通知--><aop:after-returning method="afterReturnMethod" pointcut-ref="pointcut"returning="result"></aop:after-returning><!--            异常通知--><aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut"throwing="throwable"></aop:after-throwing><!--            环绕通知--><aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around></aop:aspect></aop:config></beans>

代理类(切面类)

/*** 切面类*/
@Component //ioc容器管理
public class LogAspect {private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);//通知类型: 前置,返回,异常,后置,环绕//前置 @Before()//返回 @AfterReturning//异常 @AfterThrowing//后置 @After()//环绕 @Around()//设置切入点与通知类型//切入点表达式: execution(访问修饰符 增强方法返回类型 增强方法所在类的全类名.方法名称(参数列表))//前置  @Before(value = "切入点表达式配置切入点")public void beforeMethod() {logger.info("前置通知==============");}public void afterMethod(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();logger.info("后置通知========,方法名为:{},传参为:{}", methodName, Arrays.asList(args));}public void afterReturnMethod(JoinPoint joinPoint, Object result) {String methodName = joinPoint.getSignature().getName();logger.info("返回通知========,方法名为:{},返回结果为:{}", methodName, result.toString());}public void afterThrowingMethod(JoinPoint joinPoint, Throwable throwable) {String methodName = joinPoint.getSignature().getName();logger.info("异常通知========,方法名为:{},异常为:{}", methodName, throwable);}public Object aroundMethod(ProceedingJoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();Object object = null;try {logger.info("环绕通知===,目标方法执行前");object = joinPoint.proceed();logger.info("环绕通知===,目标方法返回值之后");} catch (Throwable throwable) {logger.info("环绕通知===,目标方法出现异常:", throwable);} finally {logger.info("环绕通知===,目标方法执行完毕");}return object;}
}

测试类

/*** 测试配置切面类后的方法执行结果*/
public class TestLogAspect {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("beanaop.xml");Calculator calculator = context.getBean(Calculator.class);
//        calculator.add(1, 2);
//        calculator.sub(2,3);
//        calculator.mul(1,2);calculator.div(2, 1);}
}

参考资料

https://docs.spring.io/spring-framework/reference/core/aop.html

https://docs.spring.io/spring-framework/reference/core/aop-api.html

https://www.bilibili.com/video/BV1kR4y1b7Qc/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/873407.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java的控制流程

Java的控制流程 1. Scanner对象Java.util.Scanner 是Java5的新特性,我们可以通过Scanner类来获取用户的输入。 基本语法: Scanner s = new Scanner(System.in); 通过Scanner类的next() 与nextLine()方法获取输入的字符串,在读取我们一般需要 使用 hasNext() 与 hasNextLine…

【Java安全】基础汇总

一、JAVA安全 1.1 java的序列化和反序列化 Java 序列化是指把 Java 对象转换为字节序列的过程ObjectOutputStream类的 writeObject() 方法可以实现序列化 Java 反序列化是指把字节序列恢复为 Java 对象的过程ObjectInputStream 类的 readObject() 方法用于反序列化。 1、序列化…

云计算和服务器

一、云计算概述 ICT是世界电信协会在2001年的全球性会议上提出的综合性概念,ICT分为IT和CT,IT(information technology)信息技术,负责对数据生命周期的管理;CT(communication technology),负责数据的传输管理。 CT技术是ICT技术栈的底核。 1、计算机 计算机是现代一种…

movfuscator学习

demovfuscator docker镜像 - 狗小剩就是利用32位mov的图灵完备性,来代替各种代码(32位汇编太逆天了).如果看到只有mov就知道是这b玩意了,不过这种程序性能肯定不行,代码段也好长.可以利用ida查锁定字节码的范围,查相应的字符串. demovfuscator问题太多了,一个是识别c的代码无法…

中考英语优秀范文-016 How to keep a good relationship with parents 如何与父母保持良好的关系

1 写作要求 某英文报社正就青少年与父母关系这一话题开展题为“How to keep a good relationship with parents”的征文活动。请你根据以下要点, 写一篇80个词左右的英语短文参加此次活动: 1 父母规矩太多, 过于强调学习成绩, 不理解自己等问题; 2 你对这些问题的看法; 3 你与父…

KubeSphere 开源社区 2024 年度回顾与致谢

随着 2024 年圆满落幕,我们回顾 KubeSphere 社区这一年走过的每一步,感慨万千。2024 年,KubeSphere 继续领跑云原生技术的创新与发展,推动开源文化的传播,致力于为全球开发者和企业用户提供更强大的平台和解决方案。感谢每一位社区成员的辛勤付出,正是因为你们的共同努力…

云--什么是云

https://whatiscloud.com/

城市生命线安全保障:技术应用与策略创新

城市生命线工程是维系城市正常运行、满足群众生产生活需要的重要基础设施。随着城市化进程的加快,城市基础设施生命线安全运行的复杂性日益加剧,保障城市居民日常生活正常运行的水、电、气、热等各类地下管线以及桥梁、市政设施、轨道交通等城市基础设施的安全问题日益突出。…

Android图形层垂直同步虚拟VSYNC机制

简介 某次调图形性能的时候(启动后台录屏,下(或)称case)发现Android SurfaceFlinger Vsync机制并没有以前想的这么简单粗糙,特别是这次调图形性能发现一些跟Vsync有关联,因此做个总结详解。 跟不上旋律节奏的VSYNC 一份追踪报告,发现Vsync信号非常不规律,于是从这里入手…

[日志] 打印异常堆栈信息的技巧

序Java的异常堆栈信息,对提升排查问题的效率,有极大的帮助————便于我们快速定位异常的发生过程和发生异常的代码行。本文使用的日志框架slf4j : 1.7.25 log4j(2) : 2.20.0 日志行的打印策略 : log4j2.properties# property.log.layout.consolePattern=%d{yyyy/MM/dd HH:m…

【vjudge训练记录】大一寒假专项训练——前缀和/差分

训练情况A题 前缀和模板题,我们输入完 \(a_i\) 后直接求前缀和 \(a_i = a_i + a_{i-1}\),求区间 \([l,r]\) 的和就为 \(a_r-a_{l-1}\)点击查看代码 #include <bits/stdc++.h> #define int long long #define endl \nusing namespace std;void solve(){int n,m;cin>&…