Spring AOP简介

Spring AOP简介

1. AOP 概述
1.1.1 What's AOP?
AOP(Aspect Orient Programming):面向切面编程
AOP是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以经过预编译方式和运行期动态代理方式,实如今不修改源代码的状况下给程序动态统一添加额外功能的一种技术。如图-1所示:imagejava

AOP与OOP(Object Orient Programming)字面意思相近,但其实二者彻底是面向不一样领域的设计思想。实际项目中咱们一般将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,能够在对象运行时动态织入一些扩展功能或控制对象执行。
1.1.2 AOP 应用场景分析?
实际项目中一般会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时咱们首先要完成的是核心业务的实现,非核心业务通常是经过特定方式切入到系统中,这种特定方式通常就是借助AOP进行实现。mysql

AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并能够"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等。如图-2所示:
image算法

思考:现有一业务,在没有AOP编程时,如何基于OCP原则实现功能扩展?
实现对象功能扩展如图所示:imagespring

1.1.3 AOP 应用原理分析(先了解)?
Spring AOP底层基于代理机制实现功能扩展:sql

  1. 假如目标对象(被代理对象)实现接口,则底层能够采用JDK动态代理机制为目标对象建立代理对象(目标类和代理类会实现共同接口)。
  2. 假如目标对象(被代理对象)没有实现接口,则底层能够采用CGLIB代理机制为目标对象建立代理对象(默认建立的代理类会继承目标对象类型)。
    Spring AOP 原理分析,如图-3所示:

image

说明:Spring boot2.x版本中AOP如今默认使用的CGLIB代理,假如须要使用JDK动态代理能够在配置文件(applicatiion.properties)中进行以下配置:数据库

spring.aop.proxy-target-class=false

1.2 AOP 相关术语分析编程

  • 切面(aspect): 横切面对象,通常为一个具体类对象(能够借助@Aspect声明)。
  • 通知(Advice):在切面的某个特定链接点上执行的动做(扩展功能),例如around,before,after等。
  • 链接点(joinpoint):程序执行过程当中某个特定的点,通常指被拦截到的的方法。
  • 切入点(pointcut):对多个链接点(Joinpoint)一种定义,通常能够理解为多个链接点的集合。

链接点与切入点定义如图-4所示:image缓存

说明:咱们能够简单的将机场的一个安检口理解为链接点,多个安检口为切入点,安全检查过程当作是通知。总之,概念很晦涩难懂,多作例子,作完就会清晰。先能够按白话去理解。安全

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;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * @Aspect 注解,描述的类为spring容器中一个切面对象类型(此类型中封装切入点与通知方法)
 * 1)切入点:要执行扩展业务的方法的集合
 * 2)通知方法:封装了在切入点方法上要执行的扩展业务方法
 */
@Order(1)//@Order注解用于描述切面的优先级,数字越小优先级越高,默认优先级比较低
@Aspect
@Slf4j
@Component
public class SysLogAspect {
//    private static final Logger log=LoggerFactory.getLogger(SysLogAspect.class); //这句代码等效于@Slf4j注解
    /**
     * @Pointcut 注解用于描述切入点(在哪些点上执行扩展业务)
     * bean(bean对象名字  默认为类名首字母小写):为一种切入点表达式(这个表达式中定义了哪一个或哪些bean对象的方法要进行功能扩展).
     *     例如,bean(sysUserServiceImpl)表达式表示名字为sysUserServiceImpl的bean对象中全部方法的集合为切入点,
      *  也就是说这个sysUserServiceImpl对象中的任意方法执行时都要进行功能扩展.
     */
    @Pointcut("bean(sysUserServiceImpl)")  
    public void doLogPointCut() {}//此方法内部不须要写具体实现,方法的方法名也是任意的
    /**
     * @Around 注解描述的方法为一个通知方法(便是一个服务增益方法),此方法内部能够作服务增益(扩展业务),@Around注解
     *  内部要指定切入点表达式,在此切入点表达式对应的切入点方法上作功能扩展
     * @param jp  表示链接点,链接点是动态肯定的,用于封装正在执行的切入点方法(目标方法)信息.
     * @return 目标方法的执行结果
     * @throws Throwable  通知方法中执行过程出现的异常
     */
    @Around("doLogPointCut()")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        try {
            //1.记录方法开始执行时间
            long start = System.currentTimeMillis();
            log.info("start:{}",start);
            //2.执行目标方法
            Object result=jp.proceed();//最终(中间还能够调用本类其它通知或其它切面的通知)会调用目标方法
            //3.记录方法结束执行时间
            long after = System.currentTimeMillis();
            log.info("after:{}",after);
            String targetClassMethod=getTargetClassMethod(jp);
            log.info("{}目标方法的执行耗时:{}",targetClassMethod,(after-start));
            //4.返回目标方法的执行结果
            return result;//目标方法的执行结果
        }catch(Throwable e) {
            log.error("目标方法执行时出现了异常:{}",e.getMessage());
            throw e;
        }
    }
    /**获取目标方法的全限定名(目标类全名(包名+类名)+方法名)*/
    private String getTargetClassMethod(ProceedingJoinPoint jp) {
        //1.获取目标对象的类型
         Class<?> targetCls = jp.getTarget().getClass();
        //2.获取目标对象的类全名(包名+类名)
        String targetClsName = targetCls.getName();
        //3.火球目标对象的方法名
        //3.1 获取方法签名(方法签名对象中封装了方法相关信息)
        MethodSignature ms = (MethodSignature)jp.getSignature();
        //3.2 基于方法签名获取方法名
        String methodName=ms.getName();
        //4.构建方法的全限定名并返回
        return targetClsName+"."+methodName;
        
    }
    

}

