Spring核心系列之AOP(一)

Spring核心系列之AOP(一)

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

  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的概念都提一提:spring

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

网上有不少概念,什么链接点,织入,目标对象,引入什么的。我我的以为,在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>
复制代码

小狗类,会说话:bash

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;
    }
}
复制代码

切面类:app

@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方法:maven

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()切入进去.ui

而后说下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在不一样的注解里写不少遍...

结语

好了,Spring AOP 基于AspectJ注解如何实现AOP给你们分享完了,其实不是很难,由于博主比较懒,主说了主要内容,像切入点表达式要说其实有不少东西能够说,还有加强级别的排序问题,好比一个Aspect类中定义了多个@Before,谁先调用,由于博主以为这些知识点不是很重要,用的很是少,因此就没提了。这一期把基于注解的分享完了,下一次准备把Spring AOP基于XML的配置讲一讲,而后说一些实际的运用场景。包括多个切面命中一个切点时,切面级别的调用顺序等都会提一提。而后把Spring AOP底层使用的JDK动态代理和CGLib动态代理都稍微说一说。好了,这一期就到这里了..Over,Have a good day .

相关文章
相关标签/搜索