AOP
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
首先
在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。
AspectJ 中几个必须要了解的概念:
Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice:Advice 定义了在 pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target: 被通知的对象。通俗的说,就是与业务本身相关的原始对象。
Proxy: 向目标对象应用通知之后创建的对象。通俗的说,就是原始对象用代理对象包装了一层又一层的对象,就是图中最上面的一大坨方框。
AspectJ 支持 5 种类型的通知注解:
@Before : 前置通知, 在方法执行之前执行
@After : 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around : 环绕通知, 围绕着方法执行。
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.
对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
下面用实例来讲解:
//加入jar包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
//建立Spring配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.lyf.C">
<context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"></context:include-filter>
<!--含义是允许base-package包中使用基于面向切面的注解-->
</context:component-scan>
<aop:aspectj-autoproxy/> <!--含义是启用Spring对aspectj切面配置的支持-->
</beans>
package com.lyf.C;
import org.springframework.stereotype.Component;
/**
* Created by fangjiejie on 2017/4/25.
*/
@Component
public class Animal {
public String play(String name){
System.out.println("动物也有娱乐活动比如:"+name);
// System.out.println(6/0);
return "666";
}
}
package com.lyf.C;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
/**
* Created by fangjiejie on 2017/4/25.
*/
@Aspect
@Order(1)//设置横切关注点的优先级顺序
public class Dog {
/*
* 在目标方法执行之前,执行该通知
*/
@Before("execution(* com.lyf.C.Animal.*(..))")
void bark1(){
System.out.println("Dog来打哈哈---Before");
}
/*
* 在目标方法执行之后(无论是否发生异常),执行该通知
*/
@After("execution(* com.lyf.C.Animal.*(..))")
void bark2(){
System.out.println("Dog来打哈哈---After");
}
/*
* 在目标方法正常执行之后执行的代码
* 返回通知是可以访问目标方法的返回值的
*/
@AfterReturning(pointcut = "execution(* com.lyf.C.Animal.*(..))",returning = "ret")//自定义的ret必须在该方法的形参中出现
//如果注解中参数有多个,要用逗号来链接,参数都要以键值对的形式存在
void bark3(String ret){
System.out.println("Dog来打哈哈---AfterReturning"+"---"+ret);
}
/*
* 回环通知需要携带ProceedingJoinPoint类型参数
* 回环通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
* 且回环通知必须有返回值,返回值即为目标方法的返回值
*/
@Around("execution(* com.lyf.C.Animal.play(..))")
String bark4(ProceedingJoinPoint joinPoint){
String rs=null;
try {
Object os[]=joinPoint.getArgs();//可以获取目标方法的形参
System.out.println("Dog来打哈哈---Around--before"+"---"+os[0]);
rs=(String) joinPoint.proceed();//以插入点形式执行,执行后用rs来保留目标方法的返回值
System.out.println("Dog来打哈哈---Around--after"+"---"+os[0]);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return rs;
}
/*
* 在目标方法出现异常时出现代码
* 可以访问到异常对象.
*/
@AfterThrowing(pointcut = "execution(* com.lyf.C.Animal.play(..))",throwing = "ex")
public void bark5(Throwable ex){
System.out.println("抛出异常"+ex);
}
}
package com.lyf.C;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
/**
* Created by fangjiejie on 2017/4/25.
*/
@Aspect
@Order(2)
public class Cat {
@Before("execution(* com.lyf.C.Animal.*(..))")
void bark1(){
System.out.println("Cat来打哈哈---Before");
}
@After("execution(* com.lyf.C.Animal.*(..))")
void bark2(){
System.out.println("Cat来打哈哈---After");
}
void bark3(String ret){
System.out.println("Cat来打哈哈---AfterReturning"+"---"+ret);
}
@Around("execution(* com.lyf.C.Animal.play(..))")
String bark4(ProceedingJoinPoint joinPoint){
String rs=null;
try {
Object os[]=joinPoint.getArgs();//可以获取目标方法的形参
System.out.println("Cat来打哈哈---Around--before"+"---"+os[0]);
rs=(String) joinPoint.proceed();//以插入点形式执行,执行后用rs来保留目标方法的返回值
System.out.println("Cat来打哈哈---Around--after"+"---"+os[0]);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return rs;
}
@AfterThrowing(pointcut = "execution(* com.lyf.C.Animal.play(..))",throwing = "ex")
public void bark5(Throwable ex){
System.out.println("抛出异常"+ex);
}
}
package com.lyf.C;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by fangjiejie on 2017/4/25.
*/
public class Test {
public static void main(String[] args) {
ApplicationContext act=new ClassPathXmlApplicationContext("bean1.xml");
Animal animal=(Animal) act.getBean("animal");
animal.play("swim");
}
}
执行结果:
Dog来打哈哈---Around--before---swim
Dog来打哈哈---Before
Cat来打哈哈---Around--before---swim
Cat来打哈哈---Before
动物也有娱乐活动比如:swim
Cat来打哈哈---Around--after---swim
Cat来打哈哈---After
Dog来打哈哈---Around--after---swim
Dog来打哈哈---After
Dog来打哈哈---AfterReturning---666
我们可以自行从执行结果中分析出执行顺序。