说明:

  • @Aspect 注解用于标识或者描述AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
  • @Pointcut注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的方式进行描述),在本案例中切入点表达式用的是bean表达式,这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的名字。
  • @Around注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务方法执行以前和以后要执行的一个动做),@Aournd注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut注解描述的方法的方法名)。
  • ProceedingJoinPoint类为一个链接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。只能用于@Around注解描述的方法参数。

2.3.1 业务切面测试实现
启动项目测试或者进行单元测试,其中Spring Boot项目中的单元测试代码以下:

@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());

 }

}

对于测试类中的userService对象而言,它有可能指向JDK代理,也有可能指向CGLIB代理,具体是什么类型的代理对象,要看application.yml配置文件中的配置。

aop:
      proxy-target-class: false #false表示系统底层会基于JDK方式为目标对象建立代理对象。默认为true,表示系统底层会基于CGLIB方式为目标对象建立代理对象

2.3.2 应用总结分析
在业务应用,AOP相关对象分析,如图-5所示:
image

2.4 扩展业务织入加强分析

2.4.1 基于JDK代理方式实现
假如目标对象有实现接口,则能够基于JDK为目标对象建立代理对象,而后为目标对象进行功能扩展,如图-6所示:image

2.4.2 基于CGLIB代理方式实现
假如目标对象没有实现接口(固然实现了接口也是能够的),能够基于CGLIB代理方式为目标对象织入功能扩展,如图-7所示:
image
说明:目标对象实现了接口也能够基于CGLIB为目标对象建立代理对象。

3 Spring AOP编程加强

3.1切面通知应用加强
3.1.1 通知类型
在基于Spring AOP编程的过程当中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:

  • @Before。
  • @AfterReturning。
  • @AfterThrowing。
  • @After。
  • @Around.重点掌握(优先级最高)
    说明:在切面类中使用什么通知,由业务决定,并非说,在切面中要把全部通知都写上。

    3.1.2 通知执行顺序
    假如五种类型的通知所有写到一个切面对象中,其执行顺序及过程,如图-8所示:
    image

    说明:实际项目中可能不会在切面中定义全部的通知,具体定义哪些通知要结合业务进行实现。

    3.1.3 通知实践过程分析
    代码实践分析以下:

package com.cy.pj.common.aspect;

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.Component;
@Aspect
@Component
public class SysTimeAspect {

    @Pointcut("bean(sysUserServiceImpl)")
    public void doTime() {
        //不写具体内容
    }

    @Before("doTime()")
    public void doBefore(JoinPoint jp){
        System.out.println("@Before");
    }

    @After("doTime()")
    public void doAfter(){
        System.out.println("@After");
    }

    /**核心业务正常结束时执行* 说明:假若有after,先执行after,再执行returning*/
    @AfterReturning("doTime()")
    public void doAfterReturning(){
        System.out.println("@AfterReturning");
    }

