AOP

头像
    阅读 7 分钟

    AOP是面向切面编程,是一种设计思想,它要在不改变原有目标对象的基础上,为目标对象基于动态织入的特定方式进行功能扩展。这里的特定方式一种是编译时动态,还有一种是运行时动态。我们可以将设计思想理解为OOP(面向对象编程)思想的补充和完善,OOP强调的一种静态过程,而AOP是一种动态过程,它要为设计好的对象在动态编译或运行时做服务增益,例如记录日志、事务增强、权限控制等。AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们称之为动态代理对象。


    创建代理对象的方式有两种:

    1.借助JDK官方API为目标对象创建其兄弟类型,但是目标对象类型需要实现相应的接口

    2.借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰


    AOP相关术语

    切面对象(Aspect):封装了扩展业务逻辑的对象,在spring中可以使用@Aspect描述。

    切入点(Poincut):定义了切入扩展业务逻辑的一些方法的集合,就是哪些方法要运行的时候切入扩展业务,会通过表达式进行相关定义。一个切面中可以定义多个切入点的定义

    连接点(JoinPoint):切入点方法集合中封装了某个正在执行的目标方法信息对象。其实就是连接点是切入点方法当中的用来封装这个方法信息的对象。可以通过此对象获取具体的目标方法信息,甚至区调用目标方法(执行扩展业务逻辑)

    通知(Advice):切面(Aspect)内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知


    AOP依赖

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

    package com.cy.pj.sys.service.aspect;
    
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * @Aspect注解描述的类型为切面对象类型,此切面中可以定义多个切入点和通知方法
     */
    @Aspect
    @Component
    @Slf4j
    public class SysLogAspect {
        /**
         * @Pointcut注解用于定义切入点 bean(" spring容器中bean的名字 ")这个bean表达式为切入点表达式定义的一种语法
         * 它描述的是某个bean或多个bean中所有方法的集合为切入点;缺点是不能精确到某一个具体方法
         */
        @Pointcut("bean(sysUserServiceImpl)")//这个bean中所有方法的集合都是切入点
        //@Pointcut("bean(*ServiceImpl)")表示名字以ServiceImpl结尾的所有bean
        public void doLog() {//此方法用来承载切入点
        }
    
        /**
         * @param joinPoint 连接点对象,此对象封装了要执行的切入点方法信息,
         *                  可以通过连接点对象调用目标方法
         * @return 目标方法的执行结果
         * @throws Throwable
         * @Around()注解描述的方法,会在切入点执行之前和之后执行业务拓展, 在当前业务中,此方法为日志环绕通知方法
         */
        @Around("doLog()")//在这个切入点方法执行的时候,执行下面这个通知方法
        public Object doAround(ProceedingJoinPoint joinPoint/*这个连接点只能用在环绕通知中*/) throws Throwable {
           log.info("start:{}" + System.currentTimeMillis());
            try {
                Object result = joinPoint.proceed();//执行目标方法(切入点中的某个方法,就是正在执行的方法)
               log.info("end:{}" + System.currentTimeMillis());
                return result;
            } catch (Throwable e) {
                e.printStackTrace();
               log.error("Exception:{}" + System.currentTimeMillis());
                throw e;
            }
        }
    }
    

    Spring切面工作原理

    当我们切面内部切入点对应的目标业务方法执行时,底层会通过代理对象用反射来访问切面中的通知方法,进而通过通知方法为目标业务做功能增强。我们可以通过断点看到,如图:

    如果两个方法用了同一个切入点,执行顺序是随机的,要看spring容器优先扫描到哪个。


    JDK动态代理

    要在yml配置文件中加上

    spring:
       aop:
        proxy-target-class: false


    Spring中AOP通知类型

    @Around(所有通知中优先级最高的通知,可以在执行方法之前和之后灵活进行业务拓展)
    @Before(在目标方法之前调用)
    @AfterReturning(目标方法正常结束时执行)
    @AfterThrowing(目标方法异常结束时执行)
    @After(目标方法正常结束和目标方法异常结束时都执行)

    package com.cy.pj.sys.service.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    //ProceedingJoinPoint joinPoint这个连接点只能用在环绕通知中
    //如果其它方法想用连接点,可以用JoinPoint jp
    @Aspect
    @Component
    public class SysTimeAspect {
        @Pointcut("bean(sysRoleServiceImpl)")
        public void doTime() {
        }
    
        @Before("doTime()")
        public void doBefore() {
            System.out.println("@Before");
        }
    
        @After("doTime()")
        public void doAfter() {
            System.out.println("@After");
        }
    
        @AfterReturning("doTime()")
        public void doAfterReturning() {
            System.out.println("@AfterReturning");
        }
    
        @AfterThrowing("doTime()")
        public void doAfterThrowing() {
            System.out.println("@AfterThrowing");
        }
    //最重要,优先级最高
        @Around("doTime()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            try {
                System.out.println("@Around.before");
                Object proceed = joinPoint.proceed();
                System.out.println("@Around.AfterReturning");
                return proceed;
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("Around.AfterThrowing");
                throw e;
            } finally {
                System.out.println("@Around.After");
            }
        }
    }
    


    Spring中AOP切入点表达式

    在Spring工程中对于切入点表达式的定义,可以分成两大类型:

    粗粒度切入点表达式定义(不能精确到具体方法)
    细粒度切入点表达式定义(可以精确到具体方法)

    粗粒度切入点表达式定义:
    bean("bean的名字")表达式:用于粗粒度切入点的定义,不能精确到具体方法。

    bean(sysUserServiceImpl)//这个bean中所有方法的集合都是切入点
    bean(*ServiceImpl)//表示名字以ServiceImpl结尾的所有bean

    within("包名..类型")表达式:用于粗粒度切入点的定义,不能精确到具体方法。

    within(" com.cy.pj.sys.impl.SysUserServiceImpl")//表示这个包下这个类里的所有方法
    within(" com.cy.pj.sys.impl.*")//表示这个包下所有类里的方法
    within(" com.cy.pj.sys.impl..*")//表示这个包以及子包下所有类里的方法

    细粒度切入点表达式定义
    execution("返回值 类全名.方法名(参数列表)")表达式:用于细粒度切入点的定义,可以精确到具体方法。

    execution("int com.cy.pj.sys.impl.SysUserServiceImpl.validById(Integer, Integer)//表示方法的返回值是int类型 这个包下的这个类中的这个方法,方法参数有两个Integer类型。
    
    execution("* com.cy.pj.sys.impl.SysUserServiceImpl.*(..)//表示任意返回值,这个包这个类中的任意方法,参数列表也是任意。
    
    execution("* com.cy.pj.sys.impl..*.*(..)//表示任意返回值,这个包以及子包下任意类中的任意方法,参数列表也是任意。
    
    execution("* com.cy.pj.sys.impl.*.*(..)//表示任意返回值,这个包下任意类中的任意方法,参数列表也是任意。

    @annotation("注解的类全名"):用于细粒度切入点的定义,可以精确到具体方法。

    @annotation("com.cy.pj.annotation.RequiredCache")//表示由RequiredCache这个注解描述的方法就是缓存切入点方法
    @annotation("com.cy.pj.annotation.RequiredLog")//表示由RequiredLog这个注解描述的方法就是日志切入点方法

    Spring中切面优先级设置

    当项目中有多个切面时,且对应着相同的切入点,假如切面中的通知方法的执行需要有严格顺序要求,此时我们需要设置切面的优先级,这个优先级可以通过@Order(数字)注解进行描述,数字越小优先级越高。假如没有指定优先级,默认是最低的优先级。相同切入点下的多个切面会形成一个切面链。


    Spring中异步操作

    1.首先用@EnableAsync注解描述启动类,在我们再次运行启动类的时候,底层会帮我们配置一个线程池(这个底层配置的线程池,核心线程数默认是8)。

    2.用@Async注解描述方法,在spring中会认为这个是异步切入点方法,在这个切入点方法进行时,底层会通过通知方法获取线程池中的线程,来调用切入点方法。但是这个注解不能直接去描述有方法返回值的方法,因为我们并不知道异步操作底层到底是何时结束

    Spring中yml文件中线程池的配置

      task:
        execution:
          pool:
            core-size: 3               
            max-size: 5
            keep-alive: 60000
            queue-capacity: 10
          thread-name-prefix: db-service-task-

    core-size:核心线程数 一般设置的值是cpu核数+1 有IO操作的设置2xcpu核数x8+1 1是指磁盘的个数
    max-size:最大线程数(包含核心线程)
    keep-alive:空闲等待时间
    queue-capacity:队列容量,也就是等待排队的线程数
    thread-name-prefix:设置线程名的前缀,用于好分辨线程是哪来的

    core-size: 3 当核心线程数没有达到3时,每来一个新的任务就会创建一个新的线程,然后存储到线程池中。假如池中线程数已经达到核心线程数时,再接收新的任务时,要检查是否有空闲的核心线程,假如有,则使用空闲的核心线程执行新的任务。

    queue-capacity: 假如核心线程数已经达到核心线程数的值,并且所有核心线程都在忙,再来新任务时,会将任务存储到任务队列中。

    max-size: 当队列已满,核心线程数也都在忙,再来新的任务则会创建新的线程,但所有的线程数不能超过最大线程数设置的值,否则会抛出异常,拒绝执行。

    keep-alive: 假如池中的线程数多余核心线程数的值,此时又没有新的任务,则一旦空闲线程空闲的时间超过keep-alive设置的时间值,则会被释放。

    这些参数值都是传给ThreadPoolExecutor这个对象。


    Spring中事务的处理

    Spring中事务控制,推荐在业务层基于AOP方式进行实现,这样可以将事务逻辑与业务逻辑进行更好的解耦,同时重用事务逻辑代码。

    @Transactional注解描述的方法为事务切入点方法,此方法在执行时就会通过通知方法为其进行事务的增强


    @Transactional注解内部属性:

    1.readyOnly 用来描述此事务是否为只读事务,若为只读,readOnly为true,默认值是false(表示不是只读事务),对于查询而言建议设置readOnly为true。

    2.rollbackFor 用来指定,出现什么异常的时候回滚(默认RuntimeException)

    3.noRollbackFor 用来指定出现什么异常时,不回滚

    4.isolation用来设置事务并发执行隔离级别(隔离级别越高,数据正确性约好,但并发越差)
    4.1 Isolation.READ_COMMITTED只读取别人提交的数据
    4.2 Isolation.REPEATABLE_READ读数据的时候不给别人修改,重复读
    4.3 Isolation.SERIALIZABLE在统计的时候不允许写入或删除数据

    5.timeout超时时间 如果事务执行时间超过了这个设置的值,则抛出异常。默认-1(不超时,实际项目中建议设置超时时间)

    6.propagation设置事务传播特性(默认值为Propagation.REQUIRED),不同业务对象之间的方法出现相互调用时,事务的执行策略。REQUIRED表示参与调用者的事务中去。
    6.1Propagation.REQUIRED表示不管谁来调用,都参与到调用的事务中去
    6.2Propagation.REQUIRES_NEW表示不管谁来调用,都不管,只在自己的事务中

    如果类上和方法上都定义了事务的特性,那么方法的优先级最高


    16 声望8 粉丝