Spring AOP——Spring 中面向切面编程

前面两篇文章记录了 Spring IOC 的相关知识,本文记录 Spring 中的另一特性 AOP 相关知识。

部分参考资料:
《Spring实战(第4版)》
《轻量级 JavaEE 企业应用实战(第四版)》
Spring 官方文档
W3CSchool Spring教程
易百教程 Spring教程

一、AOP——另一种编程思想

1.1 什么是 AOP

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
从《Spring实战(第4版)》图书中扒了一张图:
img

从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

1.2 为什么需要 AOP

想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。

1.3 AOP 实现分类

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

下面给出常用 AOP 实现比较
img

如不清楚动态代理的,可参考我前面的一篇文章,有讲解静态代理、JDK动态代理和 CGlib 动态代理。
静态代理和动态代理 https://www.cnblogs.com/joy99/p/10865391.html

二、AOP 术语

AOP 领域中的特性术语:

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

概念看起来总是有点懵,并且上述术语,不同的参考书籍上翻译还不一样,所以需要慢慢在应用中理解。

三、初步认识 Spring AOP

3.1 Spring AOP 的特点

AOP 框架有很多种,1.3节中介绍了 AOP 框架的实现方式有可能不同, Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。

3.2 Spring AOP 的简单例子

下面先上代码,对着代码说比较好说,看下面这个例子:
这个例子是基于gradle创建的,首先 build.gradle 文件添加依赖(Maven也可以,导入依赖):

dependencies {compile 'org.springframework:spring-context:5.0.6.RELEASE'
}

首先创建一个接口 IBuy.java

package com.sharpcj.aopdemo.test1;public interface IBuy {String buy();
}

Boy 和 Gril 两个类分别实现了这个接口:
Boy.java

package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Component
public class Boy implements IBuy {@Overridepublic String buy() {System.out.println("男孩买了一个游戏机");return "游戏机";}
}

Girl.java

package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Component
public class Girl implements IBuy {@Overridepublic String buy() {System.out.println("女孩买了一件漂亮的衣服");return "衣服";}
}

配置文件, AppConfig.java

package com.sharpcj.aopdemo;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
public class AppConfig {
}

测试类, AppTest.java

package com.sharpcj.aopdemo;import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Boy boy = context.getBean("boy",Boy.class);Girl girl = (Girl) context.getBean("girl");boy.buy();girl.buy();}
}

运行结果:
img

这里运用SpringIOC里的自动部署。现在需求改变了,我们需要在男孩和女孩的 buy 方法之前,需要打印出“男孩女孩都买了自己喜欢的东西”。用 Spring AOP 来实现这个需求只需下面几个步骤:
1、 既然用到 Spring AOP, 首先在 build.gralde 文件中引入相关依赖:

dependencies {compile 'org.springframework:spring-context:5.0.6.RELEASE'compile 'org.springframework:spring-aspects:5.0.6.RELEASE'
}

2、 定义一个切面类,BuyAspectJ.java

package com.sharpcj.aopdemo.test1;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class BuyAspectJ {@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void haha(){System.out.println("男孩女孩都买自己喜欢的东西");}
}

这个类,我们使用了注解 @Component 表明它将作为一个Spring Bean 被装配,使用注解 @Aspect 表示它是一个切面。
类中只有一个方法 haha 我们使用 @Before 这个注解,表示他将在方法执行之前执行。关于这个注解后文再作解释。
参数("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))") 声明了切点,表明在该切面的切点是com.sharpcj.aopdemo.test1.Ibuy这个接口中的buy方法。至于为什么这么写,下文再解释。
3、 在配置文件中启用AOP切面功能

package com.sharpcj.aopdemo;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

我们在配置文件类增加了@EnableAspectJAutoProxy注解,启用了 AOP 功能,参数proxyTargetClass的值设为了 true 。默认值是 false,两者的区别下文再解释。
OK,下面只需测试代码,运行结果如下:
img

我们看到,结果与我们需求一致,我们并没有修改 Boy 和 Girl 类的 Buy 方法,也没有修改测试类的代码,几乎是完全无侵入式地实现了需求。这就是 AOP 的“神奇”之处。

四、通过注解配置 Spring AOP

4.1 通过注解声明切点指示器

Spring AOP 所支持的 AspectJ 切点指示器
img
在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。

下图的切点表达式表示当Instrument的play方法执行时会触发通知。
img
我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 .. 标识切点选择任意的play方法,无论该方法的入参是什么。
多个匹配之间我们可以使用链接符 &&||来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。

