第4章 面向切面的Spring
package concert;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
@Pointcut("execution(**concert.Performance.perform(..))")
public void performance() {
}
@Before("performance()")
public void silence(){
System.out.println("please close your phone!");
}
@Before("performance()")
public void seatDown(){
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 watcherPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("before");
jp.proceed();
System.out.println("afterReturning");
} catch (Throwable e){
System.out.println("afterthrowing");
}
}
}
<?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">
<bean id="audience" class="concert.Audience"/>
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..))"/>
<aop:before pointcut-ref="performance" method="silence"/>
<aop:before pointcut-ref="performance" method="seatDown"/>
<aop:after-returning pointcut-ref="performance" method="applause"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
</aop:aspect>
</aop:config>
</beans>
一、重要概念
1. 切点
指定的连接点
2. 通知
要做的事情以及时间,如before,after等
3. 切面
包含了通知,切点以及何时做的信息
4. 连接点
所有被标记的可以连接的点,相当于候选切点
5. 引入
可以进行类的包装,例如一个剧场类,如果剧场有返场演出类,那么不是所有的剧场类都要添加返场类,因此可以使用引入,使用切面功能向特定剧场类添加返场类
6. 织入
spring运行aop的时期和AspectJ运行的时期不同,由于运行时期不同,因此spring只支持方法的切点,而Aspect支持构造器方法的切点,甚至字段切点
二、创建切点
创建一个演出接口
package concert;
public interface Performance {
public void perform();
}
演出前后需要让观众安静并就坐,演出成功后观众鼓掌。因此将演出作为切点,并在其前后设置安静就坐和鼓掌方法
"execution(* concert.Performance.perform(..)) && within(concert)"
这个语句指明了切点执行的地方,以及within限制了切点所在的位置
*表示任意的类型,… 表示perform的任意参数,也就是切点是所有在concert包中实现了perfrom的方法
在xml文件中有些不同,&& 变为 and, ||变为 or ,! 变为 not
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..)) and !bean('cd')"/>
三、创建切面
前后通知
观众并非演出类的关键点,而且其可以应用到任何一个演出类上,因此是一个很好的切面
package concert;
import org.aspectj.lang.annotation.*;
@Aspect
public class Audience {
// 定义一个共同切点
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance(){
}// 该方法只是一个标志,供pointcut注释依附
@Before("performance()")// 将在调用perfrom之前使用该方法
public void silence(){
System.out.println("please close your phone!");
}
@Before("performance()")
public void seatDown(){
System.out.println("Taking seats");
}
@AfterReturning("performance()")// 在perfrom方法完成之后
public void applause(){
System.out.println("CLAP CLAP CLAP");
}
@AfterThrowing("performance()")// perfrom异常后调用该方法
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
抛去注释来看,该方法仍为一个POJO类,所以也可以变成Spring中的bean
@Bean
public Audience audience(){
return new Audience();
}
如果使用javaConfig进行配置项目的话
package concert;
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
@EnableAspectJAutoProxy //使用该注释启用AspectJ的自动配置
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience(){
return new Audience();
}
}
<aop:aspectj-autoproxy/>
<bean class="concert.Audience"/>
添加自动代理功能后,AspectJ自动代理会为使用了Apect注解的bean创建一个代理,这个代理会围绕所有该切面的切点所匹配的bean。这样就会为Concernt bean创建一个代理,Audience类中的通知方法将会在perform调用前后执行。
虽然使用了Aspect的注释,但调用仍然是spring的代理,而非直接调用的AspectJ功能,必须进行直接调用
环绕通知
around注释将以perfrom为目标,可以将前后通知放在同一个方法里面
@Around("performance()")
public void watchPerformance(Concert concert){
// 包裹被通知的方法
try {
System.out.println("before");
concert.perform();// 被通知的方法需要进行某种行为,否则会阻塞
System.out.println("afterReturning");
} catch (Throwable e){
System.out.println("afterThrowing");
}
}
处理参数
package CD;
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(* CD.CompactDisc.playTrack(int))" + "&& args(track)")
public void setTrack(int track){
}
@Before("setTrack(track)") // 切点参数为track
public void countTrack(int track){
int currentTrack= getPlayCount(track);
trackCounts.put(track, currentTrack+1);
}
private int getPlayCount(int track){
return trackCounts.containsKey(track)?trackCounts.get(track):0;
}
}
四、创建介入
如果想要添加新的方法,而又不想对原有的类进行修改,可以使用aop介入方法
因为切面就是一个封装了原有类的代理,向切面中添加方法, 可以达成向原有类添加方法的效果
例如我们向Concert类添加返场功能,并非所有的音乐会都有返场,因此可以通过创建介入切面实现
package concert;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value = "concert.Performance+", // 哪种类型的bean将要使用该类型
defaultImpl = DefaultEncoreable.class) // 使用哪种方法实现
public static Encoreable encoreable;// 静态属性指明了要引入的方法的接口类型
}
- value 属性指定了哪种类型的bean要引入该接口。在本例中就是所有实现Performance的类型。加号表示是Performance的所有子类型,而非其本身
- defaultImpl 属性指定了为引入功能提供实现的类,例如DefaultEncoreable类实现了返场功能
- @DeclarParents 所注解的静态属性指明了要引入功能的接口,这里所引入的是Encoreable接口
和其他切面一样,需要将其声明为一个bean
<bean class="concert.EncoreableIntroducer"/>
spring的自动代理机制将会获取到它的声明,当spring发现该bean使用了aspect注解时,就会创建一个切面自动代理,然后执行引入功能。
此功能虽然简单,但是必须知道通知类的源码,才能进行注释的修改,xml提供了更直接的方法
五、在XML中声明切面
javaConfig虽然优秀,但是需要改动源码,如果不能向代码添加注解,就需要使用xml进行配置了
重新对Audience类进行配置
package concert;
public class Audience {
// audience删除注解后,其实就是个POJO,但已经可以进行切面操作了
public void silence(){
System.out.println("please close your phone!");
}
public void seatDown(){
System.out.println("Taking seats");
}
public void applause(){
System.out.println("CLAP CLAP CLAP");
}
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
<bean id="audience" class="concert.Audience"/>
<bean id="encoreableDelegate" class="concert.DefaultEncoreable"/>
<aop:config> <!--开始配置切面-->
<aop:aspect ref="audience"><!--切面类指定为audience bean-->
<!--定义切点-->
<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..)) and !bean('audience')"/>
<!--定义前置方法 pointcut-ref指定引用的切点定义 method指定引用的POJO方法-->
<aop:before pointcut-ref="performance" method="silence"/>
<aop:before pointcut-ref="performance" method="seatDown"/>
<aop:after-returning pointcut-ref="performance" method="applause"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
<aop:around pointcut-ref="performance" method="encoreable"/>
<!--切面引入新方法-->
<aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" default-impl="concert.DefaultEncoreable" delegate-ref="encoreableDelegate"/> 使用ref引用被封装为bean的实现类
</aop:aspect>
</aop:config>
带参数的切面
package CD;
import java.util.HashMap;
import java.util.Map;
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<>();
public void setTrack(int track){
}
public void countTrack(int track){
int currentTrack= getPlayCount(track);
trackCounts.put(track, currentTrack+1);
}
private int getPlayCount(int track){
return trackCounts.containsKey(track)?trackCounts.get(track):0;
}
}
<?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:c="http://www.springframework.org/schema/c" 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">
<bean id="cd" class="CD.BlankDisc">
<property name="artist" value="王菲"/>
<property name="title" value="浮躁"/>
<property name="tracks">
<list>
<value>浮躁</value>
<value>无常</value>
<value>分裂</value>
</list>
</property>
</bean>
<bean id="cdPlayer" class="CD.CDPlayer">
<constructor-arg name="cd" ref="cd"/>
</bean>
<bean id="trackCounter" class="CD.TrackCounter"/>
<aop:config>
<aop:aspect ref="trackCounter">
<aop:pointcut id="trackPlayed" expression="execution(* CD.TrackCounter.countTrack(int)) and args(track)" />
<aop:before pointcut-ref="trackPlayed" method="countTrack"/>
</aop:aspect>
</aop:config>
</beans>
六、注入AspectJ切面
由于java构造器的特殊性,spring基于代理的aop不能把通知应用于对象的创建过程,因此需要使用AspectJ
在实现切面的时候,可能需要多个类进行协作,这样就可以使用spring将其封装为bean,利用依赖注入来管理切面
这时候新建一个评论家类,评论家独立于演出,并且演出结束后会给出意见
package concert;
public aspect CriticAspect {
public CriticAspect(){
}
// 定义切点
pointcut performance() : execution(* concert.Performance.perfrom(..));
after() : performance() {
// 切点方法结束之后执行方法体中的内容
System.out.println(criticismEngine.getCriticism());
}
private CriticismEngine criticismEngine;
// 通知方法所需使用的POJO类,使用setter方法减少和切面之间的耦合
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
package concert;
public interface CriticismEngine {
public String getCriticism();
}
package concert;
public class CriticismEngineImpl implements CriticismEngine {
@Override
public String getCriticism() {
int i = (int) (Math.random()*criticismPool.length);
return criticismPool[i];
}
private String[] criticismPool;
// 要注入的方法,注入评论池
public void setCriticismPool(String[] criticismPool){
this.criticismPool = criticismPool;
}
}
spring bean由spring容器初始化,而aspect切面由aspect在运行时创建。等spring有机会为评论家切面注入评论池的时候,其已经被实例化了。因为spring不能负责评论家切面的创建,也就不能把aspect简单的声明为一个bean。所以可以通过factory-method的静态aspectOf方法获得一个aspect单例。
spring不能简单使用bean来创建一个切面实例,而是需要对由aspectJ创建的切面实例进行引用,也就是通过aspectOf工厂方法进行引用,然后执行注入。
<bean class="concert.CriticAspect" factory-method="aspectOf">
<property ref="criticismEngines" name="criticismEngine"/>
</bean>
今天的文章spring面向切面编程的原理_什么叫切面分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/84153.html