跟着柴毛毛学Spring(4)——面向切面编程![这里写图片描述](http://img.blog.csdn.net/20171031111402095)

面向切面编程简介

1. 什么是面向切面编程?

  面向切面编程是Spring的第二大特性,它能将一个函数中非主体但有很必要的代码封装到一个单独的类中,在程序运行的时候再把它们插入到函数中。这样能使程序猿只关注函数的主体功能,而且写出来的代码具有具有较强的可读性,简约明了。

2. 面向切面编程的优点

面向切面编程的优点有两个: 1. 一个函数中所有额外的功能都被封装在一个类中,而不是分散在函数的各处。 2. 由于将非主体功能的代码转移到其他类中,因此函数的代码将更佳简洁。

3. Spring的面向切面编程特点

目前面向切面(AOP)市场的三足鼎立格局是这样的: - AspectJ - JBoss Aspect - Spring Aspect Spring Aspect借鉴AspectJ。

  所谓面向切面,前面已经介绍过,就是将函数的一部分代码抽取出来,封装到一个类中,然后在某一时刻将其插入到程序中去。 AOP世界中,将切面插入的到程序中的时机有以下三种: 1. 编译时插入 2. 类被加载的时候插入 3. 程序运行时被插入

  Spring是在程序运行期间将切面插入到相关函数中去的。   此外,在编译时插入需要特殊的编译器,在类加载的时候插入需要特殊的类加载器,而在运行时插入不需要额外的软件支持。

开始使用

  假设现在有一个函数,用来查询数据库中名字为name的Person对象:

    public String queryPersons(String name){
        //查询的具体操作……
        //此处省略100行代码……
    }

  我们需要在查询开始前判断name是否为空,在查询结束后记录操作日志,在查询发生异常时记录异常日志。 在过去,我们把这些代码都直接写在这个函数中,这样会导致这个函数很长,可读性很差。下面我们使用面向切面编程的思想解决这个问题。

1. 定义切面代码

  首先需要将这个函数中额外的功能封装到一个类中,这个类就是一个普通类,功能封装在函数中:

class PersonAspect(){
    /**
      * 判断参数是否为空
      */
    public void verifyNull(String name){
        //判断参数是否为空
    }

    /**
      * 记录操作日志
      */
    public void logInfo(){

    }

    /**
      * 记录异常日志
      */
    public void logError(){

    }
}

2. 将切面声明为bean

在Spring的配置文件中添加bean的声明:

    <bean id="personAspect" class="com.njupt.PersonAspect">
    </bean>

或者直接在PersonAspect类上使用@Componet标注:

@Componet("personAspect")
class PersonAspect(){
    /**
      * 判断参数是否为空
      */
    public void verifyNull(String name){
        //判断参数是否为空
    }

    /**
      * 记录操作日志
      */
    public void logInfo(){

    }

    /**
      * 记录异常日志
      */
    public void logError(){

    }
}

3. 声明通知

  定义好了额外的功能之后,接下来我们需要告诉Spring,这些功能需要在何时添加到哪个函数的什么位置,因此我们需要在Spring的配置文件中作如下声明:

    <aop:config>
        <aop:aspect ref="personAspect">
            <!-- 在queryPersons函数执行之前先运行函数verifyNull -->
            <aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />

            <!-- 在queryPersons函数执行之后再运行函数logInfo -->
            <aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />

            <!-- 在queryPersons函数发生异常时运行函数logError -->
            <aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />
    </aop:config>

到此为止,Spring AOP已经可以运行了!

4. 声明环绕通知

  在上述示例中,函数前插入一段代码,函数后插入一段代码,函数异常时插入一段代码,这些代码块都是独立的,无法共享数据。如果需要实现函数前后代码块的数据共享,那就要使用环绕通知。 说来也很简单,首先需要在PersonAspect类中加上一个参数为ProceedingJoinPoint的函数,如下所示:

@Componet("personAspect")
class PersonAspect(){
    /**
      * 判断参数是否为空
      */
    public void verifyNull(String name){
        //判断参数是否为空
    }

    /**
      * 记录操作日志
      */
    public void logInfo(){

    }

    /**
      * 记录异常日志
      */
    public void logError(){

    }

    /**
      * 环绕通知
      */
    public void xxx(ProceedingJoinPoint joinPoint){
        int num = 0;
        //在函数前执行一些功能,修改num参数

        //执行函数
        joinPoint.proceed();

        //在函数后处理num
        num++;
        //……
    }
}

然后在配置文件中把这个函数声明为环绕通知即可:

    <aop:config>
        <aop:aspect ref="personAspect">
            <!-- 在queryPersons函数执行之前先运行函数verifyNull -->
            <aop:before pointcut="excution(* com.njupt.Person.queryPersons(..))" method="verifyNull" />

            <!-- 在queryPersons函数执行之后再运行函数logInfo -->
            <aop:after-returning pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logInfo" />

            <!-- 在queryPersons函数发生异常时运行函数logError -->
            <aop:throwing pointcut="excution(* com.njupt.Person.queryPersons(..))" method="logError" />

            <!-- 环绕通知 -->
            <aop:around pointcut="excution(* com.njupt.Person.queryPersons(..))" method="xxx()" />
        </aop:aspect>
    </aop:config>

到此为止,Spring的AOP介绍完毕,下面介绍如何使用注解取代XML。

使用注解代替XML

1. 注解切面

  首先需要在切面类上加上注解@Aspect,告诉Spring这个类是个切面类;   其次需要在切面类中创建一个函数,用于定义切点。该函数的名字即为切点名,函数返回值和参数均为空。

@Aspect
class PersonAspect(){
    /**
      * 定义切点
      */
    @Pointcut("excution(* com.njupt.Person.queryPersons(..))")
    public void personAspect(){
    }

    /**
      * 判断参数是否为空
      */
    @Before("personAspect()")
    public void verifyNull(String name){
        //判断参数是否为空
    }

    /**
      * 记录操作日志
      */
    @AfterReturning("personAspect()")
    public void logInfo(){

    }

    /**
      * 记录异常日志
      */
    @AfterThrowing("personAspect()")
    public void logError(){

    }

    /**
      * 环绕通知
      */
    @Around("personAspect()")
    public void xxx(ProceedingJoinPoint joinPoint){
        int num = 0;
        //在函数前执行一些功能,修改num参数

        //执行函数
        joinPoint.proceed();

        //在函数后处理num
        num++;
        //……
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术记录

Netty-Websocket 根据URL路由,分发机制的实现

最近在做netty整合websocket,发现网上很多项目都是最简单的demo,单例的一个项目。 然而公司的项目需要接受几个不同功能的ws协议消息,因此最好是用...

4445
来自专栏微信公众号:Java团长

Java异常处理和设计

在程序设计中,进行异常处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。试想一下,如果一个项目从头到...

1013
来自专栏Java与Android技术栈

Kotlin Coroutines 笔记 (一)

在操作系统中,我们知道进程和线程的概念以及区别。而协程相比于线程更加轻量级,协程又称微线程。

692
来自专栏nnngu

02 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之Service层

项目源代码:https://github.com/nnngu/nguSeckill ---- 首先在编写Service层代码前,我们应该首先要知道这一层到底是...

6469
来自专栏刘君君

JDK8的CAS实现学习笔记

1886
来自专栏微信公众号:Java团长

Java动态代理原理及解析

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类...

544
来自专栏Java 源码分析

Java 虚拟机运行时数据区

运行时数据区: Java 虚拟机的运行时数据区按照大的可以分为线程独立使用的数据区,和所有线程共享的数据区。 一.线程独立使用数据区 1.程序计数器 程序计数器...

3174
来自专栏小勇DW3

设计模式--代理模式(附源码分析)

 在平时的开发过程中,我们实现方法的调用往往只是普通的对象调用方法,实现复杂的业务就是一层一层的对象调用方法依次进行实现,但是如果我要实现在某些方法执行前或者...

1243
来自专栏向治洪

java虚拟机构造原理

 Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三...

1826
来自专栏大内老A

ASP.NET Core应用的错误处理[3]:ExceptionHandlerMiddleware中间件如何呈现“定制化错误页面”

DeveloperExceptionPageMiddleware中间件利用呈现出来的错误页面实现抛出异常和当前请求的详细信息以辅助开发人员更好地进行纠错诊断工作...

2438

扫码关注云+社区