举例:
限定该切点仅匹配的包是 com.sharpcj.aopdemo.test1,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)
在切点中选择 bean,可以使用
execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)
修改 BuyAspectJ.java

package com.sharpcj.aopdemo.test1;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class BuyAspectJ {@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)")public void hehe(){System.out.println("男孩女孩都买自己喜欢的东西");}
}

此时,切面只会对 Girl.java 这个类生效,执行结果:
img

细心的你,可能发现了,切面中的方法名,已经被我悄悄地从haha改成了hehe,丝毫没有影响结果,说明方法名没有影响。和 Spring IOC 中用 java 配置文件装配 Bean 时,用@Bean 注解修饰的方法名一样,没有影响。

4.2 通过注解声明 5 种通知类型

Spring AOP 中有 5 中通知类型,分别如下:
img

下面修改切面类:

package com.sharpcj.aopdemo.test1;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class BuyAspectJ {@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void hehe() {System.out.println("before ...");}@After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void haha() {System.out.println("After ...");}@AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void xixi() {System.out.println("AfterReturning ...");}@Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}}

为了方便看效果,我们测试类中,只要 Boy 类:

package com.sharpcj.aopdemo;import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Boy boy = context.getBean("boy",Boy.class);Girl girl = (Girl) context.getBean("girl");boy.buy();// girl.buy();}
}

执行结果如下:
img

结果显而易见。指的注意的是 @Around 修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPointproceed() 方法。 如果没有调用 该方法,执行结果为 :

Around aaa ...
Around bbb ...
After ...
AfterReturning ...

可见,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用,当然也有可能你的实际需求就是这样。

4.3 通过注解声明切点表达式

如你看到的,上面我们写的多个通知使用了相同的切点表达式,对于像这样频繁出现的相同的表达式,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式,修改代码如下:
BuyAspectJ.java

package com.sharpcj.aopdemo.test1;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class BuyAspectJ {@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void point(){}@Before("point()")public void hehe() {System.out.println("before ...");}@After("point()")public void haha() {System.out.println("After ...");}@AfterReturning("point()")public void xixi() {System.out.println("AfterReturning ...");}@Around("point()")public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}
}

程序运行结果没有变化。
这里,我们使用

@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
public void point(){}

声明了一个切点表达式,该方法 point 的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用。

4.4 通过注解处理通知中的参数

上面的例子,我们要进行增强处理的目标方法没有参数,下面我们来说说有参数的情况,并且在增强处理中使用该参数。
下面我们给接口增加一个参数,表示购买所花的金钱。通过AOP 增强处理,如果女孩买衣服超过了 68 元,就可以赠送一双袜子。
更改代码如下:
IBuy.java

package com.sharpcj.aopdemo.test1;public interface IBuy {String buy(double price);
}

Girl.java

package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Component
public class Girl implements IBuy {@Overridepublic String buy(double price) {System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));return "衣服";}
}

Boy.java

package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Component
public class Boy implements IBuy {@Overridepublic String buy(double price) {System.out.println(String.format("男孩花了%s元买了一个游戏机", price));return "游戏机";}
}

再看 BuyAspectJ 类,我们将之前的通知都注释掉。用一个环绕通知来实现这个功能:

package com.sharpcj.aopdemo.test1;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class BuyAspectJ {/*@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void point(){}@Before("point()")public void hehe() {System.out.println("before ...");}@After("point()")public void haha() {System.out.println("After ...");}@AfterReturning("point()")public void xixi() {System.out.println("AfterReturning ...");}@Around("point()")public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}*/@Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")public void gif(double price) {}@Around("gif(price)")public String hehe(ProceedingJoinPoint pj, double price){try {pj.proceed();if (price > 68) {System.out.println("女孩买衣服超过了68元,赠送一双袜子");return "衣服和袜子";}} catch (Throwable throwable) {throwable.printStackTrace();}return "衣服";}
}

前文提到,当不关心方法返回值的时候,我们在编写切点指示器的时候使用了 * , 当不关心方法参数的时候,我们使用了 ..。现在如果我们需要传入参数,并且有返回值的时候,则需要使用对应的类型。在编写通知的时候,我们也需要声明对应的返回值类型和参数类型。

测试类:AppTest.java

package com.sharpcj.aopdemo;import com.sharpcj.aopdemo.test1.Boy;
import com.sharpcj.aopdemo.test1.Girl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Boy boy = context.getBean("boy",Boy.class);Girl girl = (Girl) context.getBean("girl");String boyBought = boy.buy(35);String girlBought = girl.buy(99.8);System.out.println("男孩买到了:" + boyBought);System.out.println("女孩买到了:" + girlBought);}
}

