spring aop之aspectj的使用教程

2018-08-07  本文已影响144人  捉虫大师

上一篇文章最后讲到了动态代理,在spring中有个特性也是利用动态代理实现的,那就是面向切面编程(aop)。Spring aop有两种实现方式:一种是spring aop,另一种是aspectj。这两种实现方式的主要区别在于:spring aop采用的是动态织入(运行期期植入),而aspectJ是静态织入(编译期植入)。

本文只阐述spring aspectj在实际项目中如何使用,不关乎具体实现,后续可能会写写一些spring aop的或者他们的实现原理。

首先是引入依赖:
pom.xml,测试项目我用maven来管理依赖

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.3.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12-beta-1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

然后是配置文件,我觉得spring会写配置文件差不多就成功了一大半了。
aop.xml,这里我使用了注解配置的方式,因为这样感觉测试代码会清晰很多。

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--自动扫描bean-->
    <context:component-scan base-package="aop" />

   <!--开启aspectj-->
    <aop:aspectj-autoproxy/>

</beans>

写一个简单的Service,其实就是一个bean,只不过我喜欢写成@Service,这样更好理解吧

package aop;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String add(Integer id, String name) {
        String str = "add user id = " + id + " name = " + name;
        System.out.println(str);
        return str;
    }

    public void delete(Integer id) {
        System.out.println("delete user id = " + id);
    }

    public void update(Integer id) throws Exception {
        throw new Exception("更新出错");
    }

}

然后写一个切面,切面也是一个bean

package aop;

import org.aspectj.lang.JoinPoint;
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.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class OperateService {

    @Pointcut("execution(* aop.UserService.add(..))")
    public void pointCut(){};

    @Before(value = "execution(* aop.UserService.add(..)) && args(id,name)", argNames = "id,name")
    public void beforeAdd(Integer id, String name) {
        System.out.println("插入前..." + "args.id = " + id + ";name=" + name);
    }

    @After("execution(* aop.UserService.add(..))")
    public void afterAdd() {
        System.out.println("插入后...1");
    }

    @AfterReturning(pointcut = "pointCut()", returning = "ret")
    public void afterAdd2(JoinPoint joinPoint, Object ret) {
        System.out.println("插入后...2; return = " + ret);
    }

    @AfterThrowing(value = "execution(* aop.UserService.*(..))", throwing = "e")
    public void afterThrowException(Exception e) {
        System.out.println("报错了;e=" + e.getMessage());
    }

    @Around(value = "execution(* aop.UserService.add(..)) && args(id,name)", argNames = "id,name")
    public void aroundOperate(ProceedingJoinPoint point, Integer id, String name) {
        System.out.println("around1 id =" + id + " name=" + name);
        try {
            point.proceed();
        } catch (Throwable e) {
        }
        System.out.println("around2");
    }

}

在这个切面中我尽可能地把通知和切点的各种形式都写了出来,怎么传参数,怎么获取返回值,代码中都能找到。
大致总结一下:

最后我们来试一下:

package aop;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class aopMain {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add(1, "lk");
        try {
            userService.update(1);
        } catch (Exception e) {

        }
    }
}

输出结果:

around1 id =1 name=lk
插入前...args.id = 1;name=lk
add user id = 1 name = lk
around2
插入后...1
插入后...2; return = null
报错了;e=更新出错

细心的人会发现afterReturning取的返回值是null,这是不是有问题,确实,查找资料发现afterReturning和Around共存时是拿不到返回值的,但是这个说法有点片面,因为around其实是代理了我们的方法,around没有return,拿到的自然是null,如果给around一个返回值,那么afterReturning也是能拿到值的。
我们将around改写一下:

@Around(value = "execution(* aop.UserService.add(..)) && args(id,name)", argNames = "id,name")
    public String aroundOperate(ProceedingJoinPoint point, Integer id, String name) {
        System.out.println("around1 id =" + id + " name=" + name);
        String str = "";
        try {
            str = (String) point.proceed();
        } catch (Throwable e) {
            System.out.println("报错了");
        }
        System.out.println("around2");
        return str;
    }

这时的输出

插入前...args.id = 1;name=lk
add user id = 1 name = lk
插入后...1
插入后...2; return = add user id = 1 name = lk
报错了;e=更新出错

从这些输出结果中,我们能发现他们的执行顺序是这样的:


执行顺序.png

好了,到这里差不多会使用spring aspectj来写一些简单的东西了,可以动手把你项目中一些代码重构一下啦。

上一篇下一篇

猜你喜欢

热点阅读