Aspect Oriented Programming(AOP)译作“面向切面编程”。
看名字确实非常抽象,不容易理解。我们从 是什么,为什么,如何做 来攻克它!
下面是AOP的知识网络图:
原图下载:
http://download.csdn.net/detail/qq_34149805/9821716
一、AOP到底是什么?
AOP 是一种编程思想,是Spring框架中的一个重要内容。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Aop采用的是横向抽取机制,取代了传统的纵向继承体系重复代码。
应用场景:事务管理,权限校验。
如果你看了上面的说法可能还是有点蒙,请看第二点的解释。
二、为什么要使用AOP?
用一个老掉牙的例子来解释:
如果在调用所有方法前都要进行权限校验,在调用所有方法后都要进行日志记录,而我们不能直接在原方法修改。
public class UserviceImp implements UserService {
@Override
public void add() {
System.out.println("add");
}
@Override
public void delete() {
System.out.println("delete");
}
}
我们第一想到的可能是装饰者模式,即直接在原类的基础上再继承出一个装饰者类,这样既不改变原来的代码又可以实现需求。
public class userDecorator implements UserService{
@Override
public void add() {
System.out.println("权限校验");
System.out.println("add");
System.out.println("日志记录");
}
@Override
public void delete() {
System.out.println("权限校验");
System.out.println("delete");
System.out.println("日志记录");
}
}
这样做确实可以解决问题,但却使我们的程序陷入的高度耦合,试想如果有10个service,就要抽象出10个装饰者。
AOP就可以解决这个问题,所谓切面,就是要横向切入每一个目标方法中,而不是纵向继承,拉长代码。
所以,AOP就是如何将目标方法”增强”的一个过程,或者说我们如何将我们自己定义的通知和目标方法结合起来的过程。
JAVA中的动态代理机制,可以创建目标类的代理类,从何实现在代理类中的目标方法前后执行代码。我们会在下面的代码中用JAVA的代理机制自己写一个”AOP”。
当然,代理机制只适用于实现了接口的目标类,Spring中使用了CGLIB来实现对目标类(不实现接口)的增强。
三、如何实现AOP
3.1 AOP 专有名词
下面的实现中,经常会用到一些专有名词,提前科普一下会帮助我们更好的书写程序。
1.target 目标类:需要被代理的类。例如UserService
2.Joinpoint 连接点:可能被拦截到的方法。例如:所有方法
3.PointCut 切入点:已经被增强的连接点,例如:addUser()
4.advice 通知/增强,before,after
5.weaving 织入 把增强advice应用到目标对象target来创建新代理对象Proxy的过程。
6.Proxy 代理类
7.Aspect 切面:是切入点pointcut和通知advice的结合。
3.2 AOP的实现步骤
仔细看来,每种AOP的实现方式都离不开以下三步。
1.创建/声明目标类。
2.创建/声明切面类(声明advice)。
3.将advice织入到目标类中。
我们接下来的程序将以以上三步展开。
3.3 手动实现AOP
3.3.1 使用JDK动态代理
1.创建目标类。
public interface UserService {
void add();
void update();
void delete();
}
2.创建切面类(声明advice)。
public class MyAspect{
public void before(){
System.out.println("鸡首");
}
public void after(){
System.out.println("牛后");
}
}
3.将advice织入到目标类中。
public static UserService cteateService(){
//1.目标类
UserService userService = new UserviceImp();
//2.切面类
MyAspect myAspect = new MyAspect();
//3.代理类:将目标类(切入点)和切面类(通知)结合-->切面
//参数2: 还可以使用 new Class[]{UserService.class}
UserService proxService = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
myAspect.before();
Object invoke = (UserService) method.invoke(userService, args);
myAspect.after();
return invoke;
}
});
return proxService;
}
3.3.2 CGLIB字节码增强
public static UserviceImp cteateService(){
final UserviceImp uService = new UserviceImp();
final MyAspect myAspect = new MyAspect();
/*
* 代理类,采用cglib,底层创建目标类的自雷
* */
//核心类
Enhancer enhancer = new Enhancer();
//确定父类
enhancer.setSuperclass(uService.getClass());
//回调函数,等效jdk中InvocationHandler
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
myAspect.before();
Object invoke = method.invoke(uService, objects);
//执行代理类的父类,执行目标类(目标类和代理类是父子关系)
methodProxy.invokeSuper(o,objects);
myAspect.after();
return invoke;
}
});
UserviceImp proxService = (UserviceImp) enhancer.create();
return proxService;
}
3.4 AspectJ 实现AOP
AspectJ是一个面向切面的框架,它扩展了Java语言,定义了AOP语法。
3.4.1 AspectJ入门案例
1.声明目标类
2.声明切面
/**
* 切面类中确定通知,需要实现不同接口,接口就是规范,从而确定方法名称。
* 采用环绕通知,(必须手动执行目标方法)
*/
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前方法");
Object proceed = methodInvocation.proceed();
System.out.println("后方法");
return proceed;
}
}
3.将advice织入到目标类中。
<?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:context="http://www.springframework.org/schema/context"
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.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 创建目标类-->
<bean id="userService" class="com.aop.lishiqi.c_spring_aop.UserviceImp"></bean>
<!-- 创建切面类-->
<bean id="myAspect" class="com.aop.lishiqi.c_spring_aop.MyAspect"></bean>
<!-- aop,添加约束
使用<aop:config>进行配置
<aop:pointcut>切入点,从目标对象获得具体方法
<aop:advisor> 特殊的切面,只有一个通知和一个切入点
advice-ref 通知引用 pointcut-ref:通知引用
切入点表达式:execution(* com.aop.lishiqi.c_spring_aop.*.*(..))
选择方法 返回值任意 包 类名任意,方法任意 参数任意
<aop:pointcut>
-->
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="myAspect" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
</beans>
在Spring配置文件中将目标类和切面类配置为bean。
可以发现,在 <aop:config>
中使用 <aop:pointcut
首先声明切入点,即我们要将那些方法织入通知。
然后使用 <aop:advisor
将切入点和切面关联起来,这是其中一种写法,下面的代码我们也用 <aop:aspect
来实现。
3.4.2 expression语法规则
<aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut>
这句话用来声明哪些目标方法是切入点。
expression的语法规则是:
execution(修饰符 返回值 包.类.方法(参数)throws异常)
3.4.3 通知类型
我们在入门案例中使用的是环绕通知,AspectJ中还有其他几种类型,他们各有不同的特点。
前置通知:应用:各种数据校验,在方法前执行,如果排除异常阻止方法运行。
后置通知:方法正常返回后执行,如果方法中抛出异常,阻止方法运行
环绕通知:方法执行前后执行,可以阻止方法执行,必须手动执行目标方法
异常通知:抛出异常时执行
最终通知:无论发生什么最终都执行
我们可以通过实现接口和<aop:advisor
来使用他们,也可以使用自定义方法和<aop:aspect
来实现。
下面以第二种写法分别展示他们的用法。
后置通知:
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知"+joinPoint.getSignature().getName());
}
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.aop.lishiqi.c_spring_aop.*.*(..))"></aop:pointcut>
<aop:aspect ref="myAspect">
<aop:before method="myBefore" pointcut-ref="myPointCut"></aop:before>
</aop:aspect>
</aop:config>
后置通知:
<!--后置通知
returning:通知方法第二个参数的名称
-->
<aop:after-returning method="myAfterreturning" pointcut-ref="myPointCut" returning="ret"></aop:after-returning>
//第二个参数是切入点的返回值
public void myAfterreturning(JoinPoint joinPoint,Object ret){
System.out.println("后置通知"+joinPoint.getSignature().getName()+ret);
}
环绕通知:
<!--环绕通知
返回值类型必须为Object
参数 ProceedingJoinPoint 需要抛出异常
执行目标方法:Object proceed = joinPoint.proceed();
方法名 任意-->
<aop:around method="myAround" pointcut-ref="myPointCut"></aop:around>
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
//手动执行目标方法
System.out.println("前通知");
Object proceed = joinPoint.proceed();
System.out.println("后通知");
return proceed;
}
异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("抛出异常通知");
System.out.println(e);
}
<!-- 抛出异常通知
参数1 连接点的描述对象
参数2 获得一场因戏 类型Throwable 参数名由throwing="e"配置
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"></aop:after-throwing>
最终通知:
public void myAfter(JoinPoint joinPoint){
System.out.println("最终通知");
}
<!-- 最终通知
无论发生什么最终都执行
-->
<aop:after method="myAfter" pointcut-ref="myPointCut"></aop:after
3.5 AOP注解开发
注解是代替XML的配置,所以以上所有的XML都可以用注解替代。
扫描包:
<context:component-scan base-package="com.aop.lishiqi.d_aspect_注解"></context:component-scan>
确定AOP注解生效:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
切面类:
//声明切面
@Component("myAspect")
@Aspect
public class MyAspect {
//声明切入点
@Pointcut("execution(* com.aop.lishiqi.c_spring_aop.*.*(..)) ")
private void myPointCut(){
}
@Before("execution(* com.aop.lishiqi.c_spring_aop.*.*(..))")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知"+joinPoint.getSignature().getName());
}
@AfterReturning(value="myPointCut()",returning = "ret")
public void myAfterreturning(JoinPoint joinPoint,Object ret){
System.out.println("后置通知"+joinPoint.getSignature().getName()+ret);
}
@Around(value = "myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
//手动执行目标方法
System.out.println("前通知");
Object proceed = joinPoint.proceed();
System.out.println("后通知");
return proceed;
}
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("抛出异常通知");
System.out.println(e);
}
@After("myPointCut()")
public void myAfter(JoinPoint joinPoint){
System.out.println("最终通知");
}
}