切点用于准确定位应该在什么地方应用切面的通知。{通知,切点}是切面的最基本元素。
通知:做什么,什么时候做
切点:在哪里做(缩小使用切面的范围)
AspectJ 指示器 | 描述 |
arg() | 限制连接点匹配参数为指定类型的执行方法 |
execution | 用于匹配时连接点的执行方法 |
this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |
target | 限制连接点匹配目标对象为指定类型的类 |
within | 限制连接点匹配指定的类型 |
4.2.1编写切点
package concert;
public interface Performance {
public void perform();
}
execution(* concert.Performance.perform(..))
我们先有一个performance接口,当这个perform方法被调用的时候会触发通知。我们用下面的那个表达式来设置。
execution : 在方法执行时触发
*:返回类型任意
concert.Performance:方法所属的类
.perform:方法
(..)任意参数
execution(* concert.Performance.perform(..) && within(concert.*))
within代表concert包小面任意类的方法被调用时
execution(* concert.Performance.perform(..) && bean("woodstock"))
使用bean来限定什么bean的名字
4.3.1定义切面
我们定义了Performance这是目标类,就是我们需要把通知加上去的类。现在我们需要定义切面
CONFIG
@ComponentScan(basePackages = "concert")
//@ComponentScan(basePackageClasses = {CompactDisc.class, MediaPlayer.class})
@EnableAspectJAutoProxy //开启proxy
@Configuration
public class ConfigClass {
@Bean
public Audience audience(){
return new Audience();
} //注册切面为bean
}
@Component
public class Me implements Performance{
@Override
public void perform() {
System.out.println("haha");
}
}
@Aspect
public class Audience {
@Before("execution(* concert.Performance.perform(..))")
public void slienceCellPhone() {
System.out.println("silencing cell phones");
}
}
AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,就是audience,这个代理会围绕着所有该切面的切点所匹配的bean。
4.3.2 创建环绕通知
@Aspect
public class Audience {
@Around("execution(* concert.Performance.perform(..))")
public void slienceCellPhone(ProceedingJoinPoint joinPoint) {
try {
System.out.println("silencing cell phones");
System.out.println("take seats");
joinPoint.proceed();
System.out.println("hahahahah");
} catch (Throwable throwable) {
System.out.println("demanding a refund");
throwable.printStackTrace();
}
}
}
这里我们定义了一个环绕通知,joinPoint.proceed()方法可以调用被拦截的方法,这个方法之前的就是前置通知,后面就是后置通知。
4.3.3 处理通知中的参数
这个例子中我们想要统计这个track中的每首歌都播放了多少次。由于这个记录工作不应该属于这个磁盘的工作,所以,我们创建了一个切面来记录。这个切面的通知里面可以接收参数!!
@Configuration
@ComponentScan(basePackages = "aspectJ")
@EnableAspectJAutoProxy
public class AspectJConfig {
@Bean
public CompactDisc blankDisc() {
List<String> track = new ArrayList<>();
track.add("野区");
track.add("晴天");
return new BlankDisc(
"十一月的肖邦",
"周杰伦",
track
);
}
@Bean
public TrackCounter trackCounter(){
return new TrackCounter();
}
}
@Getter
@Setter
@AllArgsConstructor
@Component
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void playTrack(int i) {
System.out.println(tracks.get(i));
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
System.out.println("--------track------");
for (int i = 0; i < tracks.size(); i++) {
playTrack(i);
}
}
}
@Aspect
@Getter
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<>();
// @Pointcut("execution(* aspectJ.BlankDisc.playTrack(int)) && args(trackNumber)")
// public void trackPlayed(int trackNumber) {
// }
@Before("execution(* aspectJ.BlankDisc.playTrack(int)) && args(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
// @Before("execution(* aspectJ.CompactDisc.playTrack(..))")
// public void countTrack() {
// System.out.println("hha");
// }
public int getPlayCount(int trackNumber) {
return trackCounts.getOrDefault(trackNumber, 0);
}
}
这里同样的,拦截playTrack方法,args()表明我们需要把传给playTrack方法的参数同样传给通知中去!!
要点:args(trackNumber)和countTrack(int trackNumber)的参数名字一样。
4.3.4 通过注解引入新功能
一些语言,如RUBY。可以不用直接修改对象或者类的定义就能够为对象或类增加新方法。
但是Java不是动态语言,一旦编译完成了,就很难添加新功能了。使用切面可以为SpringBean添加新方法。
在Spring中,切面只是实现了它们包装bean相同接口的代理。如果代理也可以暴露接口,那么看起来这个bean也实现了这个接口!
package concert;
public interface Encoreable {
void performEncore();
}
假设,我们有一个新的接口,需要应用到Performance的实现中。假设我们可以获得所有实现了Performance的实现的类,手动添加这个接口的实现,这样非常麻烦,也不是所有的Performance都有这个特性的。也不是所有的第三方代码可以让你修改的。
这时候,我们可以使用aop切面来实现这个功能,非侵入式。