SpringAOP的使⽤详解
什么是AOP
AOP(Aspect Oriented Programming ⾯向切⾯编程),通过预编译⽅式和运⾏期动态代理实现程序功能的统⼀维护的⼀种技术。AOP是OOP的延续,是软件开发中的⼀个热点,也是Spring框架中的⼀个重要内容,是函数式编程的⼀种衍⽣范型。利⽤AOP可以对业务逻辑的各个部分进⾏隔离,从⽽使得业务逻辑各部分之间的耦合度降低,提⾼程序的可重⽤性,同时提⾼了开发的效率。
常⽤于⽇志记录,性能统计,安全控制,事务处理,异常处理等等。
定义AOP术语
切⾯(Aspect):切⾯是⼀个关注点的模块化,这个关注点可能是横切多个对象;
连接点(Join Point):连接点是指在程序执⾏过程中某个特定的点,⽐如某⽅法调⽤的时候或者处理异常的时候;
通知(Advice):指在切⾯的某个特定的连接点上执⾏的动作。Spring切⾯可以应⽤5中通知:
前置通知(Before):在⽬标⽅法或者说连接点被调⽤前执⾏的通知;
后置通知(After):指在某个连接点完成后执⾏的通知;
返回通知(After-returning):指在某个连接点成功执⾏之后执⾏的通知;
异常通知(After-throwing):指在⽅法抛出异常后执⾏的通知;
环绕通知(Around):指包围⼀个连接点通知,在被通知的⽅法调⽤之前和之后执⾏⾃定义的⽅法。
切点(Pointcut):指匹配连接点的断⾔。通知与⼀个切⼊点表达式关联,并在满⾜这个切⼊的连接点上运⾏,例如:当执⾏某个特定的名称的⽅法。
引⼊(Introduction):引⼊也被称为内部类型声明,声明额外的⽅法或者某个类型的字段。
⽬标对象(Target Object):⽬标对象是被⼀个或者多个切⾯所通知的对象。
AOP代理(AOP Proxy):AOP代理是指AOP框架创建的对对象,⽤来实现切⾯契约(包括通知⽅法等功能)
织⼊(Wearving):指把切⾯连接到其他应⽤出程序类型或者对象上,并创建⼀个被通知的对象。或者说形成代理对象的⽅法的过程。
Spring对AOP的⽀持
1. 基于代理的经典SpringAOP;
2. 纯POJO切⾯;
3. @AspectJ注解驱动的切⾯;
4. 注⼊式AspectJ切⾯(适⽤于Spring各版本);
前三种都是SpringAOP实现的变体,SpringAOP构建在动态代理基础之上,因此,Spring对AOP的⽀持局限于⽅法的拦截。
切⼊点表达式
使⽤SpringAOP
SpringAOP的⽀持必须呀导⼊spring-aspects的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.5.RELEASE</version>spring aop应用场景
</dependency>
使⽤注解定义切⾯
采⽤注解的⽅式定义切⾯以及通知
@Aspect
public class Audience {
//使⽤@Pointcut注解声明频繁使⽤的切⼊点表达式
@Pointcut("execution(* t.Performance.perform(..))")
public void performance(){}
@Before("performance()")
public void silenceCellPhones(){
System.out.println("Sillencing cell phones");
}
@Before("performance()")
public void takeSeats(){
System.out.println("Task Seat");
}
@AfterReturning("performance()")
public void applause(){
System.out.println("CLAP CLAP CLAP");
}
@AfterThrowing("performance()")
public void demandRefund(){
System.out.println("Demand a Refund");
}
}
另外需要在l也就是spring的配置⽂件中添加配置:
<!--启⽤AspectJ的⾃动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean class="t.Audience"/>
在XML中声明切⾯
定义pojo类,这⾥只是把上⾯定义的注解全public class AudienceXML {
public void silenceCellPhones() {
System.out.println("Sillencing cell phones");
}
public void takeSeats() {
System.out.println("Task Seat");
}
public void applause() {
System.out.println("CLAP CLAP CLAP");
}
public void demandRefund() {
System.out.println("Demand a Refund");
}
<!--声明bean-->
<bean name="audienceXML" class="t.AudienceXML"/>
<aop:config>
<!--引⼊bean-->
<aop:aspect ref="audienceXML">
<!--定义切点-->
<aop:pointcut id="perform"
expression="execution(* t.Performance.perform(..))"/>
<!--定义通知
method:通知,也就是具体的⽅法
pointcut-ref:引⽤的切点
pointcut:切点-->
<aop:before method="silenceCellPhones"
pointcut-ref="perform"/>
<aop:before method="takeSeats" pointcut-ref="perform"/>
<aop:after-returning method="applause" pointcut-ref="perform"/>
<aop:after-throwing method="demandRefund"
pointcut="execution(* t.Performance.perform(..))"/>
</aop:aspect>
</aop:config>
环绕通知
在springAOP中有五种通知,环绕通知是最为强⼤的通知。它能够让你编写的逻辑将被通知的⽬标⽅法完全包装起来。实际上就像在⼀个通知⽅法中同时编写前置通知和后置通知。
本⽚⽂章具体讲解环绕通知的使⽤。
使⽤注解
使⽤环绕通知定义切⾯:
@Aspect
public class AudienceAround {
//使⽤@Pointcut注解声明频繁使⽤的切⼊点表达式
@Pointcut("execution(* t.Performance.perform(..))")
public void performance(){}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint){
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
joinPoint.proceed();
System.out.println("Demanding a refund");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
可以看到在上⾯的代码中,定义通知的时候在通知⽅法中添加了⼊参:ProceedingJoinPoint。在创建环绕通知的时候,这个参数是必须写的。因为在需要在通知中使⽤ProceedingJoinPoint.proceed()⽅法调⽤被通知的⽅法。
另外,如果忘记调⽤proceed()⽅法,那么通知实际上会阻塞对被通知⽅法的调⽤。
在XML中定义
⾸先去掉上⾯类的所有注解:这⾥为了区别就重新创建⼀个类
public class AudienceAroundXML {
public void watchPerformance(ProceedingJoinPoint joinPoint){
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
joinPoint.proceed();
System.out.println("Demanding a refund");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
配置:
<!--声明bean-->
<bean name="audienceAroundXML" class="t.AudienceAroundXML"/>
<!--配置切⾯及通知-->
<aop:config>
<aop:aspect ref="audienceAroundXML">
<aop:pointcut id="performance"
expression="execution(* t.Performance.perform(..))"/>
<aop:around method="watchPerformance" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
处理通知中的参数
Spring借助AspectJ的切点表达式语⾔来定义Spring切⾯
在spring中尝试使⽤其他指⽰器时,会抛出IllegalArgument-Exception异常。
如上的这些指⽰器,只有exception指⽰器是实际执⾏匹配的,⽽其他都是⽤来限制匹配的。
切⾯表达式分析
带参数的切点表达式分解
在该切点表达式中使⽤了args(trackNumber)限定符。表⽰传递给playTrack()⽅法的int类型参数也会传递到通知中去。参数名trackNumber也与切点⽅法签名中的参数相匹配。
创建切⾯
@Aspect
public class TrackCounter {
@Pointcut("execution(* com.wqh.aop.CompactDisc.playTrack(int))&&args(trackNumber)")
public void trackPlayder(int trackNumber){}
@Before("trackPlayder(trackNumber)")
public void countTrack(int trackNumber) {
System.out.println("前置通知:targetNumber=" + trackNumber);
}
}
连接点类
@Service
public class CompactDisc {
public void playTrack(int trackNumber){
System.out.println("trackNumber =" + trackNumber);
}
}
XML配置
<!--启⽤AspectJ的⾃动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean class="com.wqh.aop.TrackCounter"/>
<!--⾃动扫描包下的类-->
<context:component-scan base-package="com.wqh.aop"/>
测试
@Test
public void testT(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(
new String[]{"classpath:/l"});
CompactDisc compactDisc = (CompactDisc) Bean("compactDisc");
compactDisc.playTrack(12);
}
上⾯给指定⽅法传⼊的参数是12,在通知中获取到了该参数
另外:在xml中配置切⾯来处理通知中的参数,其实也差不多,只是把切点表达式放到了XML配置⽂件中。给类添加新的功能
引⼊Spring实战中的知识
在SpringAOP中,我们可以为Bean引⼊新的⽅法。代理调⽤并委托给实现该⽅法的其他对象。
当引⼊接⼝的⽅法被调⽤时,代理会把此调⽤委托给实现了新接⼝的某给其他对象。
使⽤注解⽅式引⼊
代码
⾸先是连接点的接⼝及其实现类
public interface Person {
void say();
}
public class ChinesePerson implements Person {
@Override
public void say() {
System.out.println("说中⽂");
}
}
创建需要添加的功能,这⾥个⼈类扩展⼀个吃的功能
public interface Food {
void eat();
}
public class ChineseFood implements Food {
@Override
public void eat() {
System.out.println("吃中餐");
}
}
编写切⾯
@Aspect
public class addFuction {
@DeclareParents(value = "com.wqh.addfunction.Person+",defaultImpl = ChineseFood.class)
public static Food food;
}
注意这⾥的表达式使⽤的式@DeclareParents注解;该注解所标注的静态属性指明了要引⼊的接⼝。
注解中使⽤的value属性指定哪种类型的bean要引⼊该接⼝,这⾥Person后后⾯的“+”号表⽰所有⼦类型,⽽不是该类的本⾝。defaultImpl,指定了为引⼊功能提供实现的类。
使⽤XML配置bean:
<!--启⽤AspectJ的⾃动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean class="com.wqh.addfunction.addFuction"/>
<bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/>
测试
@Test
public void testAdd(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"classpath:l");
Person person = (Person) Bean("chinesePerson");
person.say();
//这⾥可以将chinesePerson bean转换为Food类,所以添加成功
Food food = (Food) Bean("chinesePerson");
food.eat();
}
在XML中引⼊
⾸先将上⾯的addFuction注解全部删除,其他不变;然后在xml中添加相应的配置:
<!--启⽤AspectJ的⾃动代理-->
<aop:aspectj-autoproxy/>
<!--声明bean-->
<bean name="chinesePerson" class="com.wqh.addfunction.ChinesePerson"/>
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.wqh.addfunction.Person+"
implement-interface="com.wqh.addfunction.Food"
default-impl="com.wqh.addfunction.ChineseFood"/>
</aop:aspect>
</aop:config>
这⾥的types-matching与上⾯的vale作⽤⼀样;
default-impl与defaultImpl作⽤⼀样,这也可以使⽤delegate-ref;当然如果使⽤delegate-ref则是要引⽤SpringBean;implement-interface则是要引⼊的接⼝
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论