AOP-面向切面编程
初学spring4.3,喜欢使用纯java代码和注解方式来进行Spring内的一些相关配置工作,AOP作为Sping框架的一个重要特点,需要深入学习,本问记录了一个菜鸟初次接触Sping-AOP的学习过程,多有谬误,还请各路大神多多指教:
什么是AOP?有什么用?
AOP即面向切面编程。在应用程序中,有一些功能会散布在应用中的多处(如日志/安全/事务),这些功能十分重要但却又与业务逻辑没有多大关联,通常我们称呼这些功能为“横切关注点”。)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。
AOP术语
通知,切面,切点,连接点,织入,引入
切面 是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
通知 切面的工作被称为通知。通知定义了切面是什么以及何时作用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
切点 定义了应用通知的地点。
JUST DO IT!
1.通过切点来选择连接点
Spring只支持方法级别的连接点
1.1 定义一个接口,对于一场演出而言,表演是很重要的:
package concert;
public interface Performance {
public void perform();
}
1.2 这次演出Duck将上台演出,他是接口的一个实现:
package concert;
public class Duckperform implements Performance{
@Override
public void perform() {
System.out.println("Duck sings: I believe i can fly!");
}
}
我们需要编写Performance中perform方法触发的通知,即当程序调用perform()时触发通知,需要通过该方法定义一个切点:
execution(* concert.Duckperform.perform(..))
我们使用execution()指示器选择Performance的perform()方法。方法表达式以“*”号开始,表明了我们不关心方法返回值的类型然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
现在假设我们需要配置的切点仅匹配concert包。在此场景下,可以使用within()指示器来限制匹配(与/或/非):
execution(* concert.Duckperform.perform(..))&&within(concert.*)
Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用beanID或者bean名称作为参数来限制切点只匹配特定的bean(and和!and)。
execution(* concert.Duckperform.perform(..)) and bean('ID')
2.使用注解来创建切面
2.1 创建切面
如果一场演出没有观众的话,那不能称之为演出。从演出的角度来看,观众是非常重要的,但是对演出本身的功能来讲,它并不是核心,这是一个单独的关注点。因此,将观众定义为一个切面,并将其应用到演出上就是较为明智的做法.
package concert;
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;
@Aspect
public class Audience {
//@Pointcut注解能够在一个@AspectJ切面内定义可重用的切点。
@Pointcut("execution(* concert.Duckperform.perform(..))")
public void performance(){}
//实用一个环绕通知代替之前多个不同的前置通知和后置通知
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("Sliencing cell phones!");
System.out.println("Taking seats!");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund!");
}
}
/*
@Before("performance()")
public void sillenceCellPhones(){
System.out.println("Sillencing cell phones!");
}
@Before("performance()")
public void takeSeats(){
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来申明通知方法,在Spring中使用注解来申明通知方法有以下几种:
@After:通知方法会在目标方法返回或抛出异常后调用
@AfterReturning:通知方法会在目标方法返回后调用
@AfterThrowing:通知方法会在目标方法抛出异常后调用
@Around:通知方法会将目标方法封装起来
@Before:通知方法会在目标方法调用之前执行
2.2 切面配置
使用注解方式来进行配置工作:
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 //启用自动代理
@ComponentScan
public class AopConfig {
@Bean(name="Duck")
public Performance duckperform(){
return new Duckperform();
}
@Bean
public Audience audience(){
return new Audience();
}
}
一个简单的配置已经完成,接下来测试是否配置成功。
3.测试
测试代码如下:
package concert;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(AopConfig.class);
Performance dk=(Performance) context.getBean("Duck");
dk.perform();
}
}
测试结果:
Sliencing cell phones!
Taking seats!
Duck sings: I believe i can fly!
CLAP CLAP CLAP!!!
在测试代码中,有一句代码值得思考:
Performance dk=(Performance) context.getBean("Duck");
按自己的想法,如果将其改为:
Duckperform dk=(Duckperform) context.getBean("Duck");
程序运行结果应该也会相同,然而事实并非如此,在开启了自动代理(@EnableAspectJAutoProxy)的情况下,程序将报错;把“Duck”改为Duckperform.class也会报错。查资料得知,这与在Spring中设置的代理方式有关:基于接口的还是基于类的代理被创建。默认情况下基于接口代理,则修改后的代码不可用,如果改为基于类的代理( @EnableAspectJAutoProxy(proxyTargetClass=true)),程序可正常运行。关于代理方式问题,值得进一步理解学习。
注:在Spring本身是不支持@Aspect这种注解的,如需实现文中的配置,需要添加aspectj库,该库可以在ecilipse官网找到,贴下下载地址。
http://www.eclipse.org/aspectj/downloads.php