    /**核心业务出现异常时执行说明:假若有after,先执行after,再执行Throwing*/
    @AfterThrowing("doTime()")
    public void doAfterThrowing(){
        System.out.println("@AfterThrowing");
    }


    
    @Around("doTime()")
    public Object doAround(ProceedingJoinPoint jp)
            throws Throwable{
        System.out.println("@Around.before");
        try{
            Object obj=jp.proceed(); //执行目标方法
            System.out.println("@Around.after");
            return obj;
        }catch(Throwable e){
            //e.printStackTrace();
            System.out.println(e.getMessage());
            System.out.println("@Around.throwing");
            throw e;
        }

    }



}

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

课堂练习:定义一个异常监控切面,对目标页面方法进行异常监控,并以日志信息的形式输出异常

package com.cy.pj.common.aspect;

import lombok.extern.slf4j.Slf4j;

@Slf4j

@Aspect

@Component

public class SysExceptionAspect {

 @AfterThrowing(pointcut="bean(\*ServiceImpl)",throwing = "e")

 public void doHandleException(JoinPoint jp,Throwable e) {

 MethodSignature ms\=(MethodSignature)jp.getSignature();

 log.error("{}'exception msg is

{}",ms.getName(),e.getMessage());

 }

}

说明:AfterThrowing中throwing属性的值,须要与它描述的方法的异常参数名相同。

3.2 切入点表达式加强
Spring中经过切入点表达式定义具体切入点,其经常使用AOP切入点表达式定义及说明:

表-1 Spring AOP 中切入点表达式说明
| 指示符 | 做用 |
|bean| 用于匹配指定bean对象的全部方法|
| within | 用于匹配指定包下全部类内的全部方法|
|execution | 用于按指定语法规则匹配到具体方法 |
| @annotation | 用于匹配指定注解修饰的方法 |

3.2.4 bean表达式(重点)
bean表达式通常应用于类级别,实现粗粒度的切入点定义,案例分析:

  • bean("userServiceImpl")指定一个userServiceImpl类中全部方法。
  • bean("*ServiceImpl")指定全部后缀为ServiceImpl的类中全部方法。

说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。

3.2.5 within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:

  • within("aop.service.UserServiceImpl")指定当前包中这个类内部的全部方法。
  • within("aop.service.*") 指定当前目录下的全部类的全部方法。
  • within("aop.service..*") 指定当前目录以及子目录中类的全部方法。

within表达式应用场景分析:

1)对全部业务bean都要进行功能加强,可是bean名字又没有规则。

2)按业务模块(不一样包下的业务)对bean对象进行业务功能加强。

3.2.6 execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。

  • execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
  • execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
  • execution(* aop.service..*.*(..)) 万能配置。

    3.2.7 @annotation表达式(重点)

@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析

  • @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
  • @annotation(anno.RequiredCache) 匹配有此注解描述的方法。

其中:RequiredLog为咱们本身定义的注解,当咱们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日志扩展操做。
课堂练习:定义一Cache相关切面,使用注解表达式定义切入点,并使用此注解对须要使用cache的业务方法进行描述,代码分析以下:
第一步:定义注解RequiredCache

package com.cy.pj.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 *  自定义注解,一个特殊的类,全部注解都默认继承Annotation接口
 * @Retention注解用于定义注解什么时候生效
 * @Target注解用于定义注解能够描述对象
 * @Documented 将注解中的文档注释在提取时也要生存API文档
 * @author Administrator
 *
 */
@Retention(RetentionPolicy.RUNTIME)//@Retention注解用于定义注解什么时候生效
@Target(ElementType.METHOD) //@Target注解用于定义注解能够描述对象
@Documented //将注解中的文档注释在提取也要生存API文档
public @interface RequiredCache {
    
    String key() default "";

}

第二步:定义SysCacheAspect切面对象

