美文网首页
spring aop之aspectj的使用教程

spring aop之aspectj的使用教程

作者: 捉虫大师 | 来源:发表于2018-08-07 23:40 被阅读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");
        }
    
    }
    

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

    • 总共有五种通知 Before, Around, After, AfterReturning, AfterThrowing,至于他们的顺序见后面的图;
    • 其中我觉得最难理解的是Around,他更加灵活,能实现其他4种通知,带参数ProceedingJoinPoint,且ProceedingJoinPoint.proceed就是原方法的执行。

    最后我们来试一下:

    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来写一些简单的东西了,可以动手把你项目中一些代码重构一下啦。

    相关文章

      网友评论

          本文标题:spring aop之aspectj的使用教程

          本文链接:https://www.haomeiwen.com/subject/rsjzvftx.html