您的位置:首页 > 运维架构

AOP的正确打开方式

2017-04-21 21:48 232 查看


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("最终通知"); }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring aop 框架