package com.cy.pj.common.aspect;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SysCacheAspect {
    //假设这个容器就是cache(固然这个cache还须要进行更好的设计)
//    private Map<Object,Object> cache=new HashMap<>();//此map为线程不安全的hashMap
    private Map<Object,Object> cache=new ConcurrentHashMap<>();  //此map为线程安全的hashMap<>();//此map为线程不安全的hashMap
    
    
    /**
     * 基于@annotation(注解类全名 )表达式定义切入点(这种切入点一般理解为细粒度的切入点)
     */
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)") //自定义注解
    public void doCache() {}
    
    @Pointcut("@annotation(com.cy.pj.common.annotation.ClearCache)") //自定义注解
    public void doClearCache() {}
    //FAQ分析  假如我如今须要一个清楚chache的通知方法,咱们该写在哪一个通知方法中?写在@AfterReturning通知方法中

    
    @AfterReturning("doClearCache()")
    public void doAfterReturing() {
        cache.clear();
    }
    
    
    
    
    @Around("doCache()")
    public Object around(ProceedingJoinPoint jp)throws Throwable{
        //1.从cache中获取数据,假如cache中有咱们须要的数据集则直接返回,不须要在查询数据,这样能够确保更好的性能
        System.out.println("Get data from cahce");
        Object result = cache.get("dept");//这个key的名字是本身随意指定的(未来能够写得更加灵活)
        //FAQ分析? 如何将cache中的key定义的更加灵活(在描述切入点的方法注解中直指定)
        //FAQ分析?如何获取切入点方法上注解中的key?(向获取目标方法,而后基于目标方法获取方法上的注解,再经过注解提取key的值)
        if(result!=null)return result;
        //2.假如cache中没有,则从数据库中去查询
        result = jp.proceed();
        System.out.println("Put data to cache");
        //3.将查询的结果存储到cache中,便于下次查询使用
        cache.put("dept", result);
        return result;
        
        
    }

}

第三步:使用@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 {

 …

}

说明:当多个切面做用于同一个目标对象方法时,这些切面会构建成一个切面链,相似过滤器链、拦截器链,其执行分析如图-9所示:

image

3.4 关键对象与术语总结
Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图-10所示:
image

3.5用户行为日志记录实现(实践)
以AOP方式记录项目中的用户行为信息,并将其存储到数据库

4.Spring AOP事务处理

4.1 Spring 中事务简介
4.1.1事务定义(什么是事务?)
事务(Transaction)是一个业务,是一个不可分割的逻辑工做单元,基于事务能够更好的保证业务的正确性。
4.1.2 事务特性
事务具有ACID特性,分别是:

  • 原子性(Atomicity):一个事务中的多个操做要么都成功要么都失败。
  • 一致性(Consistency): 例如存钱操做,存以前和存以后的总钱数应该是一致的。
  • 隔离性(Isolation):事务与事务应该是相互隔离的。
  • 持久性(Durability):事务一旦提交,数据要持久保存。

说明:目前市场上在事务一致性方面,一般会作必定的优化,比方说只要最终一致就能够了,这样的事务咱们一般会称之为柔性事务(只要最终一致就能够了).

4.2 Spring 中事务管理
4.2.1 Spring 中事务方式概述
Spring框架中提供了一种声明式事务的处理方式,此方式基于AOP代理,能够将具体业务逻辑与事务处理进行解耦。也就是让咱们的业务代码逻辑不受污染或少许污染,就能够实现事务控制。

在SpringBoot项目中,其内部提供了事务的自动配置,当咱们在项目中添加了指定依赖spring-boot-starter-jdbc时,框架会自动为咱们的项目注入事务管理器对象,最经常使用的为DataSourceTransactionManager对象。
4.2.2 Spring 中事务管理实现
本小节重点讲解实际项目中最经常使用的注解方式的事务管理,以注解@Transactional配置方式为例,进行实践分析。

基于@Transactional 注解进行声明式事务管理的实现步骤分为两步:

  1. 启用声明式事务管理,在项目启动类上添加@EnableTransactionManagement,新版本中也可不添加(例如新版Spring Boot项目)。
  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注解用于描述类或方法,告诉spring框架咱们要在此类的方法执行时进行事务控制,其具体说明以下:。

  • 当@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。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不但愿出现这些现象可考虑修改事务的隔离级别(但隔离级别越高并发就会越小,性能就会越差)

Spring 中事务控制过程分析,如图-11所示:
image

Spring事务管理是基于接口代理(JDK)或动态字节码(CGLIB)技术,而后经过AOP实施事务加强的。当咱们执行添加了事务特性的目标方式时,系统会经过目标对象的代理对象调用DataSourceTransactionManager对象,在事务开始的时,执行doBegin方法,事务结束时执行doCommit或doRollback方法。

4.2.3Spring 中事务传播特性

事务传播(Propagation)特性指"不一样业务(service)对象"中的事务方法之间相互调用时,事务的传播方式,如图-12所示:
image

其中,经常使用事务传播方式以下:

  • @Transactional(propagation=Propagation.REQUIRED) 。

