| @within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) |
| @annotation | 限制匹配带有指定注解的连接点 |
在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgument-Exception异常。
注意:当我们查看如上所展示的这些Spring支持的指示器时,只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。
编写切点
为了阐述Spring中的切面,我们定义一个Performance接口:
package concert;
public interface Performance {
public void perform() ;
}
假设我们想编写Performance的perform()方法触发的通知,下面展示了切点表达式的写法
使用AspectJ切点表达式来选择Performance的perform()方法
现在假设我们需要配置的切点仅匹配concert包。我们可以使用within()指示器来限制匹配,如下图
使用within指示器限制切点范围
注意:我们使用了“&&”操作符把execution()和within()指示器连接在一起形成与(可以用and代替)关系(切点必须匹配所有的指示器)。类似地,我们可以使用“||操作符”来标识或(可以用or代替)关系,而使用“!”操作符来标识非(可以用not代替)操作。
在切点中选择bean
除了表所列的指示器外,Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用beanID或bean名称作为参数限制切点只匹配特定的bean。
例如,考虑如下的切点:
execution(* concert.Performance.perform())
and bean(‘woodstock’)
还可以使用非操作为除了特定ID以外的其他bean应用通知:
execution(* concert.Performance.perform())
and !bean(‘woodstock’)
我们已经定义了Performance接口,它是切面中切点的目标对象。现在,让我们使用AspectJ注解来定义切面
AspectJ提供了五个注解来定义通知,如下表所示。
| 注 解 | 通 知 |
| — | — |
| @After | 通知方法会在目标方法返回或抛出异常后调用 |
| @AfterReturning | 通知方法会在目标方法返回后调用 |
| @AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
| @Around | 通知方法会将目标方法封装起来 |
| @Before | 通知方法会在目标方法调用之前执行 |
定义切面
我们将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。
下面为Audience类的代码
package com.springinaction.perf;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
//切面 POJO
@Aspect
public class Audience {
//定义命名的切点
@Pointcut(“execution(** com.springinaction.perf.Performance.perform(…))”)
public void performance(){
}
//定义通知
@Before(“performance()”) // 表演之前
public void silenceCellPhones(){
System.out.println(“Silencing cell phones”);
}
@Before(“performance()”) // 表演之前
public void takeSeats(){
System.out.println(“Taking seats”);
}
@AfterReturning(“performance()”) // 表演之后
public void applause(){
System.out.println(“CLAP CLAP CLAP”);
}
@AfterThrowing(“performance()”) // 表演失败之后
public void demandRefund(){
System.out.println(“Demanding a refund”);
}
@Around(“performance()”) // 环绕通知方法
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println(“Silencing cell phones Again”);
System.out.println(“Taking seats Again”);
jp.proceed();
System.out.println(“CLAP CLAP CLAP Again”);
}
catch (Throwable e){
System.out.println(“Demanding a refund Again”);
}
}
}
关于环绕通知,我们首先注意到它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为要在通知中通过它来调用被通知的方法。
需要注意的是,一般情况下,别忘记调用proceed()方法。如果不调用,那么通知实际上会阻塞对被通知方法的调用,也许这是所期望的效果。当然,也可以多次调用,比如要实现一个场景是实现重试逻辑。
除了注解和没有实际操作的performance()方法,Audience类依然是一个POJO,可以装配为Spring中的bean
@Bean
public Audience audience(){ // 声明Audience
return new Audience();
}
除了定义切面外,还需要启动自动代理,才能使这些注解解析。
如果使用JavaConfig的话,需要如下配置
package com.springinaction.perf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy //启动AspectJ自动代理
public class AppConfig {
@Bean
public Audience audience(){ // 声明Audience
return new Audience();
}
}
假如在Spring中要使用XML来装配bean的话,那么需要使用Spring aop命名空间中的<aop:aspectj-autoproxy>
元素。
<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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package=“com.springinaction.perf” />
<aop:aspectj-autoproxy />
无论使用JavaConfig还是XML,Aspect自动代理都会使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。这种情况下,将会为Concert的bean创建一个代理,Audience类中的通知方法将会在perform()调用前后执行。
我们需要记住的是,Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。本质上,它依然是Spring基于代理的切面。
处理通知中的参数
目前为止,除了环绕通知,其他通知都没有参数。如果切面所通知的方法确实有参数该怎么办呢?切面能访问和使用传递给被通知方法的参数吗?
为了阐述这个问题,我们来重新看一下BlankDisc样例。假设你想记录每个磁道被播放的次数。为了记录次数,我们创建了TrackCounter类,它是通知playTrack()方法的一个切面。
package com.springinaction.disc;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import java.util.HashMap;
import java.util.Map;
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<>();
@Pointcut(“execution(* com.springinaction.disc.CompactDisc.playTrack(int))” +
“&& args(trackNumber)”) // 通知playTrack()方法
public void trackPlayed(int trackNumber){}
@Before(“trackPlayed(trackNumber)”) // 在播放前,为该磁道计数
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber){
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
以下为切点表达式分解
在切点表达式中声明参数,这个参数传入到通知方法中
其中args(trackNumber)限定符表明传递给playTrack()方法的int类型参数也会传递到通知中去。trackNumber也与切点方法签名中的参数相匹配。切点定义中的参数与切点方法中的参数名称是一样的。
下面我们启动AspectJ自动代理以及定义bean
package com.springinaction.disc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {
@Bean
public CompactDisc sgtPeppers(){
BlankDisc cd = new BlankDisc();
cd.setTitle(“Sgt. Pepper’s Lonely Hearts Club Band”);
cd.setArtist(“The Beatles”);
List tracks = new ArrayList<>();
tracks.add(“Sgt. Pepper’s Lonely Hearts Club Band”);
tracks.add(“With a Little Help from My Friends”);
tracks.add(“Luck in the Sky with Diamonds”);
tracks.add(“Getting Better”);
tracks.add(“Fixing a Hole”);
tracks.add(“Feel My Heart”);
tracks.add(“L O V E”);
cd.setTracks(tracks);
return cd;
}
@Bean
public TrackCounter trackCounter(){
return new TrackCounter();
}
}
最后的简单测试
package com.springinaction;
import static org.junit.Assert.*;
import com.springinaction.disc.CompactDisc;
import com.springinaction.disc.TrackCounter;
import com.springinaction.disc.TrackCounterConfig;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TrackCounterConfig.class)
public class TrackCounterTest {
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired
private CompactDisc cd;
@Autowired
private TrackCounter counter;
@Test
public void testTrackCounter(){
cd.playTrack(1);
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(7);
cd.playTrack(7);
assertEquals(1,counter.getPlayCount(1));
assertEquals(1,counter.getPlayCount(2));
assertEquals(4,counter.getPlayCount(3));
assertEquals(0,counter.getPlayCount(4));
assertEquals(0,counter.getPlayCount(5));
assertEquals(0,counter.getPlayCount(6));
assertEquals(2,counter.getPlayCount(7));
}
}
通过注解引入新功能
我们除了给已有的方法添加新功能外,还可以添加一些额外的功能。
回顾一下,在Spring中,切面只是实现了它们所包装bean相同的接口代理。如果除了实现这些接口,代理也能暴露新接口。即便底层实现类并没有实现这些接口,切面所通知的bean也能实现新的接口。下图展示了它们是如何工作的。
使用Spring AOP,我们可以为bean引入新的方法。代理拦截调用并委托给实现该方法的其他对象
需要注意的是,当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中。
为了验证能行得通,我们为所有的Performance实现引入Encoreable接口
package com.springinaction.perf;
public interface Encoreable {
void performEncore();
}
借助于AOP,我们创建一个新的切面
package com.springinaction.perf;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer { // 需要给Performance和其实现类额外添加方法的实现
@DeclareParents(value = “com.springinaction.perf.Performance+”,
defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}
其中@DeclareParents注解,将Encoreable接口引入到Performance bean中。
@DeclareParents注解有三个部门组成:
-
value属性指定了哪种类型的bean要引入该接口。(本例中,就是Performance,加号表示Performance的所有子类型)
-
defaultImpl属性指定了为引入功能提供实现的类。
-
@DeclareParents注解所标注的静态属性指明了要引入的接口。
然后需要在配置中声明EncoreableIntroducer的bean
@Bean
public EncoreableIntroducer encoreableIntroducer(){
return new EncoreableIntroducer();
}
当调用委托给被代理的bean或被引入的实现,取决于调用的方法属性被代理的bean还是属性被引入的接口。
在Spring中,注解和自动代理提供了一种很便利的方式来创建切面。但有一个劣势:必须能够为通知类添加注解,要有源码。
如果没有源码或者不想注解到你的代码中,能可选择Spring XML配置文件中声明切面。
如果声明切面,但不能为通知类添加注解时,需要转向XML配置了。
在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面,如下表所示
| AOP配置元素 | 用途 |
| — | — |
| <aop:advisor>
| 定义AOP通知器 |
| <aop:after>
| 定义AOP后置通知(不管被通知的方法是否执行成功) |
| <aop:after-returning>
| 定义AOP返回通知 |
| <aop:after-throwing>
| 定义AOP异常通知 |
| <aop:around>
| 定义AOP环绕通知 |
| <aop:aspect>
| 定义一个切面 |
| <aop:aspectj-autoproxy>
| 启用@AspectJ注解驱动的切面 |
| <aop:before>
| 定义AOP前置通知 |
| <aop:config>
| 顶层的AOP配置元素。大多数的<aop:*>
元素必须包含在<aop:config>
元素内 |
| <aop:declare-parents>
| 以透明的方式为被通知的对象引入额外的接口 |
| <aop:pointcut>
| 定义一个切点 |
我们重新看一下Audience类,这一次我们将它所有的AspectJ注解全部移除掉:
package com.springinaction.perf;
public class Audience {
public void silenceCellPhones(){
System.out.println(“Silencing cell phones”);
}
public void takeSeats(){
System.out.println(“Taking seats”);
}
public void applause(){
System.out.println(“CLAP CLAP CLAP”);
}
public void demandRefund(){
System.out.println(“Demanding a refund”);
}
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println(“Silencing cell phones Again”);
System.out.println(“Taking seats Again”);
jp.proceed();
System.out.println(“CLAP CLAP CLAP Again”);
}
catch (Throwable e){
System.out.println(“Demanding a refund Again”);
}
}
}
声明前置、后置以及环绕通知
下面展示了所需要的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/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
aop:config
<aop:aspect ref=“audience”>
<aop:pointcut id=“perf” expression=“execution(* com.springinaction.perf.Performance.perform(…))” />
<aop:before pointcut-ref=“perf” method=“silenceCellPhones” />
<aop:before pointcut-ref=“perf” method=“takeSeats” />
<aop:after-returning pointcut-ref=“perf” method=“applause” />
<aop:after-throwing pointcut-ref=“perf” method=“demandRefund”/>
<aop:around pointcut-ref=“perf” method=“watchPerformance”/>
</aop:aspect>
</aop:config>
为通知传递参数
我们使用XML来配置BlankDisc。
首先,移除掉TrackCounter上所有的@AspectJ注解。
package com.springinaction.disc;
import java.util.HashMap;
import java.util.Map;
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<>();
// 在播放前,为该磁道计数
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber){
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
下面展示了在XML中将TrackCounter配置为参数化的切面
<?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/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
Sgt. Pepper’s Lonely Hearts Club Band
With a Little Help from My Friends
Lucy in the Sky with Diamonds
Getting Better
Fixing a Hole
Feel My Heart
L O V E
aop:config
<aop:aspect ref=“trackCounter”>
<aop:pointcut id=“trackPlayed” expression="
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/0665baab4576c224203c9f54c1b370bf.jpeg)
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
- 第一,设计模式能让专业人之间交流方便,如下:
程序员A:这里我用了XXX设计模式
程序员B:那我大致了解你程序的设计思路了
- 第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了XXX设计模式,所以改起来很快
- 第三,设计模式是编程经验的总结
程序员A:B,你怎么想到要这样去构建你的代码
程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
3515342293)]
[外链图片转存中…(img-LOdYy7DL-1713515342294)]
[外链图片转存中…(img-X1NL3cM7-1713515342295)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/0665baab4576c224203c9f54c1b370bf.jpeg)
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
- 第一,设计模式能让专业人之间交流方便,如下:
程序员A:这里我用了XXX设计模式
程序员B:那我大致了解你程序的设计思路了
- 第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了XXX设计模式,所以改起来很快
- 第三,设计模式是编程经验的总结
程序员A:B,你怎么想到要这样去构建你的代码
程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
[外链图片转存中…(img-cV4YNLqr-1713515342296)]
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
[外链图片转存中…(img-MlslcdgP-1713515342297)]
搜集费时费力,能看到此处的都是真爱!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!