• 温故知新——Spring AOP


    Spring AOP 面向切面编程,相信大家都不陌生,它和Spring IOC是Spring赖以成名的两个最基础的功能。在咱们平时的工作中,使用IOC的场景比较多,像咱们平时使用的@Controller、@Service、@Repository、@Component、@Autowired等,这些都和IOC相关。但是,使用AOP的场景却非常少,也就是在事务控制这里使用到了AOP,随着SpringBoot的流行,事务控制这块也不用自己配置了,SpringBoot内部已经给咱们配置好了,我们只需要使用@Transactional这个注解就可以了。

    Spring AOP作为Spring的基础功能,大家在学习的时候肯定都学过,但是由于平时使用的比较少,渐渐的就遗忘了,今天我们就再来看看Spring AOP,全面的给大家讲一下,我本人也忘记的差不多了,在面试的时候,人家问我Spring AOP怎么使用,我回答:呵呵,忘得差不多了。对方也是微微一笑,回了一个呵呵。好了,咱们具体看看Spring AOP吧。

    Spring AOP解决的问题

    面向切面编程,通俗的讲就是将你执行的方法横切,在方法前、后或者抛出异常时,执行你额外的代码。比如:你想要在执行所有的方法前,要验证当前的用户有没有权限执行这个方法。如果没有AOP,你的做法是写个验证用户权限的方法,然后在所有的方法中,都去调用这个公共方法,如果有权限再去执行后面的方法。这样做是可以的,但是显得比较啰嗦,而且硬编码比较多,如果哪个小朋友忘了这个权限验证,那就麻烦了。

    现在我们有了AOP,只需要几个简单的配置,就可以在所有的方法之前,去执行我们的验证权限的公共方法。

    Sping AOP的核心概念

    在AOP当中,核心的术语非常多,有8个,而且理解起来也是晦涩难懂,在这里给大家罗列一下,大家如果感兴趣可以去查阅一下其他的资料。

    AOP的8个术语:切面(Aspect)、连接点(Join point)、通知(Advice)、切点(Pointcut)、引入(Introduction)、目标对象(Target object)、AOP代理(AOP proxy)、编织(Weaving)。

    在这里,我个人觉得Spring AOP总结成以下几个概念就可以了。

    • 切面(Aspect):在Spring AOP的实际使用中,只是标识的这一段是AOP的配置,没有其他的意义。
    • 切点(Pointcut):就是我们的方法中,哪些方法需要被代理,它需要一个表达式,凡是匹配成功的方法都会执行你定义的通知。
    • 通知(Advice):就是你要另外执行的方法,在前面的例子中,就是权限校验的方法。

    好了,知道这几个概念,我个人觉得在平时工作中已经足够了。

    Sping AOP的具体配置——注解

    我们的实例采用SpringBoot项目搭建,首先我们要把Spring AOP的依赖添加到项目中,具体的maven配置如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    具体的版本我们跟随SpringBoot的版本即可。既然它是个starter,我比较好奇它在配置文件中有哪些配置,我们到application.properties中去看一下,

    spring.aop.auto=true
    spring.aop.proxy-target-class=true
    
    • spring.aop.auto:它的默认值是true,它的含义是:Add @EnableAspectJAutoProxy,也就是说,我们在配置类上不再需要标注@EnableAspectJAutoProxy了。这个算是Spring AOP中的一个知识点了,我们重点说一下。

    我们在Spring中使用AOP,需要两个条件,第一个,要在我们的项目中引入aspectjweaver.jar,这个,我们在引入spring-boot-starter-aop的时候已经自动引入了。第二个,就是我们我们的配置类中,标注@EnableAspectJAutoProxy 这个注解,如果你使用的是xml的方式,需要你在xml文件中标明<aop:aspectj-autoproxy/>。这样才能在我们的项目使用Spring-AOP。

    • spring.aop.proxy-target-class:AOP代理的实现,这个值默认也是true。它的含义是是否使用CGLIB代理。这也是AOP中的一个知识点。

    Spring AOP的代理有两种,一种是标准的JDK动态代理,它只能代理接口,也即是我们在使用的时候,必须写一个接口和实现类,在实现类中写自己的业务逻辑,然后通过接口实现AOP。另外一种是使用CGLIB 代理,它可以实现对类的代理,这样我们就不用去写接口了。

    Spring AOP默认使用的是JDK动态代理,只能代理接口,而我们在开发的时候为了方便,希望可以直接代理类,这就需要引入CGLIB ,spring.aop.proxy-target-class默认是true,使用CGLIB,我们可以放心大胆的直接代理类了。

    通过前面的步骤,我们已经可以在项目中使用Spring AOP代理了,下面我们先创建个Service,再写个方法,如下:

    @Service
    public class MyService {
    
        public void gotorun() {
            System.out.println("我要去跑步!");
        }
    
    }
    

    我们写了个“我要去跑步”的方法,然后再通过AOP,在方法执行之前,打印出“我穿上了跑鞋”。下面重点看一下这个AOP代理怎么写,

    @Component
    @Aspect
    public class MyAspect {
    
        @Pointcut("execution(public * com.example.springaopdemo.service.*.*(..))")
        private void shoes() {}
    
        @Before("com.example.springaopdemo.aspect.MyAspect.shoes()")
        public void putonshoes() {
            System.out.println("我穿上跑步鞋。");
        }
    }
    

    首先,要创建一个切面,我们在类上使用@Aspect注解,标识着这个类是一个切面,而@Component注解是将这个类实例化,这样这个切面才会起作用。如果只有@Aspect而没有被Spring实例是不起作用的。当然Spring实例化的方法有很多,不一定就非要使用@Component。

    再来看看切点的声明,切点的作用是匹配哪些方法要使用这个代理,我们使用@Pointcut注解。@Pointcut注解中有个表达式就是匹配我们的方法用到,它的种类也有很多,这里我们给大家列出几个比较常用的execution表达式吧,

    execution(public * *(..))      //匹配所有的public方法
    execution(* set*(..))          //匹配所有以set开头的方法
    execution(* com.xyz.service.AccountService.*(..))     //匹配所有AccountService中的方法
    execution(* com.xyz.service.*.*(..))    //匹配所有service包中的方法
    execution(* com.xyz.service..*.*(..))   //匹配所有service包及其子包中的方法
    

    示例中,我们匹配的是service包中的所有方法。

    有没有同学比较好奇@Pointcut下面的那个方法?这个方法到底有没有用?方法中如果有其他的操作会不会执行?答案是:方法里的内容不会被执行。那么它有什么用呢?它仅仅是给@Pointcut一个落脚的地方,仅此而已。但是,Spring对一个方法也是有要求的,这个方法的返回值类型必须是void。原文是这么写的:

    the method serving as the pointcut signature must have a void return type

    最后我们再来看看通知,通知的种类有5种,分别为:

    • @Before:前置通知,在方法之前执行;
    • @AfterReturning:返回通知,方法正常返回以后,执行通知方法;
    • @AfterThrowing:异常通知,方法抛出异常后,执行通知方法;
    • @After:也是返回通知,不管方法是否正常结束,都会执行这个方法,类似于finally;
    • @Around:环绕通知,在方法执行前后,都会执行通知方法;

    在示例中,使用的是@Before前置通知,我们最关心的是@Before里的内容:

    @Before("com.example.springaopdemo.aspect.MyAspect.shoes()")
    public void putonshoes() {
        System.out.println("我穿上跑步鞋。");
    }
    

    @Before里的内容是切点的方法,也就是我们定义的shoes()方法。那么所有匹配了shoes()切点的方法,都会执行@Before这个注解的方法,也就是putonshoes()。

    在@Before里除了写切点的方法,还可以直接写切点表达式,例如:

    @Before("execution(public * com.example.springaopdemo.service.*.*(..))")
    public void putonshoes() {
        System.out.println("我穿上跑步鞋。");
    }
    

    如果我们使用这种表达式的写法,就可以省去前面的@Pointcut了,这种方法还是比较推荐的。我们再写个测试类运行一下,看看效果吧,

    @SpringBootTest
    class SpringAopDemoApplicationTests {
        @Autowired
        private MyService myService;
    
        @Test
        public void testAdvice() {
            myService.gotorun();
        }
    }
    

    运行结果如下:

    我穿上跑步鞋。
    我要去跑步!
    

    没有问题,在执行“我要去跑步”之前,成功的执行了“我穿上跑步鞋”的方法。

    好了,今天先到这里,下一篇我们看看如何使用xml的方式配置Spring AOP。

  • 相关阅读:
    Informatica 常用组件Lookup缓存之五 使用动态查找高速缓存
    Informatica 常用组件Lookup缓存之四 使用不高速缓存的查找或静态高速缓存
    Informatica 常用组件Lookup缓存之三 重建查找高速缓存
    Golang入门教程(十一)beego 框架之RESTful Controller 路由
    PHP7 学习笔记(十二)PHPExcel vs PhpSpreadsheet and PHP_XLSXWriter
    PHP7 学习笔记(十二)gRPC
    PHP7 学习笔记(十一)使用phpstudy快速配置一个虚拟主机
    Golang入门教程(十)内建函数
    Golang入门教程(九)复合数据类型使用案例二
    Golang入门教程(八)复合数据类型使用案例一
  • 原文地址:https://www.cnblogs.com/boboooo/p/13572116.html
Copyright © 2020-2023  润新知