若是没有事务建立新事务, 若是当前有事务参与当前事务, Spring 默认的事务传播行为是PROPAGATION_REQUIRED,它适合于绝大多数的状况。假设 ServiveX#methodX() 都工做在事务环境下(即都被 Spring 事务加强了),假设程序中存在以下的调用链:

Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法经过 Spring 的事务传播机制都工做在同一个事务中。如图-13所示:
image
代码示例以下:

@Transactional(propagation = Propagation.REQUIRED)

 @Override

 public List<Node> findZtreeMenuNodes() {

 return sysMenuDao.findZtreeMenuNodes();

 }

当有一个业务对象调用如上方法时,此方法始终工做在一个已经存在的事务方法,或者是由调用者建立的一个事务方法中。

@Transactional(propagation=Propagation.REQUIRES_NEW)。
必须是新事务, 若是有当前事务, 挂起当前事务而且开启新事务,如图-14所示:
image

代码示例以下:

@Transactional(propagation = Propagation.REQUIRES\_NEW)

 @Override

 public void saveObject(SysLog entity) {

 sysLogDao.insertObject(entity);

 }

当有一个业务对象调用如上业务方法时,此业务方法会始终运行在一个新的事务中。

4.3 Spring 中事务管理小结
pring 声明式事务是 Spring 最核心,最经常使用的功能。因为 Spring 经过 IOC 和 AOP的功能很是透明地实现了声明式事务的功能,对于通常的开发者基本上无须了解 Spring声明式事务的内部细节,仅须要懂得如何配置就能够了。但对于中高端开发者还须要了解其内部机制。

5. Spring AOP 异步操做实现

5.1异步场景分析
在开发系统的过程当中,一般会考虑到系统的性能问题,提高系统性能的一个重要思想就是“串行”改“并行”。提及“并行”天然离不开“异步”,今天咱们就来聊聊如何使用Spring的@Async的异步注解。

5.2 Spring 业务的异步实现
5.2.1启动异步配置
在基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,Spring Boot版的项目中,将@EnableAsync注解应用到启动类上,代码示例以下:

/**
 * 
    @EnableAsync 注解表示进行异步启动声明(启动异步任务) spring容器启动时会建立线程池
 *
 */
@EnableAsync//启动异步任务
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

5.2.2 Spring中@Async注解应用
在须要异步执行的业务方法上,使用@Async方法进行异步声明。

//@Async注解描述的方法为一个异步切入点方法(此方法会运行在spring框架提供的一个线程中)
    @Async  //@Async 描述的方法表示要运行在一个独立线程中(这个线程由spring内置的线程池来提供)
    @Transactional(propagation = Propagation.REQUIRES_NEW) //让saveObject方法始终运行在一个本身独立的事务中
    @Override
    public void saveObject(SysLog entity) {
        System.out.println("SysLogServiceImpl.saveObject.thread->"+Thread.currentThread().getName());
        //模拟耗时操做(假设写日志是耗时的)
        try {Thread.sleep(5000);} catch (Exception e) {} 
        sysLogDao.insertObject(entity);
    }

假如须要获取业务层异步方法的执行结果,可参考以下代码设计进行实现:

@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框架提供的链接池进行一些简易配置,能够参考以下代码:

spring:
  task:
    execution:
      pool:
        core-size: 3  #定义核心线程数,当池中线程没有达到这个值时,每来一个任务都会建立一个新的线程.
        queue-capacity: 1 #当来了新的任务,核心线程都在忙,假如队列尚未满,则将任务先扔到队列
        max-size: 256 #当核心线程数都在忙,任务队列也满了,再来新的任务在建立新的线程,最多能够建立多少个由这个参数决定
        keep-alive: 60000 #当并发访问高峰期事后,有些线程可能会空闲下来,超出必定的时间,线程要被释放,这个时间的指定由这个参数决定
    thread-name-prefix: cgb-db-thread- #为池中的线程起个名字的前缀

说明:对于@Async注解默认会基于ThreadPoolTaskExecutor对象获取工做线程,而后调用由@Async描述的方法,让方法运行于一个工做线程,以实现异步操做。可是假如系统中的默认拒绝处理策略,任务执行过程的异常处理不能知足咱们自身业务需求的话,我能够对异步线程池进行自定义.(SpringBoot中默认的异步配置能够参考自动配置对象TaskExecutionAutoConfiguration).

