Spring AOP简介

BolunWu

1.1 AOP概述

1.1.1 AOP是什么?

AOP是一种设计思想,是软件设计领域的面向切面编程,他是面向对象编程OOP的一种补充和完善.它以通过预编译的方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术.

1.1.2 AOP应用场景分析?

实际项目中通常会将系统分为两部分,一部分是核心业务,一部分是非核心业务.在编程实现时我们首先要完成的是核心业务的实现,非核心业务我们一般通过特定的方式切入到系统中,这种方式一般借助AOP实现.
AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以控制对象执行.例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等.

1.1.3 AOP应用原理分析

1)假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口).
2)假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型).
AOP在默认的情况下使用CGLIB代理,加入使用JDK动态代理可以在配置文件(application.properties)中配置如下:
spring.aop.proxy-target-class=false

1.2AOP相关术语分析

切面(asprct):横切面对象,一般为一个具体类对象(可以借助@Acpect声明).
通知(Advice):在横切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等.
连接点(joinpoint):程序执行中的某个特定点一般指被拦截到的方法.
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合.

2.Spring AOP快速实践

2.1业务描述

基于项目中的核心业务,简单添加日志操作,借助SLF4J日志API输出目标方法的执行时长.(前提,不能修改目标方法的代码-遵循OCP原则)

2.2项目创建及配置

创建maven项目或在已有的项目基础上添加AOP启动依赖:
<dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

说明:基于此依赖spring可以整合AspectJ框架快锁完成AOP的基本操作,AspectJ是一个面向切面的框架,它定义了AOP的一些语法,有一个专门的自己吗生成器来生成遵守java规范的class文件.

2.3 扩展业务分析及实现

2.3.1创建日志切面类对象

将此日志切面类作为核心业务增强(一个横切面对象)类,用于输出业务执行时长.
package com.cy.pj.common.aspect;

@Aspect

@Slf4j

@Component

public class SysLogAspect {

         @Pointcut("bean(sysUserServiceImpl)")

         public void logPointCut() {}


         @Around("logPointCut()")

         public Object around(ProceedingJoinPoint jp)

         throws Throwable{

                 try {

                   log.info("start:{}"+System.currentTimeMillis());

                   Object result=jp.proceed();//最终会调用目标方法

                   log.info("after:{}"+System.currentTimeMillis());

                   return result;

                 }catch(Throwable e) {

                   log.error("after:{}",e.getMessage());

                   throw e;

                 }

         }
说明:
@Aspect注解用于标识或者描述AOP中切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行.
@Pointcut注解用于描述切面中的方法,并定义切面中的切入点,(基于特定表达式的方式进行描述).
@Around注解用以描述切面中的方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和执行之后要执行的一个动作),@Around注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为@Pointcut注解描述的方法的方法名).
ProceedingJoinPoint类作为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息.只能用于@Around注解描述的方法参数.

2.3.1业务切面测试实现

启动项目测试或者进行单元测试:
@SpringBootTest

public class AopTests {

         @Autowired

         private SysUserService userService;

         @Test

         public void testSysUserService() {

                 PageObject<SysUserDeptVo> po=

                 userService.findPageObjects("admin",1);

                 System.out.println("rowCount:"+po.getRowCount());

         }

}

2.4 扩展业务织入增强分析

2.4.1基于JDK代理方式实现

假如目标对象有实现接口,则可以基于JDK为目标对象创建代理对象,然后对目标对象进行功能扩展.

2.4.2基于CGLIB代理方式实现

加入目标对象没有实现接口(当然实现了接口也是可以的),可以基于CGLIB代理方式为目标对象织入功能扩展.
说明:目标对象实现了接口也可以基于CGLIB为目标对象创建对象.

3.Spring AOP增强

3.1切面通知应用增强

3.1.1通知类型

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述是一种扩展业务),它们分别是:
@Before
@AfterReturning
@AfterThrowing
@After
@Around重点掌握(优先级最高)
说明:在切面类中使用什么通知,由业务决定,并不是说,在切面中要把所有的通知都写上.

