介绍
如果要重用功能的话,最常见的面向对象技术是继承或者委托。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。在使用切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面。
与大多数技术一样,AOP已经形成自己的术语。描述切面的常用术语有通知(advice)、切点(pointcut)和连接点(join point)。
通知(Advice):
通知定义了切面是什么以及何时使用。
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能。
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
- 返回通知(After-returning):在目标方法成功执行之后调用通知。
- 异常通知(After-throwing):在目标方法抛出异常后调用通知。
- 环绕通知(Around):通知包裹了被通知的方法,在被通知方法调用之前和调用之后执行自定义的行为 。
切点(Poincut):
我们通常使用明确的类和方法名称或者利用正则表达式定义所匹配的类和方法名称来指定这些切点。
切面(Aspect):
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么在何时和何处完成其功能。
连接点(Join point):
是在应用执行过程中能够插入切面的一个点。这个点可以使调用方法时,抛出异常时、甚至修改一个字段的时候。
因为Spring基于动态代理,所以Spring只支持方法级别的连接点。
Spring对AOP的支持:
Spring提供了4种类型的AOP支持:
- 基于代理的经典AOP支持(太过繁琐)
- 纯POJO切面(借助Spring的aop命名空间)
- @AspectJ注解驱动的切面(下面会详细介绍)
- 注入式AspectJ切面
如果你的AOP需求超过了简单的方法调用(如构造器或者属性拦截),前三种都不能帮你实现,那么你需要考虑使用AspectJ来实现切面。
我们来介绍一下使用注解创建切面:
(1)定义切面
@Aspect
public class Audience {
//前置通知(手机静音)
@Before("execution(** concert.Performance.perform(..))")
public void silenceCellPhones(){
System.out.println("Silencing cell phones");
}
//前置通知(坐下)
@Before("execution(** concert.Performance.perform(..))")
public void takeSeats(){
System.out.println("Taking seats");
}
//返回通知(鼓掌)
@AfterReturning("execution(** concert.Performance.perform(..))")
public void applause(){
System.out.println("CLAP CLAP CLAP!");
}
}
@Aspect定义为切面。
@Before等为定义通知。
通知注解传递的字符串参数为切点。
详细解释切点字符串的含义
可以定义切点就会简化切点表达式
//定义切点
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){}
@Before("performance()")
public void silenceCellPhones(){
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats(){
System.out.println("Taking seats");
}
@AfterReturning("performance()")
public void applause(){
System.out.println("CLAP CLAP CLAP!");
}
使用环绕通知(环绕通知相当于before加上after)
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp){
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
//执行前的动作
jp.proceed();
//执行后的动作
System.out.println("CLAP CLAP CLAP");
} catch (Throwable e) {
e.printStackTrace();
}
}
影响类:
public class Performance {
public void perform(){
System.out.println("Performing");
}
}
(2)在JavaConfig类中启用AspectJ注解的自动代理
@Configuration
@EnableAspectJAutoProxy
public class ConcertConfig {
@Bean
public Performance performance(){
return new Performance();
}
@Bean
public Audience audience(){
return new Audience();
}
}
使用@EnableAspectJAutoProxy开启自动代理。
(3)测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=concert.ConcertConfig.class)
public class test {
@Autowired
private Performance performance;
@Test
public void testDemo(){
performance.perform();
}
}
输出结果:
切面不光可以为对象拥有的方法添加新功能,还可以为一个对象增加一个新的方法。我们继续使用注解的方式来实现。
1.定义接口
Person接口
public interface Person {
void likePerson();
}
Person接口的实现类
@Component("women")
public class Women implements Person {
@Override
public void likePerson() {
System.out.println("我是女生,我喜欢帅哥");
}
}
Animal接口
public interface Animal {
void eat();
}
实现类
@Component
public class FemaleAnimal implements Animal {
@Override
public void eat() {
System.out.println("我是雌性,我比雄性更喜欢吃零食");
}
}
2.切面
@Aspect
@Component
public class AspectConfig {
//"+"表示person的所有子类
@DeclareParents(value = "com.lzj.spring.annotation.Person+", defaultImpl = FemaleAnimal.class)
public Animal animal;
}
value属性指定了哪种类型的bean要引入该接口(被添加功能的类),Person增加了eat的方法。
defaultImpl属性指定了引入功能提供实现的类。
DeclareParent注解标注的静态属性(扩展功能的类)
3.JavaConfig类
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AnnotationConfig {
}
4.测试类
public class MainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AnnotationConfig.class);
Person person = (Person) ctx.getBean("women");
person.likePerson();
Animal animal = (Animal)person;
animal.eat();
}
}
SpringAOP(前三种)能够满足许多应用的切面需求,但是与AspectJ相比,SpringAOP是一个功能比较弱的AOP解决方案。AspectJ提供了SpringAOP所不能支持的许多类型的切点。
SpringAOP和AspectJ更多的原理:http://www.importnew.com/24305.html
SpringAOP的底层原理就是动态代理。
Spring的应用场景:AOP与事务、AOP与日志、AOP与缓存。