5.2.3Spring 自定义异步池的实现(拓展)
为了让Spring中的异步池更好的服务于咱们的业务,同时也尽可能避免OOM(Out Of Memory:内存溢出 ),能够自定义线程池优化设计以下:关键代码以下:

package com.cy.pj.common.config

@Slf4j

@Setter

@Configuration

@ConfigurationProperties("async-thread-pool")

public class SpringAsyncConfig implements AsyncConfigurer{

 /\*\*核心线程数\*/

 private int corePoolSize\=20;

 /\*\*最大线程数\*/

 private int maximumPoolSize=1000;

 /\*\*线程空闲时间\*/

 private int keepAliveTime\=30;

 /\*\*阻塞队列容量\*/

 private int queueCapacity\=200;

 /\*\*构建线程工厂\*/

 private ThreadFactory threadFactory\=new ThreadFactory() {

 //CAS算法

 private AtomicInteger at\=new AtomicInteger(1000);

 @Override

 public Thread newThread(Runnable r) {

 return new Thread(r,

"db-async-thread-"+at.getAndIncrement());

 }

 }; 

 @Override

 public Executor getAsyncExecutor() {

 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

 executor.setCorePoolSize(corePoolSize);

 executor.setMaxPoolSize(maximumPoolSize);

 executor.setKeepAliveSeconds(keepAliveTime);

 executor.setQueueCapacity(queueCapacity);

 executor.setRejectedExecutionHandler((Runnable r,

 ThreadPoolExecutor exe) -> {

 log.warn("当前任务线程池队列已满.");

 });

 executor.initialize();

 return executor;

 }

 @Override

 public AsyncUncaughtExceptionHandler

getAsyncUncaughtExceptionHandler() {

 return new AsyncUncaughtExceptionHandler() {

 @Override

 public void handleUncaughtException(Throwable ex ,

 Method method , Object... params) {

 log.error("线程池执行任务发生未知异常.", ex);

 }

 };

 }}

其中:@ConfigurationProperties("async-thread-pool")的含义是读取application.yml配置文件中以"async-thread-pool"名为前缀的配置信息,并经过所描述类的set方法赋值给对应的属性,在application.yml中链接器池的关键配置以下:

async-thread-pool:

 corePoolSize: 20

 maxPoolSize: 1000

 keepAliveSeconds: 30

 queueCapacity: 1000

后续在业务类中,假如咱们使用@Async注解描述业务方法,默认会使用ThreadPoolTaskExecutor池对象中的线程执行异步任务

6 Spring AOP中Cache操做实现(拓展)

6.1缓存场景分析
在业务方法中咱们可能调用数据层方法获取数据库中数据,假如访问数据的频率比较高,为了提升的查询效率,下降数据库的访问压力,能够在业务层对数据进行缓存.
6.2Spring 中业务缓存应用实现
6.2.1启动缓存配置
在项目(SpringBoot项目)的启动类上添加@EnableCaching注解,以启动缓存配置。关键代码以下:

/\*\*

\* 异步的自动配置生效).

 \* @EnableCaching 注解表示启动缓存配置

 \*/

@EnableCaching

@SpringBootApplication

public class Application {

 public static void main(String\[\] args) {

 SpringApplication.run(Application.class, args);

 }

}

6.2.2业务方法上应用缓存配置
在须要进行缓存的业务方法上经过@Cacheable注解对方法进行相关描述.表示方法的返回值要存储到Cache中,假如在更新操做时须要将cache中的数据移除,能够在更新方法上使用@CacheEvict注解对方法进行描述。例如:
第一步:在相关模块查询相关业务方法中,使用缓存,关键代码以下:

/**@Cacheable 注解描述的方法为一个缓存切入点方法,此方法在执行以前首先会从缓存中取数据,缓存中没有数据,则从数据库查询*/
@Cacheable(value = "menuCache")

@Transactional(readOnly = true)

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

....

}

其中,value属性的值表示要使用的缓存对象,名字本身指定,其中底层为一个map对象,当向cache中添加数据时,key默认为方法实际参数的组合。

第二步:在相关模块更新时,清除指定缓存数据,关键代码以下:

