在我的CSDN博客的Spring分类里面,之前就已经有三篇关于面向切面编程的博客了,最近在阅读《Spring实战》这本书,不禁感叹这确实是一本好书,需要细细的阅读,今天再记述一下Spring AOP的知识,一些AOP的术语,这里就不说了,之前的博客里面都有介绍!下面开始重点:
除了添加Spring所需要的jar包之外,还需要添加aspect相关的jar包,点击下载aspect相关jar包
一、Spring对AOP的支持
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面
- @AspectJ注解驱动切面
- 注入式AspectJ切面(适用于Spring各版本)
前三种都是Spring AOP实现的变体,Spring AOP 构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
二、通过切点来选择连接点
切点用于准确定位应该在什么地方应用切面的通知。
1、Spring借助AspectJ的切点表达式语言来定义Spring切面:
AspectJ指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@arg() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限制匹配带有指定注解的连接点
注意:以上只有execution指示器是实际执行匹配的,其他指示器都是用来限制匹配的。
2、编写切点:
假设有一个业务接口,取名为Performance(这个类在com.mfc.annotation.noparameter包下),接口里有一个方法perform(),现在要写一个切点表达式,这个切点表达式能设置当前的perform()方法执行时处罚通知的调用,那么这个表达式是: execution(* com.mfc.annotation.noparameter.Performance.perform(..))
对这个表达式解释一下:表达式以“*”开始,表名不关心返回值类型,然后使用了全限定类名和方法名,对于方法参数列表,使用了两个点号(..)表明其诶单要选择任意的perform()方法,无论该方法的参数是什么。
三、使用注解创建切面:
1、通知中没有参数:
@AfterReturning 通知方法在目标方法执行成功之后调用
@AfterThrowing 通知方法在目标方法执行抛出异常后调用
@Around 环绕通知:通知方法将目标方法封装起来
@Before 通知方法在目标方法执行之前调用
package com.mfc.annotation.noparameter;
/**
* 2017年11月18日19:25:40
* 目标接口
* */
public interface Performance {
public void perform();
}
package com.mfc.annotation.noparameter;
import org.springframework.stereotype.Component;
/**
* 2017年11月18日19:26:43
* 目标方法的类,继承了目标接口
* perform()方法作为切点
* */
@Component("performance")
public class PerformanceImpl implements Performance {
@Override
public void perform() {
System.out.println("-------开始表演!");
}
}
定义切面:
package com.mfc.annotation.noparameter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 2017年11月18日19:28:16
* 切面类
* */
@Aspect
public class Audience {
/*
* 这里的performance()方法只是一个标识,供@Pointcut注解依附,并没有其他意义
* */
//定义命名的切点
@Pointcut("execution(** com.mfc.annotation.noparameter.Performance.perform(..))")
public void performance(){}
/*
* 其实这个类中可以不用performance()存在,如果performance()不存在:
* 一下的通知@Before就要写成@Before("execution(** com.mfc.annotation.Performance.perform(..))")
* 如此的话,@After等注解每一个注解后面都要加上("execution(** com.mfc.annotation.Performance.perform(..))")
* 代码重复真不是什么光彩的事情,所以有了performance()存在,下面的@After等注解后面就可以直接加上("performance()")了。
*
* Spring使用AspectJ注解来声明通知的方法
* @After 通知方法在目标方法执行成功或者抛出异常后调用
* @AfterReturning 通知方法在目标方法执行成功之后调用
* @AfterThrowing 通知方法在目标方法执行抛出异常后调用
* @Around 环绕通知:通知方法将目标方法封装起来
* @Before 通知方法在目标方法执行之前调用
* */
/*
@Before("performance()")
public void beforeAdvice(){
System.out.println("前置通知:表演要开始了,请将手机调制静音!");
}
@AfterReturning("performance()")
public void successAfterAdvice(){
System.out.println("成功后置通知:表演完美结束,现在开始鼓掌!");
}
@AfterThrowing("performance()")
public void failAfterAdvice(){
System.out.println("失败后置通知:表演出意外了,请大家谅解!");
}
*/
//环绕通知
@Around("performance()")
public void aroundAdvice(ProceedingJoinPoint jp){
try {
System.out.println("前置通知:表演要开始了,请将手机调制静音!");
jp.proceed();
System.out.println("成功后置通知:表演完美结束,现在开始鼓掌!");
} catch (Throwable e) {
System.out.println("失败后置通知:表演出意外了,请大家谅解!");
e.printStackTrace();
}
}
}
再看看java的配置类,这里使用了自动装配和java装配混合:
package com.mfc.annotation.noparameter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 2017年11月18日19:52:35
* java配置类,这里采用了bean的自动装配
* @EnableAspectJAutoProxy注解是启用Spring的自动代理功能
* 也可以在xml配置文件中使用<aop:aspectj-autoproxy />代替@EnableAspectJAutoProxy
* */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.mfc.annotation")
public class JavaConfig {
@Bean
public Audience audience(){
return new Audience();
}
}
最后看测试类:
package com.mfc.annotation.noparameter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigAopTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
Performance performance = (Performance) applicationContext.getBean("performance");
performance.perform();
}
}
测试结果:
2、通知中有参数
业务类接口及其实现:
package com.mfc.annotation.hasparameter;
/**
* 2017年11月18日21:15:58
* 接口
* */
public interface CompactDisc {
public void play();
public void playTrack(String trackNumber);
}
package com.mfc.annotation.hasparameter;
import java.util.List;
/**
* 2017年11月18日21:16:51
* 实现类
* */
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracts;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracts(List<String> tracts) {
this.tracts = tracts;
}
@Override
public void play() {
System.out.println("Playing "+title+" by "+artist);
for (String string : tracts) {
System.out.println("-Track : "+string);
}
}
@Override
public void playTrack(String trackNumber){
System.out.println("播放了:"+trackNumber);
}
}
定义切面:
package com.mfc.annotation.hasparameter;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 2017年11月18日21:35:33
* 切面类
* */
@Aspect
public class TrackCounter {
Map<String, Integer> trackCounts = new HashMap<String, Integer>();
//通知playTrack方法
/*
* 这个切点表达式应该关注的是表达式中的args(trackNumber)限定符。
* 它表明传递给playTrack()方法的String类型参数也会传递到通知中去,
* 参数的名称trackNumber也与切点方法签名中的参数相匹配
* */
@Pointcut("execution(* com.mfc.annotation.hasparameter.CompactDisc.playTrack(String)) && args(trackNumber)")
public void trackPlayer(String trackNumber){}
@Before("trackPlayer(trackNumber)")
public void countTrack(String trackNumber){
System.out.println(trackNumber+"触发了这个前置通知");
int count = getPlayCount(trackNumber);
trackCounts.put(trackNumber, count+1);
}
public int getPlayCount(String trackNumber){
//containsKey判断map集合中是否存在制定的键名
return trackCounts.containsKey(trackNumber)?trackCounts.get(trackNumber):0;
}
}
配置java:
package com.mfc.annotation.hasparameter;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 2017年11月18日21:36:07
* java配置类
* */
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {
@Bean(name = "compactDisc")
public CompactDisc sgtPepers(){
BlankDisc blankDisc = new BlankDisc();
blankDisc.setTitle("测试标题");
blankDisc.setArtist("测试艺术家");
List<String> tracks = new ArrayList<String>();
tracks.add("测试数据一");
tracks.add("测试数据二");
tracks.add("测试数据三");
tracks.add("测试数据四");
return blankDisc;
}
@Bean(name = "trackCounter")
public TrackCounter trackCounter(){
return new TrackCounter();
}
}
测试:
package com.mfc.annotation.hasparameter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 2017年11月18日21:40:44
* 测试类
* */
public class TrackCounterTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TrackCounterConfig.class);
CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("compactDisc");
TrackCounter counter = (TrackCounter) applicationContext.getBean("trackCounter");
compactDisc.playTrack("A");
compactDisc.playTrack("A");
compactDisc.playTrack("B");
compactDisc.playTrack("C");
compactDisc.playTrack("B");
compactDisc.playTrack("B");
System.out.println("A--------"+counter.getPlayCount("A"));
System.out.println("B--------"+counter.getPlayCount("B"));
System.out.println("C--------"+counter.getPlayCount("C"));
}
}
测试结果:
3、通过注解引入新功能:
一般情况,一个java类写好了就这些功能,除非修改这个java类,否则它不会有新的功能,现在Spring可以实现一个功能:写好一个java类之后,不用修改它,给它添加新的功能:
基础功能的业务类接口及其实现:
package com.mfc.annotation.newfunction;
public interface BaseFunction {
public void baseFunction();
}
package com.mfc.annotation.newfunction;
import org.springframework.stereotype.Component;
@Component("baseFunction")
public class BaseFunctionImpl implements BaseFunction {
@Override
public void baseFunction() {
System.out.println("这是BaseFunctionImpl的基础功能方法");
}
}
新添加功能的业务类及其接口:
package com.mfc.annotation.newfunction;
public interface NewFunction {
public void newFunction();
}
package com.mfc.annotation.newfunction;
import org.springframework.stereotype.Component;
@Component("newFunction")
public class NewFunctionImpl implements NewFunction {
@Override
public void newFunction() {
System.out.println("这是通过NewFunctionImpl给BaseFunctionImpl添加的新功能方法");
}
}
AOP配置:
package com.mfc.annotation.newfunction;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AopConfig {
/**
* 2017年11月18日22:32:17
* value 指定了那种类型的Bean要引入该接口,在本例中,就是所有实现了BaseFunction的类型,加号代表BaseFunction的所有子类,而不是它本身
* defaultImpl 指定了为引入功能提供实现的类
* */
@DeclareParents(value = "com.mfc.annotation.newfunction.BaseFunction+",
defaultImpl = NewFunctionImpl.class)
public static NewFunction function;
}
java配置:
package com.mfc.annotation.newfunction;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 2017年11月18日22:16:07
* java配置文件
* */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.mfc.annotation.newfunction")
public class JavaConfig {
}
测试类:
package com.mfc.annotation.newfunction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 2017年11月18日22:35:14
* 测试类
* */
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
BaseFunction baseFunction = (BaseFunction) applicationContext.getBean("baseFunction");
baseFunction.baseFunction();
NewFunction newFunction = (NewFunction) baseFunction;
newFunction.newFunction();
}
}
测试结果:
四、在XML中声明切面:
在《Spring实战》中有一句话:基于注解的配置要由于基于java的配置,基于java的配置要基于XML的配置。但是,如果需要声明切面,又不能为通知类添加注解,就必须使用xml配置。
1、通知中没有参数:
Spring的AOP配置元素能够以非侵入性的方式声明切面
AOP配置元素 用途
<aop:advisor> 定义AOP通知器
<aop:after> 定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning> 定义AOP返回通知(被通知方法执行成功后通知)
<aop:after-throwing> 定义AOP异常通知(被通知方法执行失败后通知)
<aop:around> 定义AOP环绕通知
<aop:aspect> 定义一个切面
<aop:aspectj-autoproxy> 启用@Aspect注解(自动代理Aspect注解的通知类)
<aop:before> 定义一个AOP前置通知
<aop:config> 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:declare-parents> 以透明的方式为被通知的对象引入额外的接口
<aop:pointcut> 定义一个切点
示例:
业务类接口与实现跟上面(三)中的(1)一样,Performance的接口以及它的实现类。切面类把(三)中的(1)的切面类(Audience.java)里面的注解全部删掉即可。
看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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="performanceImpl" class="com.mfc.xml.noparameter.PerformanceImpl"></bean>
<bean id="audience" class="com.mfc.xml.noparameter.Audience"></bean>
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
expression="execution(* com.mfc.xml.noparameter.Performance.perform(..))"
id="performance"/>
<!--
<aop:before method="beforeAdvice" pointcut-ref="performance"/>
<aop:after-returning method="successAfterAdvice" pointcut-ref="performance"/>
<aop:after-throwing method="failAfterAdvice" pointcut-ref="performance"/>
-->
<aop:around method="aroundAdvice" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
package com.mfc.xml.noparameter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlConfigTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("noparameter.xml");
Performance performance = (Performance) applicationContext.getBean("performanceImpl");
performance.perform();
}
}
测试结果:
2、通知中有参数:
业务类接口及其实现跟上面(三)中的(2)一样,CompactDisc的接口一起它的实现类。切面类把(三)中的(2)的切面类(TrackCounter.java)里面的注解全部删掉即可。
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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="trackCounter" class="com.mfc.xml.hasparameter.TrackCounter"></bean>
<bean id="blankDisc" class="com.mfc.xml.hasparameter.BlankDisc">
<property name="title" value="测试标题"></property>
<property name="artist" value="测试内容"></property>
<property name="tracts">
<list>
<value>测试数据一</value>
<value>测试数据二</value>
<value>测试数据三</value>
</list>
</property>
</bean>
<aop:config>
<aop:aspect ref="trackCounter">
<aop:pointcut
expression="execution(* com.mfc.xml.hasparameter.CompactDisc.playTrack(String)) and args(trackNumber)"
id="performance"/>
<aop:before method="countTrack" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
package com.mfc.xml.hasparameter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 2017年11月18日21:40:44
* 测试类
* */
public class TrackCounterTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("hasparameter.xml");
CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("blankDisc");
TrackCounter counter = (TrackCounter) applicationContext.getBean("trackCounter");
compactDisc.playTrack("A");
compactDisc.playTrack("A");
compactDisc.playTrack("B");
compactDisc.playTrack("C");
compactDisc.playTrack("B");
compactDisc.playTrack("B");
System.out.println("A--------"+counter.getPlayCount("A"));
System.out.println("B--------"+counter.getPlayCount("B"));
System.out.println("C--------"+counter.getPlayCount("C"));
}
}
测试结果:
2、通过切面引入新的功能
基础功能的业务类接口及其实现和新添加功能的业务类及其接口和(三)中(3)一样,这里可以不要java配置类JavaConfig.java了。
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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="baseFunctionImpl" class="com.mfc.xml.newfunction.BaseFunctionImpl"></bean>
<bean id="newFunctionImpl" class="com.mfc.xml.newfunction.NewFunctionImpl"></bean>
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.mfc.xml.newfunction.BaseFunction+"
implement-interface="com.mfc.xml.newfunction.NewFunction"
default-impl="com.mfc.xml.newfunction.NewFunctionImpl"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
package com.mfc.xml.newfunction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 2017年11月18日22:35:14
* 测试类
* */
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("newfunction.xml");
BaseFunction baseFunction = (BaseFunction) applicationContext.getBean("baseFunctionImpl");
baseFunction.baseFunction();
NewFunction newFunction = (NewFunction) baseFunction;
newFunction.newFunction();
}
}
测试结果:
源码下载