3.1.2 通知执行顺序

image

说明:实际项目中可能不会在切面中定义所有通知,具体定义那些通知要结合业务进行实现.

3.1.3通知实践过程分析

@Component

@Aspect

public class SysTimeAspect {

        @Pointcut("bean(sysUserServiceImpl)")

        public void doTime(){}


        @Before("doTime()")

        public void doBefore(JoinPoint jp){

                System.out.println("time doBefore()");

        }

        @After("doTime()")

        public void doAfter(){

                System.out.println("time doAfter()");

        }

        /**核心业务正常结束时执行* 说明:假如有after,先执行after,再执行returning*/

        @AfterReturning("doTime()")

        public void doAfterReturning(){

                System.out.println("time doAfterReturning");

        }

        /**核心业务出现异常时执行说明:假如有after,先执行after,再执行Throwing*/

        @AfterThrowing("doTime()")

        public void doAfterThrowing(){

                System.out.println("time doAfterThrowing");

        }

        @Around("doTime()")

        public Object doAround(ProceedingJoinPoint jp)

                        throws Throwable{

                System.out.println("doAround.before");

         try{

                 Object obj=jp.proceed();

           System.out.println("doAround.after");

          return obj;

                 }catch(Throwable e){

          System.out.println(e.getMessage());

          throw e;

         }

                

        }

}
说明:对于@AfterThrowing通知只有在出现有异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现.

3.2切入点表达式增强

3.2.1bean表达式(重点)

bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean("userServiceImpl")指定一个userServiceImpl中的所有方法.
bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法.
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中的某个bean的name.

3.2.2within表达式(了解)

within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within("aop.service.UserServiceImpl")指定当前包中这个类内部的所有方法.
within("aop.service.*") 指定当前目录下的所有类的所有方法.
within("aop.service..*") 指定当前目录以及子目录中类的所有方法.

3.2.3execution表达式(了解)

execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表)).
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法
execution( aop.service...*(..)) 万能配置.

3.2.4@annotation表达式(重点)

@annontation(anno.RequiredLog)匹配有此注解描述的方法.
@annontation(anno.RequiredCache)匹配有此注解描述的方法.
其中:RequiredLog为我们自定义注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作.
:定义一Cache相关切面,使用注解表达式定义切入点,并使用此注解对需要使用cache的业务方法进行描述,代码分析如下:
第一步:定义注解RequiredCache
package com.cy.pj.common.annotation;

/**

 * 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口

 */

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface RequiredCache {

   //...

}
第二步:定义SysCacheAspect切面对象
package com.cy.pj.common.aspect;

@Aspect

@Component

public class SysCacheAspect {

            @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")

          public void doCache() {}


          @Around("doCache()")

          public Object around(ProceedingJoinPoint jp)

    throws Throwable{

                  System.out.println("Get data from cache");

                  Object obj=jp.proceed();

                  System.out.println("Put data to cache");

                  return obj;

          }

   

}
第三步: 使用@RequiredCache注解对特定业务目标对象中的查询方法进行描述.
 @RequiredCache

        @Override

        public List<Map<String, Object>> findObjects() {

                ….

                return list;

        }

3.3切面优先级设置实现

切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级
@Order(1)

@Aspect

@Component

public class SysLogAspect {

 …

}
定义缓存切面并指定优先级
@Order(2)

@Aspect

@Component

public class SysCacheAspect {

        …

}
说明: 当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链.

3.4关键对象与术语总结

Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图-10所示:

image

4.Spring AOP中的事务处理

4.1Spring AOP中事务简介

4.1.1事务定义

事务是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更好的保证业务的正确性.

4.1.2事物的特性

事务具备的ACID特性,分别是:
原子性:一个事务中的多个操作要么都成功要么都失败。
一致性:例如存钱操作,存之前和存之后的总钱数应该是一致的。
隔离性:事务与事务应该是相互隔离的。
持久性:事务一旦提交,数据要持久保存。

4.2Spring中的事务处理

