Spring核心系列之AOP(一)

栏目: Java · 发布时间: 6年前

内容简介:Spring核心系列之AOP(一)

Hello,大家好,今天来给大家讲一讲Spring中的AOP,面向切面编程,它在Spring的整个体系中占有着重要地位。本文还是以实践为主,注解切入注入,OK,文章结构:

  1. @AspectJ 详解
  2. Spring AOP - AspectJ注解

1. @AspectJ 的由来

提到AspectJ,其实很多人是有误解的,很多人只知道在Spring中使用Aspect那一套注解,以为是Spring开发的这一套注解,这里我觉得有责任和大家澄清一下。 AspectJ是一个AOP框架,它能够对 java 代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易. 其实AspectJ单独就是一门语言,它需要专门的编译器(ajc编译器). Spring AOP 与ApectJ的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点, 转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别 。在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意, Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器 。 所以,大家要明白,Spring AOP虽然是使用了那一套注解,其实实现AOP的底层是使用了动态代理(JDK或者CGLib)来动态植入。至于AspectJ的静态植入,不是本文重点,所以只提一提。

2. Spring AOP - AspectJ注解

谈到Spring AOP,老 程序员 应该比较清楚,之前的Spring AOP没有使用@Aspect这一套注解和 aop:config 这一套XML解决方案,而是开发者自己定义一些类实现一些接口,而且配置贼恶心。本文就不再演示了,后来Spring痛下决心,把AspectJ"整合"进了Spring当中,并开启了aop命名空间。现在的Sping AOP可以算是朗朗乾坤。上例子之前,还是把AOP的概念都提一提:

  • 切点:定位到具体方法的一个表达式
  • 切面: 切点+建言
  • 建言(增强):定位到方法后干什么事

网上有很多概念,什么连接点,织入,目标对象,引入什么的。我个人觉得,在Java的Spring AOP领域,完全不用管。别把自己绕晕了。就按照我说的这三个概念就完事了,好了,先来搞个例子: maven依赖:

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>
    //下面这两个aspectj的依赖是为了引入AspectJ的注解
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.6.12</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.6.12</version>
    </dependency>
//Spring AOP底层会使用CGLib来做动态代理
    <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
  </dependency>

小狗类,会说话:

public class Dog {

    private String name;


    public void say(){
        System.out.println(name + "在汪汪叫!...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

切面类:

@Aspect //声明自己是一个切面类
public class MyAspect {
    /**
     * 前置通知
     */
     //@Before是增强中的方位
     // @Before括号中的就是切入点了
     //before()就是传说的增强(建言):说白了,就是要干啥事.
    @Before("execution(* com.zdy..*(..))")
    public void before(){
        System.out.println("前置通知....");
    }
}

这个类是重点,先用@Aspect声明自己是切面类,然后before()为增强, @Before(方位)+切入点 可以具体定位到具体某个类的某个方法的方位. Spring配置文件:

//开启AspectJ功能.
    <aop:aspectj-autoproxy />

    <bean id="dog" class="com.zdy.Dog" />
    <!-- 定义aspect类 -->
    <bean name="myAspect" class="com.zdy.MyAspect"/>

然后Main方法:

ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");
        Dog dog =(Dog) ac.getBean("dog");
        System.out.println(dog.getClass());
        dog.say();

输出结果:

class com.zdy.Dog$$EnhancerBySpringCGLIB$$80a9ee5f
前置通知....
null在汪汪叫!...

说白了,就是把切面类丢到容器,开启一个AdpectJ的功能, Spring AOP就会根据切面类中的(@Before+切入点)定位好具体的类的某个方法(我这里定义的是com.zdy包下的所有类的所有方法),然后把增强before()切入进去.

然后说下Spring AOP支持的几种类似于@Before的AspectJ注解:

  • 前置通知@Before : 前置通知通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,该参数是可选的。
@Before("execution(...)")
public void before(JoinPoint joinPoint){
    System.out.println("...");
}
  • 后置通知@AfterReturning : 通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。
@AfterReturning(value="execution(...)",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是后置通知...returnVal+"+returnVal);
}
  • 异常通知 @AfterThrowing :该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可.
@AfterThrowing(value="execution(....)",throwing = "e")
public void afterThrowable(Throwable e){
  System.out.println("出现异常:msg="+e.getMessage());
}
  • 最终通知 @After :该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行.
@After("execution(...)")
public void after(JoinPoint joinPoint) {
    System.out.println("最终通知....");
}
  • 环绕通知 @Around : 环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等
@Around("execution(...)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是环绕通知前....");
    //执行目标函数
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是环绕通知后....");
    return obj;
}

然后说下一直用"..."忽略掉的切入点表达式,这个表达式可以不是exection(..),还有其他的一些,我就不说了,说最常用的execution:

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
<fully-qualified-class-name>.*(parameters)

注意这一块,如果没有精确到class-name,而是到包名就停止了,要用两个".."来表示包下的任意类:

  • execution(* com.zdy..*(..)):com.zdy包下所有类的所有方法.
  • execution(* com.zdy.Dog.*(..)): Dog类下的所有方法.

具体详细语法,大家如果有需求自行google了,我最常用的就是这俩了。要么按照包来定位,要么按照具体类来定位.

在使用切入点时,还可以抽出来一个@Pointcut来供使用:

/**
 * 使用Pointcut定义切点
 */
@Pointcut("execution(...)")
private void myPointcut(){}

/**
 * 应用切入点函数
 */
@After(value="myPointcut()")
public void afterDemo(){
    System.out.println("最终通知....");
}

可以避免重复的execution在不同的注解里写很多遍...


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Probability and Computing

Probability and Computing

Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00

Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具