AOP的正确打开方式

这里写图片描述

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("最终通知");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值