测试结果:
img
可以看到,我们成功通过 AOP 实现了需求,并将结果打印了出来。

4.5 通过注解配置织入的方式

前面还有一个遗留问题,在配置文件中,我们用注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,我们给参数 proxyTargetClass 赋值为 true,如果我们不写参数,默认为 false。这个时候运行程序,程序抛出异常
img

这是一个强制类型转换异常。为什么会抛出这个异常呢?或许已经能够想到,这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制。当这个参数为 false 时,
通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
反之,proxyTargetClasstrue,则会使用 cglib 的动态代理方式。这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。
测试一下,我们将 proxyTargetClass 参数设为 true,同时将 Girl.java 的 Buy 方法用 final 修饰:
AppConfig.java

package com.sharpcj.aopdemo;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

Girl.java

package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Component
public class Girl implements IBuy {@Overridepublic final String buy(double price) {System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));return "衣服";}
}

此时运行结果:
img

可以看到,我们的切面并没有织入生效。

五、通过 XML 配置文件声明切面

前面的示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面。下面先列出 XML 中声明 AOP 的常用元素:
img

我们依然可以使用 `` 元素,他能够自动代理AspectJ注解的通知类。

5.1 XML 配置文件中切点指示器

在XML配置文件中,切点指示器表达式与通过注解配置的写法基本一致,区别前面有提到,即XML文件中需要使用 “and”、“or”、“not”来表示 “且”、“或”、“非”的关系。

5.2 XML 文件配置 AOP 实例

下面我们不使用任何注解改造上面的例子:
BuyAspectJ.java

package com.sharpcj.aopdemo.test2;import org.aspectj.lang.ProceedingJoinPoint;public class BuyAspectJ {public void hehe() {System.out.println("before ...");}public void haha() {System.out.println("After ...");}public void xixi() {System.out.println("AfterReturning ...");}public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}
}

在 Resource 目录下新建一个配置文件 aopdemo.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"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"><bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean><bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean><bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean><aop:config proxy-target-class="true"><aop:aspect id="qiemian" ref="buyAspectJ"><aop:before pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="hehe"/><aop:after pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="haha"/><aop:after-returning pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xixi"/><aop:around pointcut="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))" method="xxx"/></aop:aspect></aop:config>
</beans>

这里分别定义了一个切面,里面包含四种类型的通知。
测试文件中,使用

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopdemo.xml");

来获取 ApplicationContext,其它代码不变。

5.3 XML 文件配置声明切点

对于频繁重复使用的切点表达式,我们也可以声明成切点。
配置文件如下:aopdemo.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"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"><bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean><bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean><bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean><aop:config proxy-target-class="true"><aop:pointcut id="apoint" expression="execution(* com.sharpcj.aopdemo.test2.IBuy.buy(..))"/><aop:aspect id="qiemian" ref="buyAspectJ"><aop:before pointcut-ref="apoint" method="hehe"/><aop:after pointcut-ref="apoint" method="haha"/><aop:after-returning pointcut-ref="apoint" method="xixi"/><aop:around pointcut-ref="apoint" method="xxx"/></aop:aspect></aop:config>
</beans>

5.4 XML文件配置为通知传递参数

BuyAspectJ.java

package com.sharpcj.aopdemo.test2;import org.aspectj.lang.ProceedingJoinPoint;public class BuyAspectJ {
public String hehe(ProceedingJoinPoint pj, double price){try {pj.proceed();if (price > 68) {System.out.println("女孩买衣服超过了68元,赠送一双袜子");return "衣服和袜子";}} catch (Throwable throwable) {throwable.printStackTrace();}return "衣服";}
}

aopdemo.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"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"><bean id="boy" class="com.sharpcj.aopdemo.test2.Boy"></bean><bean id="girl" class="com.sharpcj.aopdemo.test2.Girl"></bean><bean id="buyAspectJ" class="com.sharpcj.aopdemo.test2.BuyAspectJ"></bean><aop:config proxy-target-class="true"><aop:pointcut id="apoint" expression="execution(String com.sharpcj.aopdemo.test2.IBuy.buy(double)) and args(price) and bean(girl)"/><aop:aspect id="qiemian" ref="buyAspectJ"><aop:around pointcut-ref="apoint" method="hehe"/></aop:aspect></aop:config>
</beans>

5.5 Xml 文件配置织入的方式