4.2.1Spring中事务方式概述

Spring框架中提供了一种声明式事务的处理方式,此方式基于AOP代理,可以将具体业务逻辑与事务处理进行解耦.也就是让我们的业务代码逻辑不受污染或少量污染,就可以实现事务控制.
在SpringBoot项目中,其内部提供了事务的自动配置,当我们在项目中添加了指定依赖spring-boot-starter-jdbc时,框架会自动为我们的项目注入事务管理器对象,最常用的为DataSourceTransactionMannager对象.

4.2.2Spring 中事务管理实现

1)启用声明式事务管理,在项目启动类上添加@EnableTransactionManagement,新版本可以不用添加(例如SpringBoot项目)
2)将@Transactional注解添加到合适的业务类或方法上,并设置合适的属性信息.
代码示例:
@Transactional(timeout = 30,

               readOnly = false,

               isolation = Isolation.READ_COMMITTED,

               rollbackFor = Throwable.class,

               propagation = Propagation.REQUIRED)

  @Service

  public class implements SysUserService {

        @Transactional(readOnly = true)

    @Override

         public PageObject<SysUserDeptVo> findPageObjects(

                        String username, Integer pageCurrent) {

      …

       }

}

当@Transactional注解应用在类上时表示类中所有方法启动事务管理,并且一般用于事务共性的定义
当@Transactional描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的事务特性优先级比较高。
@Transactional 常用属性应用说明:
timeout:事务的超时时间,默认值为-1,表示没有超时显示。如果配置了具体时间,则超过该时间限制但事务还没有完成,则自动回滚事务。这个时间的记录方式是在事务开启以后到sql语句执行之前.
read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true。对添加,修改,删除业务read-only的值应该为false。
rollback-for:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for: 抛出no-rollback-for 指定的异常类型,不回滚事务
isolation事务的隔离级别,默认值采用 DEFAULT。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不希望出现这些现象可考虑修改事务的隔离级别(但隔离级别越高并发就会越小,性能就会越差).

5.Spring AOP 异步操作实现

5.1异步场景分析

在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想就是“串行”改“并行”。说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用Spring的@Async的异步注解。

5.2Sping 业务的异步实现

5.2.1启动异步配置

再基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,SpringBoot版本的项目,将@EnableAsync注解应用到启动类上,代码示例如下:
  @EnableAsync //spring容器启动时会创建线程池

   @SpringBootApplication

   public class Application {

        public static void main(String[] args) {

                SpringApplication.run(Application.class, args);

        }

}

5.2.2Spring中@Async注解应用

在需要异步执行的业务方法上,使用@Async方法进行异步声明.
@Async

        @Transactional(propagation = Propagation.REQUIRES_NEW)

        @Override

        public void saveObject(SysLog entity) {

      System.out.println("SysLogServiceImpl.save:"+

Thread.currentThread().getName());

          sysLogDao.insertObject(entity);

          //try{Thread.sleep(5000);}catch(Exception e) {}

        }
假如需要获取业务层异步方法的执行结果,可以参考如下代码设计进行实现:
 @Transactional(propagation = Propagation.REQUIRES_NEW)

   @Async

        @Override

        public Future<Integer> saveObject(SysLog entity) {

                System.out.println("SysLogServiceImpl.save:"+

Thread.currentThread().getName());

                int rows=sysLogDao.insertObject(entity);

                //try{Thread.sleep(5000);}catch(Exception e) {}

            return new AsyncResult<Integer>(rows);

        }
其中,AsyncResult对象可以对异步方法的执行结果进行封装,假如外界需要异步方法结果时,可以通过Future对象的get方法获取结果。
我们需要自己对spring框架提供的连接池进行一些简易配置,可以参考如下代码:
spring:

  task:

    execution:

      pool:

        queue-capacity: 128

        core-size: 5

        max-size: 128

        keep-alive: 60000

      thread-name-prefix: db-service-task-

阅读 157
4 声望
3 粉丝
0 条评论
4 声望
3 粉丝
宣传栏