面向切面编程(AOP)

通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑部分之间的耦合度降低,提高程序的可用性,同时提高了开发的效率。

1. 所需依赖

一定要导入,大坑!

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.8.M1</version>
</dependency>
image-20210913120436022
Image
image-20210913161740758
Image

2. AOP在Spring中的作用

提供声明式的事务,允许用户自定义切面。

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等....
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是切面(类)中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的“地点“的定义
  • 连接点(JointPoint):与切入点匹配的执行点

3. Spring支持的5种增强类型

通知类型 连接点 实现接口
前置通知 方法前 org.springframework.aop.BeforeAdvice,MethodBeforeAdvice
后置通知 方法后 org.springframework.aop.AfterReturningAdvice
环绕通知 方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvice
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor

4. 实现方式

4.1. 第一种:使用Spring原生API接口

  1. 创建目标

    package org.gs.service;
    
    /**
     * @author admin
     * @date 2021/9/13 3:36 下午
     */
    public interface UserService {
        public void add();
        public void delete();
        public void query();
        public void select();
    }
    
    package org.gs.service;
    
    /**
     * @author admin
     * @date 2021/9/13 3:37 下午
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加了一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除了一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询了一个用户");
        }
    
        @Override
        public void select() {
            System.out.println("选择了一个用户");
        }
    }
    
  2. 编写切面

    package org.gs.log;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    /**
     * @author admin
     * @date 2021/9/13 3:38 下午
     */
    public class BeforeLog implements MethodBeforeAdvice {
    
        // method:要执行的目标对象方法
        // objects:参数
        // o:目标对象
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println(o.getClass().getName() + "的" + method.getName() + "方法被执行了");
        }
    }
    
    package org.gs.log;
    
    import org.springframework.aop.AfterReturningAdvice;
    
    import java.lang.reflect.Method;
    
    /**
     * @author admin
     * @date 2021/9/13 3:42 下午
     */
    public class AfterLog implements AfterReturningAdvice {
        // method:要执行的目标对象方法
        // objects:参数
        // target:目标对象
        // returnValue:返回值
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了,返回的结果为" + returnValue);
    
        }
    }
    
  3. 配置文件

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <bean class="org.gs.service.UserServiceImpl" id="userService"/>
        <bean class="org.gs.log.AfterLog" id="afterLog"/>
        <bean class="org.gs.log.BeforeLog" id="beforeLog"/>
    
        <!--方式一:使用原生API接口-->
        <!--配置aop:需要导入aop的约束-->
        <aop:config>
            <!--执行环绕增强-->
            <aop:pointcut id="pointcut" expression="execution(* org.gs.service.UserServiceImpl.*(..))"/>
            <!--pointcut切入点, expression:表达式-->
            <!--execution 要执行的位置! -->
            <!--(*(修饰词) *(返回值) *(类名) *(方法名) *(参数))-->
    
            <!--这俩货必须在切入点下面,大坑-->
            <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        </aop:config>
    </beans>
    
  4. 执行结果

    方法执行前
    增加了一个用户
    方法执行后
    

4.2. 第二种:自定义切面

  1. 创建目标

    同上

  2. 编写切面

    package org.gs.log;
    
    /**
     * @author admin
     * @date 2021/9/13 5:08 下午
     */
    public class LogAdvice {
        public void before() {
            System.out.println("方法执行前");
        }
    
        public void after() {
            System.out.println("方法执行后");
        }
    }
    
  3. 配置文件

    <bean class="org.gs.service.UserServiceImpl" id="userService" />
    <bean class="org.gs.log.LogAdvice" id="logAdvice"/>
    
    <aop:config>
      <aop:aspect ref="logAdvice">
        <aop:pointcut id="pointcut" expression="execution(* org.gs.service.UserServiceImpl.*(..))"/>
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
      </aop:aspect>
    </aop:config>
    
  4. 执行结果

    方法执行前
    增加了一个用户
    方法执行后
    

4.3. 使用注解实现

  1. 创建目标

    同上

  2. 编写切面

    package org.gs.log;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    /**
     * @author admin
     * @date 2021/9/13 5:08 下午
     */
    @Aspect
    public class LogAdvice {
        @Before("execution(* org.gs.service.UserServiceImpl.*(..))")
        public void before() {
            System.out.println("方法执行前");
        }
    
        @After("execution(* org.gs.service.UserServiceImpl.*(..))")
        public void after() {
            System.out.println("方法执行后");
        }
    
        /*
        JoinPoint是程序运行过程中可识别的点,这个点可以用来作为AOP切入点。JoinPoint对象则包含了和切入相关的很多信息。
        比如切入点的对象,方法,属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。
        Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。
        proceed很重要,这个是aop代理链执行的方法。
         */
        // 环绕通知
        @Around("execution(* org.gs.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("around before");
            proceedingJoinPoint.proceed();
            System.out.println("around after");
        }
    }
    
  3. 配置文件

    <bean class="org.gs.service.UserServiceImpl" id="userService" />
    <bean class="org.gs.log.LogAdvice" id="logAdvice"/>
    
    <!--开启aop注解扫描-->
    <!-- proxy-target-class="false" JDK执行aop(默认)-->
    <!-- proxy-target-class="true"  cglib执行aop-->
    <aop:aspectj-autoproxy/>
    
  4. 执行结果

    around before
    方法执行前
    增加了一个用户
    方法执行后
    around after
    

5. execution表达式

语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
修饰符 一般省略
public 公共方法
* 任意
返回值 不能省略
void 没有返回值
String 返回值字符串
* 任意
[省略]
org.gs.crm 固定包
org.gs.crm.*.service crm包下任意的包含service包的子包
org.gs.crm.. crm下所有子包(包括自己)
org.gs.crm.*.service.. crm包下任意子包,固定目录service,service下任意包
[省略]
UserServiceImpl 指定类
*Impl 以Impl结尾
User 以User开头
* 任意
方法名 不能省略
addUser 公共方法
add* 以add开头
*Do 以Do结尾
* 任意
参数 不可省略
() 无参
(int) 一个int
(int, int) 两个int
(..) 参数任意
throws 可省略,一般不写

案例:

execution(* org.gs.service.UserServiceImpl.*(..))
Copyright © rootwhois.cn 2021-2022 all right reserved,powered by GitbookFile Modify: 2022-11-26 20:03:31

results matching ""

    No results matching ""