同注解配置类似,
CGlib 代理方式:

<aop:config proxy-target-class="true"> </aop:config>

JDK 代理方式:

<aop:config proxy-target-class="false"> </aop:config>

六、总结

本文简单记录了 AOP 的编程思想,然后介绍了 Spring 中 AOP 的相关概念,以及通过注解方式和XML配置文件两种方式使用 Spring AOP进行编程。 相比于 AspectJ 的面向切面编程,Spring AOP 也有一些局限性,但是已经可以解决开发中的绝大多数问题了,如果确实遇到了 Spring AOP 解决不了的场景,我们依然可以在 Spring 中使用 AspectJ 来解决。

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

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

相关文章

python getchar,Linux C编程学习:getchar()和getch()

getchar函数名: getchar功 能: 从stdin流中读字符用 法: int getchar(void);注解&#xff1a;getchar有一个int型的返回值&#xff0c;当程序调用getchar时程序就等着用户按键&#xff0c;用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。当用…

python 折线图中文乱码_彻底解决 Python画图中文乱码问题--Pyplotz组件

1 源起 自从开始学习Python&#xff0c;就非常喜欢用来画图。一直没有需求画要中文显示信息的图&#xff0c;所以没有配置Python中文的环境。由于昨天就需要画几十个形式相同&#xff0c;只是数据不同的图&#xff0c;并且需要显示中文信息。如果用Excel画图会很浪费时间&#…

SpringBoot的AOP是默认开启的,不需要加注解@EnableAspectJAutoProxy____听说SpringAOP 有坑?那就来踩一踩

Aspect Component public class CustomerServiceInterceptor {Before("execution(public * org.example.aop.demo..*.*(..))")public void doBefore() {System.out.println("do some important things before...");} }另外SpringBoot默认是cglib动态代理&a…

mysql 开启远程访问_QxOrm 访问 MySQL

在前面的 QxOrm 章节中&#xff0c;我们已经介绍了对本地数据库的操作&#xff0c;现在是时候介绍对远程数据库的访问了&#xff0c;那么就以最常用的 MySQL 为例吧&#xff01;在开始之前&#xff0c;首先要安装 MySQL。如果条件允许&#xff0c;建议将其安装在 Linux 系统上&…

当泛型遇到重载

当泛型遇到了重载&#xff0c;好戏&#xff0c;就发生了。 请看下面代码&#xff1a; 问题&#xff1a;代码能正确编译吗&#xff1f; 这个题目是一个考察泛型的题目。java里面&#xff0c;泛型实际上是“伪泛型”&#xff0c;并不像C#那样是实际上的泛型。 IDE会提示我们下…

redis查询所有key命令_三歪推荐:Redis常见的面试题

本文公众号来源&#xff1a;科技缪缪作者&#xff1a;科技缪缪本文已收录至我的GitHub说说Redis基本数据类型有哪些吧字符串&#xff1a;redis没有直接使用C语言传统的字符串表示&#xff0c;而是自己实现的叫做简单动态字符串SDS的抽象类型。C语言的字符串不记录自身的长度信息…

springboot系列——redisTemplate和stringRedisTemplate对比、redisTemplate几种序列化方式比较

文章目录一、redisTemplate和stringRedisTemplate对比1、StringRedisTemplate2、RedisTemplate二、redisTemplate序列化方式比较1、性能测试对比2、性能总结3、方案一、考虑效率和可读性&#xff0c;牺牲部分空间4、方案二、空间敏感&#xff0c;忽略可读性和效率影响5、使用示…

mysql查询默认排序规则_深究 mysql 默认排序, order by 的顺序【收藏】

mysql 语句中如果没有使用 order by 来排序&#xff0c;通常会用 主键正序排列&#xff0c;但是有的时候不是这样&#xff0c;来看一个实例。实例群友问&#xff1a;请教一个问题&#xff0c;mysql 默认排序问题&#xff0c;当sql 语句 的排序没有指定 主键&#xff08;id&…

Spring Boot jackson配置使用详解

Spring Boot系列-json框架jackson配置详解 T1 - 前言 目前Java最常见的3中JSON操作框架分别为Gson、Jackson、FastJson&#xff0c;该篇文章主要讲解jackson在SpringBoot环境中各配置项的具体作用。 T2 - 环境依赖 jackson是spring-boot的web/webflux框架默认依赖的json库&…

频率统计表用c语言_空间矢量脉宽调制建模与仿真(基于C语言的SIMULINK仿真模型 | 基于SVPWM模块的仿真)...

