什么是面向切面
散布于应用中多处的功能称为横切关注点,这么横切关注点从概念上是与应用的业务逻辑相分离。AOP可以实现横切关注点与它们所影响的对象之间的解耦。
横切关注点可以被模块化为特殊类。这些类被称为切面。
好处:
①、每个关注点都集中在一个地方,为不是分散在多处代码中。
②、服务模块更简洁。因为只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移的切面中。
AOP术语
通知(Advice)
切面的工作被称为通知。通知定义了切面是什么及何时使用。
5种类型的通知:
①、前置通知(Before):在目标方法被调用之前调用通知功能。
②、后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
③、返回通知(After-returning):在目标方法成功执行之后调用通知。
④、异常通知(After-returning):在目标方法抛出异常后调用通知。
⑤、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(Join point) (哪里可以织入)
连接点是在应用执行过程中能够插入切面的一个点。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
切点(Pointcut) (在哪里织入)
通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。
切面(Aspect)
切面是通知和切点的结合,通知和切点共同定义了切面的全部内容–是什么,在何时和何处。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。
在目标对象的生命周期有多个点可以织入:
①、编译器:切面在目标类编译时被织入。(AspectJ的织入编译器就是这种方式织入切面的)。
②、类加载器:切面在目标类加载到JVM时被织入。
③、运行期:切面在应用运行的某个时刻被织入。AOP为目标对象动态的创建一个代理对象。Spring AOP级以这种方式织入切面。
总结
通知包含了需要用于多个应用对象的横切行为,连接点是在程序执行过程中能够应用通知的所有的点。切点定义了通知被应用的具体位置。切点定义了哪些连接点会得到通知。
Spring对AOP的支持
①、基于代理的经典Spring AOP
②、纯POJO
③、@AspctJ注解驱动的切面
④、注入式ApjectJ切面。
前三种都是Spring AOP实现的变体,基于动态代理基础之上,因此Spring对AOP的支持仅限于对方法的拦截。
Spring AOP支持的切点指示器。
定义切面
@Aspect
public class Audience {
//*任意返回类型,
//com.beanConfig.interfaceS.Performance方法
//perform方法
//..任意参数
// @Before("execution(* com.beanConfig.interfaceS.Performance.perform(..))")
// @Before("execution(* com.beanConfig.*.Performance.perform(..))")
@Before("execution(* com.beanConfig.*.*mance.perform(..))") //*可以上省略你某个包,甚至还可以像模糊查询一样
public void silencePhone() {
System.out.println("Silencing cell phone");
}
@Before("execution(* com.beanConfig.*.*mance.perform(..))")
public void takeSeats() {
System.out.println("Take seats");
}
@AfterReturning("execution(* com.beanConfig.*.*mance.perform(..))")
public void applaues() {
System.out.println("CLAP CLAP CLAP");
}
@AfterThrowing("execution(* com.beanConfig.*.*mance.perform(..))")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
我们发现以上的切点都是可重用的。那么就可是使用 @Pointcut注解定义可重用切点。
@Aspect
public class Audience {
@Pointcut("execution(* com.beanConfig.*.*mance.perform(..))")
public void performance() {
}
@Before("performance()")
public void silencePhone() {
System.out.println("Silencing cell phone");
}
@Before("performance()")
public void takeSeats() {
System.out.println("Take seats");
}
@AfterReturning("performance()")
public void applaues() {
System.out.println("CLAP CLAP CLAP");
}
@AfterThrowing("performance())")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
Audience 依然是一个POJO,也可以把它装配为bean。
@Bean
public Audience audience() {
return new Audience();
}
如果仅仅这样,Audience 仅仅只是spring容器中的一个bean,而不是切面。
我们需要手动开启自动代理功能。
第一种:@EnableAspectJAutoProxy 注解
@Configuration
@EnableAspectJAutoProxy //开启AspectJ自动代理
public class AspectAOPConfig {
@Bean
public Audience audience() {
return new Audience();
}
@Bean
public Performance show() {
return new Show();
}
}
第二种:xml配置
<aop:aspectj-autoproxy/>
<bean class="com.beanConfig.aspectAOP.Audience"></bean>
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AspectAOPConfig.class)
public class AOPTest {
@Autowired
private Audience audience;
@Autowired
private Performance performance;
@Test
public void play() {
performance.perform();
}
}
环绕通知
看代码注释吧。
@Aspect
public class AroundAudience {
@Pointcut("execution(* *com.beanConfig.interfaceS.Performance.perform(..))")
public void performance() {
}
@Around("performance()")
//ProceedingJoinPoint 通过它调用被通知的方法。
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phone");
System.out.println("Take seats");
//当要将控制权交给被通知的方法时,调用ProceedingJoinPoint的proceed()方法。
//如果不调用的话,阻塞被通知方法的访问。
jp.proceed();
System.out.println("CLAP CLAP CLAP");
} catch (Throwable throwable) {
System.out.println("Demanding a refund");
}
}
}
处理通知中的参数
为了记录每个磁道播放的次数,创建切面
/**
* Created by 31696 on 2019/5/7.
* 为记录每个磁道播放的次数
*/
@Aspect
public class TrackCounter {
private Map<Integer,Integer> trackCounts = new HashMap<Integer, Integer>();
@Pointcut("execution(* com.beanConfig.classes.bean.BlankDisc.playTracks(int)) && args(trackNumber)")
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;
}
}
切点
@Pointcut("execution(* com.beanConfig.classes.bean.BlankDisc.playTracks(int)) && args(trackNumber)")
int:表示接收int类型的参数。
args(trackNumber):限定符,表示传递给playTracks()方法的int类型参数也要传到通知中去,且参数的名称也要与切点方法签名的参数相匹配。
public class BlankDisc implements CompactDisc{
/**
*标题
*/
private String title;
/**
* 艺术家
*/
private String artist;
/**
* 音轨
*/
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
@Override
public void playTracks(int trackNumber) {
}
@Override
public void play() {
}
}
@Configuration
@EnableAspectJAutoProxy
public class TrackCountConfig {
@Bean
public CompactDisc blankDisc() {
BlankDisc blankDisc = new BlankDisc();
blankDisc.setTitle("sgt.Papers");
blankDisc.setArtist("The Beatles");
List<String> tracks = new ArrayList<String>();
tracks.add("AAAAAA");
tracks.add("BBBBBB");
tracks.add("CCCCCC");
tracks.add("DDDDDD");
tracks.add("EEEEEE");
tracks.add("FFFFFF");
tracks.add("HHHHHH");
blankDisc.setTracks(tracks);
return blankDisc;
}
@Bean
public TrackCounter trackCounter () {
return new TrackCounter();
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= TrackCountConfig.class)
public class AOPArgsTest {
@Autowired
private CompactDisc blankDisc;
@Autowired
private TrackCounter counter;
@Test
public void testTrackCounter() {
blankDisc.playTracks(1);
blankDisc.playTracks(2);
blankDisc.playTracks(3);
blankDisc.playTracks(3);
blankDisc.playTracks(3);
blankDisc.playTracks(3);
blankDisc.playTracks(7);
blankDisc.playTracks(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)));
}
}