/**@CacheEvict 注解描述的方法也是一个缓存切入点方法,此方法在执行以前首先会从缓存中取数据,缓存中没有数据,则从数据库查询
     * 1) value 属性用于指定缓存对象的名称
     * 2) allEntries 用于指定是否清除缓存全部数据,true表示清除缓存全部数据
     * 3) beforeInvocation用于指定清楚缓存的动做在目标方法执行以前仍是以后执行.这里的false表示以后
    */
    
  @CacheEvict(value="menuCache",allEntries\=true)

 @Override

 public int saveObject(SysDept entity) {...}

其中,allEntries表示清除全部。

说明:spring中的缓存应用原理,如图-15所示:
image

6.2.3Spring中自定义缓存的实现(拓展)
在Spring中默认cache底层实现是一个Map对象,假如此map对象不能知足咱们实际须要,在实际项目中咱们能够将数据存储到第三方缓存系统中.

Spring AOP原生方式实现(拓展)

7.1概述
Spring 整合AspectJ框架实现AOP只是Spring框架中AOP的一种实现方式,此方式相对比较简单,实现方便。但此方式底层仍是要转换为Spring原生AOP的实现,Spring AOP原生方式实现的核心有三大部分构成,分别是:

  • JDK代理。
  • CGLIB代理。
  • org.aopalliance包下的拦截体系。

7.1案例架构分析
本小节以Spring中一种原生AOP架构的基本实现为例进行原理分析和说明,其简易架构如图-16所示:image
其中DefaultAdvisorAutoProxyCreator这个类功能更为强大,这个类的奇妙之处是他实现BeanPostProcessor接口,当ApplicationContext读取全部的Bean配置信息后,这个类将扫描上下文,寻找全部的Advisor对象(一个Advisor由切入点和通知组成),将这些Advisor应用到全部符合切入点的Bean中。
7.2案例业务实现
7.2.1业务描述
建立SpringBoot项目,并基于Spring原生AOP的实现为特定业务对象添加简易日志实现。
7.2.2核心业务接口定义及实现
定义RequiredLog注解,用于描述目标业务对象

package com.cy.spring.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface RequiredLog {

}

定义搜索业务接口,用于定义搜索业务规范

package com.cy.spring.aop;

public interface SearchService {

 Object search(String key);

}

定义搜索业务接口实现,并使用requiredLog注解描述

package com.cy.spring.aop;

import org.springframework.stereotype.Service;

import com.cy.spring.annotation.RequiredLog;

@Service

public class DefaultSearchService implements SearchService {

 @RequiredLog

 @Override

 public Object search(String key) {

 System.out.println("search by "+key);

 return null;

 }

}

7.2.3日志Advice对象定义
定义LogAdvice对象,基于此对象为目标业务对象作日志加强。

package com.cy.spring.advisor;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

public class LogAdvice implements MethodInterceptor {

 @Override

 public Object invoke(MethodInvocation invocation)

 throws Throwable {

 System.out.println("start:"+System.currentTimeMillis());

 Object result=invocation.proceed();

 System.out.println("after:"+System.currentTimeMillis());

 return result;

 }

}

其中,MethodInterceptor对象继承Advice对象,基于此对象方法能够对目标方法进行拦截。

7.2.4日志Advisor对象定义及实现
建立日志Advisor对象,在对象内部定义要切入扩展功能的点以及要应用的通知(Advice)对象。

package com.cy.spring.advisor;

import java.lang.reflect.Method;

import org.springframework.stereotype.Component;

import com.cy.spring.annotation.RequiredLog;

@Component

public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {

 private static final long serialVersionUID = 7022316764822635205L;

 public LogMethodMatcher() {

 //在特定切入点上要执行的通知

 setAdvice(new LogAdvice());

 }

 //Pointcut

 //方法返回值为true时,则能够为目标方法对象建立代理对象

 @Override

 public boolean matches(Method method,Class<?> targetClass) {

 try {

 Method targetMethod=

 targetClass.getMethod(method.getName(),

 method.getParameterTypes());

 return targetMethod.isAnnotationPresent(RequiredLog.class);

 }catch(Exception e) {

 return false;

 }

 }

}

其中,StaticMethodMatcherPointcutAdvisor类为Spring框架中定义的一种Advisor,咱们本身写的Advisor能够直接继承此类进行资源整合。
7.2.5日志业务单元测试实现
基于Spring boot项目进行单元测试:

package com.cy;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import com.cy.spring.aop.SearchService;

@SpringBootTest

public class CgbSbootAop01ApplicationTests {

 @Autowired

 private SearchService searchService;