文末有仿真模型下载方式1.1 基于C语言的SIMULINK仿真模型使用C语言在MATLAB/SIMULINK中仿真&#xff0c;需要借助s-function builder模块实现。七段式SVPWM仿真模型如图1-1所示。仿真解算器&#xff08;Solver&#xff09;选择变步长&#xff08;Variable-step&#xff09;、od…

php基本语法 格式,PHP 基本语法格式

PHP 基本语法格式标准代码如下:复制代码 代码如下:...?>短标签模式(此模式需要修改PHP配置&#xff0c;让PHP支持短标签模式)&#xff1a;复制代码 代码如下:...?>注释&#xff1a;复制代码 代码如下:/* ...*///#时间&#xff1a; 2009-12-14Abs: 取得绝对值. Acos: 取…

linux 服务器启用端口,linux服务器放行端口

一、默认使用iptables的系统(例如centos6)1、关闭所有的 INPUT FORWARD OUTPUT 只对某些端口开放。下面是命令实现&#xff1a;iptables -P INPUT DROPiptables -P FORWARD DROPiptables -P OUTPUT DROP再用命令iptables -L -n查看 是否设置好&#xff0c; 好看到全部 DROP 了这…

MySQL 无符号和有符号的区别

随笔记录: mysql无符号和有符号的区别 无符号unsigned 表示设置的的数据为0或者正数&#xff1b; 有符号则可以是负数 -&#xff1b; 内存占比 有符号 0-255 无符号 -127~127

linux下tomcat启动后无进程,Linux中Tomcat shutdown.sh后进程仍然存在解决办法

最近我们在使用Jenkins自动化部署项目时&#xff0c;在生产liunx环境下&#xff0c;使用脚本shutdown.sh停止tomcat服务&#xff0c;然后再start之后发现应用无法访问了&#xff0c;后台查看tomcat进程是发现有个2个tomcat进程&#xff0c;说明之前的shutdown并没有完全停掉tom…

扫地机器人电路原理图_扫地机有这一台就够了:石头扫地机器人T6 首拆

听说集齐13台扫地机器人可以召唤扫地机神兽&#xff0c;所以集齐了22台扫地机后我的神兽呢&#xff1f;自从上个月入手了石头科技出品的小瓦青春版扫地机之后已经集齐了所有小米(石头)系扫地机。小瓦青春版是一款无序清洁的扫地机产品&#xff0c;也是石头科技价格最低的入门级…

让我放弃FastDFS拥抱MinIO的8个瞬间

目前可用于文件存储的网络服务选择有很多&#xff0c;比如阿里云OSS、七牛云、腾讯云等等&#xff0c;但是收费都有点小贵。为了帮公司节约成本&#xff0c;之前一直是使用fastDFS作为文件服务器&#xff0c;准确的说是图片服务器。直到我发现了MinIO&#xff0c;我决定放弃Fas…

http 和 https_Golang设置https访问,以及http如何重定向到https

设置https访问&#xff1a;初始代码为http监听&#xff1a;func main() { server : &http.Server{ Addr: ":8080", ... } go func() { if err : server.ListenAndServe(); err ! nil && err ! http.ErrServerClosed { log.Fa…

盘点分布式文件存储系统____分布式文件存储系统简介

盘点分布式文件存储系统 在项目的数据存储中&#xff0c;结构化数据通常采用关系型数据库&#xff0c;非结构化数据&#xff08;文件&#xff09;的存储就有很多种方式&#xff0c;服务器本地存储、Nas挂载、ftp等等&#xff0c;今天就来盘点一下&#xff0c;分布式文件存储系统…

常见分布式文件存储介绍、选型比较、架构设计

数据正成为世界上最有价值的资源&#xff0c;分布式文件存储是应对数据爆炸的最好解决方案&#xff0c;那就会涉及到分布式文件存储方案、选型、架构设计等。 分布式文件存储的来源 在这个数据爆炸的时代&#xff0c;产生的数据量不断地在攀升&#xff0c;从GB,TB,PB,ZB.挖掘…

为什么python工程师掌握这些就够了_Python工程师薪资飙升,Python这些技能你掌握了吗...

Python的火热&#xff0c;也带动了工程师们的就业热。那么&#xff0c;Python的市场需求和工程师待遇到底如何呢&#xff1f;今天我们来看看具体数据。2019年招聘python工程师薪资飙升&#xff0c;Python这些技能你掌握了吗?Python岗位和待遇和要求怎么样&#xff1f;下面从招…