Spring AOP 之一:基本概念与流程

原创
2017/06/26 20:28
阅读数 1.4W

基本概念

  1. Joinpoint:拦截点,连接点。如:业务逻辑模块中的方法或异常

  2. Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint

  3. Advice: 通知,要切入的逻辑

  4. Before Advice: 在方法(连接点)前切入

  5. After Advice: 在方法(连接点)后切入,抛出异常时也会切入

  6. After Returning Advice: 在方法(连接点)返回后切入,抛出异常则不会切入

  7. After Throwing Advice: 在方法(连接点)抛出异常时切入

  8. Around Advice: 在方法(连接点)执行前后切入,可以中断或忽略原有流程的执行

  9. Aspect:切面,整个切入逻辑的抽象

上面的概念如果是第一次接触,那么就显得非常抽象。没有关系,先抛开上面的概念,先来了解一下Spring AOP(或者更加广义的说面向切面编程究竟是什么)。

可以这样理解:AOP是为了非侵入式的处理一类逻辑。举一个老生常谈的例子:记录方法执行时间,很多方法可能都需要这个功能,很显然我们不愿意去为每一个方法都写记录时间的代码,这些代码会重复,会和业务逻辑耦合,这是代码的"bad smell"。

记录时间方法这就是一类逻辑,我们可以把这一类逻辑处理为一个Aspect(切面),当然这部分逻辑只是切面中的Advice(通知)部分,我们还需要指定那些方法(Joinpont)需要执行这个记录时间逻辑(Advice)。(Advice 其实包含3部分,什么地点,什么时间,做什么工作)

所以AOP简单总结:在什么地点(Joinponit,多个地点Pointcut)什么时间做什么(Advice),Advice封装了什么时候做、什么地方和做什么。

实例

上面的概念部分还是过于抽象,下面我们通过一个实例,把概念和实例一一对应来讲,我尽量简化一些概念的东西,先着重于流程部分,这样有利于从整体上把握AOP,而不是限于概念之中。

下图是一个工程的整体的目录结构图,可以对整体结构有一个了解。

工程目录

下面的例子都使用的是AspectJ的注解的方式处理的,为的是尽量让代码简化清晰,让我们专注在AOP上,而不是各种配置上。

业务方法

public interface HaveResultBusiness {
    
    Integer getResult(Integer div);

}
import org.springframework.stereotype.Service;

import cn.freemethod.business.HaveResultBusiness;

@Service
public class HaveResultBusinessImpl implements HaveResultBusiness {

    @Override
    public Integer getResult(Integer div) {
        System.out.println("HaveResultBusinessImpl getResult...");
        Integer result = 100 / div;
        return result;
    }

}

业务逻辑部分是非常简单的就是获取100除以div的结果,每一个方法都可以作为一个Joinpoint,但是Spring AOP只是抽象了Pointcut,所以我们下面看一下Pointcut。

Pointcut

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class HaveResultBusinessPointcut {
    
    @Pointcut("this(cn.freemethod.business.HaveResultBusiness)")
//    @Pointcut("execution(* cn.freemethod.business.HaveResultBusiness.*(..))")
    public void haveResultBusinessPointcut(){};

}

上面是单独定义Pointcut,为了方便多出引用,当然也可以直接写在Advice中。我们把@Pointcut("this(cn.freemethod.business.HaveResultBusiness)")中的this(cn.freemethod.business.HaveResultBusiness)部分称作为Pointcut表达式。定义的就是什么地方,Pointcut的表达式有很多类型,例如上面的this表达式,还有expression表达式等,本文不详细介绍,下一篇文章会仔细介绍。 @Pointcut("this(cn.freemethod.business.HaveResultBusiness)")表达式表示的是:实现了cn.freemethod.business.HaveResultBusiness接口的所有方法(连接点,Joinpoint)。

Aspect 切面

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TimingAspect {

    @Before("cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()")
    // @Before("execution(* cn.freemethod.business.HaveResultBusiness.*(..))")
    public void before() {
        System.out.println("TimingAspect before...");
    }

    @Around("cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object retVal = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println("time elapse:" + (end - start));
        return retVal;
    }

    @After("cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()")
    public void after() {
        System.out.println("TimingAspect after...");
    }

    @AfterThrowing(pointcut = "cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()", throwing = "ex")
    public void doRecoveryActions(Exception ex) {
        System.out.println("@AfterThrowing:" + ex.getMessage());
    }

    @AfterReturning(pointcut = "cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()", returning = "retVal")
    public void doAccessCheck(Object retVal) {
        System.out.println("@AfterReturning:"+retVal);
    }

}

上面就是最核心的Aspect类了,@Aspect表明这个类是一个切面,注意要和@Componet之类的组合使用。在这个Aspect中定义了5中类型的Advice(通知),每一个Advice都使用了cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut()这一个Pointcut。

@Before是在Pointcut之前执行,@After是在Pointcut之后执行。

@Around封装了连接点(方法)的处理过程,可以在方法前后注入逻辑

@AfterThrowing是在抛出异常之后执行,可以通过throwing = "ex"把异常注入进来

@AfterReturning是在返回之后执行,可以通过returning = "retVal"的方式把返回值注入进来。

配置

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"cn.freemethod"})
public class AspectConfig {

}

@EnableAspectJAutoProxy是对AspectJ的支持,@ComponentScan配置扫描cn.freemethod及其子包。

工具

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

//@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        ApplicationContextProvider.context=context;
        String[] names = context.getBeanDefinitionNames();
        for(String name : names){
            System.out.println(name);
        }
    }

    public static ApplicationContext getApplicationContext() {
        return context;
    }

}

打印容器中的bean名字,这个查错常用,为了不影响输出已经注释了。

启动

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import cn.freemethod.business.HaveResultBusiness;
import cn.freemethod.config.AspectConfig;

public class AnnotationConfigStart {
    
    public static void main(String[] args) {
        AbstractApplicationContext  context = new AnnotationConfigApplicationContext(AspectConfig.class);
        HaveResultBusiness haveResultBusiness = context.getBean(HaveResultBusiness.class);
        Integer result = haveResultBusiness.getResult(100);
        System.out.println(result);
        haveResultBusiness.getResult(0);
        context.close();
    }

}

部分输出

HaveResultBusinessImpl getResult...
time elapse:0
TimingAspect after...
@AfterReturning:1
1
TimingAspect before...
HaveResultBusinessImpl getResult...
TimingAspect after...
@AfterThrowing:/ by zero
java.lang.ArithmeticException: / by zero
    at cn.freemethod.business.impl.HaveResultBusinessImpl.getResult(HaveResultBusinessImpl.java:13)

附录

参考

关于Spring的注解配置可以参考:Spring注解配置

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.freemethod</groupId>
  <artifactId>aop</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>
        <spring_version>4.1.6.RELEASE</spring_version>
        <cglib_version>3.2.4</cglib_version>
        <javassist_version>3.12.1.GA</javassist_version>
        <junit_version>4.9</junit_version>
    </properties>
  
  <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring_version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring_version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring_version}</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit_version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring_version}</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.3</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.11</version>
        </dependency>
  </dependencies>
</project>

资源链接

完整工程代码

Spring AOP 之一:基本概念与流程

Spring AOP 之二:Pointcut注解表达式

Spring AOP 之三:通知(Advice)方法参数

展开阅读全文
打赏
1
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
1
分享
返回顶部
顶部