 @Test

 public void testSearch() {

 //System.out.println(searchService);

 searchService.search("tedu");

 }

}

说明:在spring 框架中,不少功能都是原生AOP进行了功能的扩展和实现。

8 Spirng AOP总结

8.1重难点分析

  • AOP 是什么,解决了什么问题,实现原理,应用场景。
    AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,
    解决的问题: 它是面向对象编程(OOP)的一种补充和完善。它以经过预编译方式和运行期动态代理方式,实如今不修改源代码的状况下给程序动态统一添加额外功能的一种技术。
    AOP实现原理:Spring AOP底层基于代理机制实现功能扩展:

(1). 假如目标对象(被代理对象)实现接口,则底层能够采用JDK动态代理机制为目标对象建立代理对象(目标类和代理类会实现共同接口)。
(2). 假如目标对象(被代理对象)没有实现接口,则底层能够采用CGLIB代理机制为目标对象建立代理对象(默认建立的代理类会继承目标对象类型)

应用场景:AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并能够"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等

  • AOP 编程基本步骤及实现过程(以基于AspectJ框架实现为例)。
  • AOP 编程中的核心对象及应用关系。
  • AOP 思想在Spring中的实现原理分析。
  • AOP 编程中基于注解方式的配置实现。(@Aspect,@PointCut,@Around,...)
  • AOP 编程中基于注解方式的事务控制。(@Transactional)
  • AOP 编程中异步操做的实现?(@EnableAsync,@Async)
  • AOP 编程中的缓存应用?(@EnableCaching,@Cacheable,@CacheEvict)

8.1FAQ分析

  • 什么是OCP原则(开闭原则)?
  • 什么是DIP原则 (依赖倒置)?
  • 什么是单一职责原则(SRP)?
  • Spring 中AOP的有哪些配置方式?(XML,注解)
  • Spring 中AOP 的通知有哪些基本类型?(5种)
    @Before。
    @AfterReturning。
    @AfterThrowing。
    @After。
    @Around.重点掌握(优先级最高)
  • Spring 中AOP是如何为Bean对象建立代理对象的?(JDK,CGLIB)
  • Spring 中AOP切面的执行顺序如何指定?(@Order)
  • Spring 单体架构项目中事务的控制要经过Connection对象实现,?
  • Spring 如何保证一个线程一个Connection对象?借助ThreadLocal实现.?
  • 多个事务并发执行时可能会出现什么问题?(脏读,不可重复读,幻影读,又叫幻读)
    what's 脏读(dirty read)?
    在一个事务中,读取到另外一个事务未提交更新的数据,即读取到了脏 数据。提示:须要将数据库的事务隔离级别设置为最低,才可以看到脏读现象。
    what's 不可重复读(unrepeatable read)?
    对同一记录的两次读取结果不一致,由于在两次查询期间,有另外一事务对该记录作了修改(是针对修改操做),这个过程叫作不可重复读。
    what's 幻读(虚读)(phantom read)?
    对同一张表的两次查询结果不一致,由于在两次查询期间,有另外一事务进行了插入或者是删除操做(是针对插入或删除操做);这个过程叫作幻读。
    注意:mysql默认的是不容许出现脏读和不可重复读
  • 如何理解数据库中的的悲观锁和乐观锁?
  • 你了解事务的隔离级别吗?知道具体的应用场景吗?
  • 事务隔离级别分四个等级,在相同数据环境下,对数据执行相同的操做,设置不一样的隔离级别,可能致使不一样的结果。不一样事务隔离级别可以解决的数据并发问题的能力也是不一样的。

一、READ UNCOMMITTED(读未提交数据)

安全性最差,可能出现任何事务并发问题(好比脏读、不能够重复读、幻读等)

但性能最好(不使用!!)

二、READ COMMITTED(读已提交数据)(Oracle默认)

安全性较差

性能较好

能够防止脏读,但不能防止不可重复读,也不能防止幻读;

三、REPEATABLE READ(可重复读)(MySQL默认)

安全性较高

性能较差

能够防止脏读不可重复读,但不能防止幻读问题;

四、SERIALIZABLE(串行化)

安全性最高,不会出现任何并发问题,由于它对同一数据的访问是串行的,非并发访问;

性能最差;(不使用!!)

MySQL的默认隔离级别为REPEATABLE READ,便可以防止脏读和不可重复读

相关文章
相关标签/搜索