AOP切面编程(动吧旅游项目) part6

Spring AOP简介

AOP 是什么?
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。如图
AOP切面编程(动吧旅游项目) part6_第1张图片
AOP与OOP字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
AOP 应用场景分析?
实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现。

AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等。如图
AOP切面编程(动吧旅游项目) part6_第2张图片
AOP 应用原理分析
1)假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口)
2)假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)。
AOP切面编程(动吧旅游项目) part6_第3张图片
说明:Spring boot2.x 中AOP现在默认使用的CGLIB代理,假如需要使用JDK动态代理可以在配置文件(applicatiion.properties)中进行如下配置:

spring.aop.proxy-target-class=false

AOP 相关术语分析

切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合
AOP切面编程(动吧旅游项目) part6_第4张图片

Spring AOP快速实践

项目创建及配置

<dependency>

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

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

   </dependency>

说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了AOP的一些语法,有一个专门的字节码生成器来生成遵守java规范的class文件。

package com.cy.pj.common.aspect;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @Aspect 注解描述的类型为Spring AOP中的切面对象类型。此对象中可以封装:
 * 1)切入点(定义在哪些目标对象的哪些方法上进行功能扩展)
 * 2)通知(封装功能扩展的业务逻辑)
 */
//@Slf4j
@Aspect
@Component
public class SysLogAspect {
	  private static final Logger log = 
			  LoggerFactory.getLogger(SysLogAspect.class); 
       /**
        * PointCut注解用于定义切入点,具体方式可以基于特定表达式进行实现。例如
        * 1)bean为一种切入点表达式类型
        * 2)sysUserServiceImpl 为spring容器中的一个bean的名字
        * 	这里的涵义是当sysUserServiceImpl对象中的任意方法执行时,都由本切面
        * 	对象的通知方法做功能增强。
        */
	   @Pointcut("bean(sysUserServiceImpl)")
	   public void doLogPointCut() {}//此方法中不需要写任何代码
	   
	   /**由@Around注解描述的方法为一个环绕通知方法,我们可以在此方法内部
	          * 手动调用目标方法(通过连接点对象ProceedingJoinPoint的proceed方法进行调用)
	          * 环绕通知:此环绕通知使用的切入点为bean(sysUserServiceImpl)
	          * 环绕通知特点:
	     	1)编写:
	           a)方法的返回值为Object.
	           b)方法参数为ProceedingJoinPoint类型.
	           c)方法抛出异常为throwable.
	        2)应用:
	           a)目标方法执行之前或之后都可以进行功能拓展
	           b)相对于其它通知优先级最高。
	     @param jp 为一个连接对象(封装了正在要执行的目标方法信息)
	     @return 目标方法的执行结果
	     */
	   @Around(value="doLogPointCut()")
	   public Object around(ProceedingJoinPoint jp)throws Throwable{
		    log.info("method start {}",System.currentTimeMillis());
		   try {
		    Object result=jp.proceed();//最终会执行目标方法(sysUserServiceImpl对象中的方法)
		    log.info("method end {}",System.currentTimeMillis());
		    return result;
		   }catch(Throwable e) {
		    log.error("method error {},error msg is {}",
				     System.currentTimeMillis(),e.getMessage());
		    throw e;
		   }
	   }
	   
}

@Aspect 注解用于标识或者描述AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
@Pointcut注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的方式进行描述),在本案例中切入点表达式用的是bean表达式,这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的名字。
@Around注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和之后要执行的一个动作),@Aournd注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut注解描述的方法的方法名)。
ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。一般用于@Around注解描述的方法参数。
AOP切面编程(动吧旅游项目) part6_第5张图片
CGLIB代理分析:
AOP切面编程(动吧旅游项目) part6_第6张图片
JDK代理分析:
AOP切面编程(动吧旅游项目) part6_第7张图片

Spring AOP编程增强

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:
前置通知 (@Before)
返回通知 (@AfterReturning)
异常通知 (@AfterThrowing)
后置通知 (@After)
环绕通知 (@Around) :重点掌握(优先级最高)
通知执行顺序
AOP切面编程(动吧旅游项目) part6_第8张图片

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

         }catch(Throwable e){

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

          throw e;

         }

                return obj;

        }

}

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

说明:对于@AfterThrowing通知只有在出现异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现。
package com.cy.pj.common.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
    * 定义一个异常切面对象,在此切面的通知方法中实现异常信息的记录
 * @author qilei
 * 记住:一个切面对象中不是一定要写上所有的通知方法,具体用什么方法由业务而定。
 */
@Slf4j
@Component
@Aspect
public class SysExceptionAspect {
	  /**
	       * 通过这个通知方法实现一个简易的异常信息的记录
	   * @param jp 连接点对象,此对象封装了你要执行的目标方法对象
	   * @param e 执行目标方法时出现的异常,这个名字一定要
	      *   与@AfterThrowing中的throwing属性值相同。
	   */
	  @AfterThrowing(value="bean(*ServiceImpl)",throwing = "e")
	  public void doRecordExceptionMsg(JoinPoint jp,Throwable e) {
		  //通过连接点对象获取正在执行的目标对象类型名称
		  String targetClassName=jp.getTarget().getClass().getName();
		  //通过连接点对象获取正在执行的方法的方法签名
		  MethodSignature ms=(MethodSignature)jp.getSignature();
		  log.error("{} invoke exception msg is {}",targetClassName+"."+ms.getName(),e.getMessage());
	  }
}

ALT+SHIFT+M 快速提取方法

代理模式和装饰模式的区别:
代理模式,注重对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。
装饰模式,注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。
对于代理类,如何调用对象的某一功能是思考重点,而不需要兼顾对象的所有功能;
对于装饰类,如何扩展对象的某一功能是思考重点,同时也需要兼顾对象的其它功能,因为再怎么装饰,本质也是对象本身,要担负起对象应有的职责。
对代理模式的理解:
创建接口:
1.MailService.java

package com.cy.java.oop;

public interface MailService {
	 void send(String msg);
}

2.创建实现类 MailServiceImpl.java:

package com.cy.java.oop;
/**
 * 目标业务对象,封装核心业务逻辑的处理
 * @author qilei
 *
 */
public class MailServiceImpl implements MailService {
	@Override
	public void send(String msg) {
		//直接修改方法内部代码实现功能拓展,这种方式违背了OCP原则(开闭)
		System.out.println("msg="+msg);
	}
}

3.创建切入点接口Aspect.java

package com.cy.java.oop;

public interface Aspect {

	 void before();
	 void after();
}

4.切入点接口实现类

a)LogAspect.java

package com.cy.java.oop;

public class LogAspect implements Aspect{

	 public void before() {
		 System.out.println("start:"+System.currentTimeMillis());
	 }
	 public void after() {
		 System.out.println("after:"+System.currentTimeMillis());
	 }
}

b)TransactionAspect.java

package com.cy.java.oop;

public class TransactionAspect implements Aspect {

	@Override
	public void before() {
		System.out.println("开启事务");
	}

	@Override
	public void after() {
		System.out.println("提交事务");
	}

}

采用继承的方式实现代理:
LogMailServiceImpl.java

package com.cy.java.oop;
/**
   *  通过继承方式实现功能扩展。
 * @author qilei
 */
public class LogMailServiceImpl extends MailServiceImpl {
	//这个对象封装了日志处理操作(扩展业务)
	private Aspect aspect;//
	public LogMailServiceImpl(Aspect aspect) {
		this.aspect=aspect;
	}
	@Override
	public void send(String msg) {
		//System.out.println("start:"+System.currentTimeMillis());
		aspect.before();
		super.send(msg);//调用父类方法,原有的业务不能丢
		//System.out.println("end:"+System.currentTimeMillis());
		aspect.after();
	}
}

Aspect和LogMailServiceImpl是组合关系
采用组合的形式实现代理:

package com.cy.java.oop;

public class TxMailServiceImpl implements MailService {

	//目标对象接口
	private MailService mailService;
	//封装扩展业务的对象
	private Aspect aspect;
	public TxMailServiceImpl(MailService mailService,Aspect aspect) {
		this.mailService=mailService;
		this.aspect=aspect;
	}
	@Override
	public void send(String msg) {
		aspect.before();
		mailService.send(msg);
		aspect.after();
	}

}

测试类:

package com.cy.java.oop;

public class MailServiceTests {
	public static void main(String[] args) {
		//doCglibProxy();
		doJdkProxy();
	}
	//JDK代理方式的AOP结构分析
    private static void doJdkProxy() {
    	//目标对象
    	MailService target=new MailServiceImpl();
    	//扩展业务(切面对象)
    	TransactionAspect aspect=new TransactionAspect();
    	//JDK代理对象(模拟)
    	MailService jdkProxy=
    	new TxMailServiceImpl(target, aspect);
    	jdkProxy.send("hello cgb 2004");
    	
    }
    
    //CGLIB代理方式的AOP结构分析
	private static void doCglibProxy() {
		//MailService mailService=new MailServiceImpl();
		//扩展业务对象
		LogAspect logAspect=new LogAspect();
		//CGLIB代理对象(模拟)
		MailService mailService=new LogMailServiceImpl(logAspect);
		mailService.send("hello cgb 2004");
	}
}

切入点表达式增强
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
AOP切面编程(动吧旅游项目) part6_第9张图片
bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:

  • bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
  • bean(“ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。
    说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。
    within表达式(了解)
    within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
    within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
    within("aop.service.
    ”) 指定当前目录下的所有类的所有方法。
    within("aop.service…") 指定当前目录以及子目录中类的所有方法。
    execution表达式(了解)
    execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
    execution(返回值类型 包名.类名.方法名(参数列表))。
    execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
    execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
    execution(
    aop.service….(…)) 万能配置。
    @annotation表达式(重点)
    @annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
    @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
    @annotation(anno.RequiredCache) 匹配有此注解描述的方法。
    其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作。

从缓存中获取数据以及缓存的清理

自定义注解:

package com.cy.pj.common.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 ClearCache {

package com.cy.pj.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解:(使用@interface定义注解,默认所有注解都是一个Annotation类型的对象)
 * @Target 注解用于告诉jdk我们自己写的注解可以描述的对象。
 * @Retention 注解用于告诉JDK我们自己写的注解何时有效。
 * 说明:所有的注解都是一种元素据(Meta Data)-一种描述数据的数据(例如使用注解描述类,
 * 描述方法,描述属性,描述方法参数等等)
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredCache {

}

定义缓存类

package com.cy.pj.common.cache;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Component;
/**
 * 简单Cache对象的实现:SimpleCache
 *  产品级Cache要考虑:
* 1)存储结构,存储内容(存储对象字节还是存储对象引用)
* 2)缓存淘汰策略(缓存满的时是否要淘汰数据)
* 3)GC策略(JVM内存不足时,是否允许清除Cache中数据)
* 4)任务调度策略(是否需要每隔一段时间刷新一下缓存)
* 5)日志记录方式(记录命中次数)
* 6)线程并发安全策略
* 7).......
 */

@Component
public class SimpleCache {

	  private Map<Object,Object> cache=new ConcurrentHashMap<>();
	  
	  public boolean putObject(Object key,Object value) {
		  cache.put(key, value);
		  return true;
	  }
	  public Object getObject(Object key) {
		  return cache.get(key);
	  }
	  public void clearObject() {
		  cache.clear();
	  }
	  //.......
}

定义切面

package com.cy.pj.common.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.cy.pj.common.cache.SimpleCache;

@Aspect
@Component
public class SysCacheAspect {
	@Autowired
	private SimpleCache simpleCache;
	
	  /**
	    * @annotation(..)为注解方式的切入点表达式,这里表示由RequiredCache注解描述
	         * 的方法为切入点方法。
	    */
	@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
public void doCachePointCut() {
		
	}
	
	
	 //@Pointcut("execution(* com.cy.pj.sys.service..*.updateObject(com.cy.pj.sys.pojo.SysDept))")
	@Pointcut("@annotation(com.cy.pj.common.annotation.ClearCache)")
	public void doCacheClear() {}
	
	
	
	//执行缓存的清楚操作
	@AfterReturning("doCacheClear()")//目标方法正常结束后执行
	public void doAfterReturning() {
		System.out.println("=====clear()=====");
		simpleCache.clearObject();
	}
	
	//执行完接入点方法后清除
	@AfterReturning("doCacheClear()")	
	public Object doAroundClearCache(ProceedingJoinPoint jp)throws Throwable{
		   Object result=jp.proceed();
		   simpleCache.clearObject();
		   return result;
	   }
	
	
	@Around("doCachePointCut()")
	public Object around (ProceedingJoinPoint pj) throws Throwable {
		System.out.println("get data from cache");
		Object obj=simpleCache.getObject("deptCache");//先写固定值
		if(obj!=null)return obj;
		Object result=pj.proceed();//最终要执行目标方法
		
		System.out.println("put data to cache");
		simpleCache.putObject("deptCache", result);
		return result;
	}
	
	
}

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

@Order(1)

@Aspect

@Component

public class SysLogAspect {}

定义缓存切面并指定优先级:

@Order(2)

@Aspect

@Component

public class SysCacheAspect {}

说明:当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链,其执行分析如图所示:
AOP切面编程(动吧旅游项目) part6_第10张图片
AOP切面编程(动吧旅游项目) part6_第11张图片

用AOP 实现日志模块

iputils工具类

package com.cy.pj.common.util;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class IPUtils {
	private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
	public static String getIpAddr() {
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
			String ip = null;
			try {
				ip = request.getHeader("x-forwarded-for");
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("Proxy-Client-IP");
				}
			if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("WL-Proxy-Client-IP");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("HTTP_CLIENT_IP");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getHeader("HTTP_X_FORWARDED_FOR");
				}
				if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
					ip = request.getRemoteAddr();
				}
			} catch (Exception e) {
				logger.error("IPUtils ERROR ", e);
			}
			return ip;
		}

}

自定义注解:

package com.cy.pj.common.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 {

	  String value() default "operation";
}
package com.cy.pj.common.aspect;
import java.lang.reflect.Method;
import java.util.Arrays;

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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.util.IPUtils;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
/**
 * @Aspect 注解描述的类型为Spring AOP中的切面对象类型。此对象中可以封装:
 * 1)切入点(定义在哪些目标对象的哪些方法上进行功能扩展)
 * 2)通知(封装功能扩展的业务逻辑)
 */
//@Slf4j
@Order(1)
@Aspect
@Component
public class SysLogAspect {
	  private static final Logger log = 
			  LoggerFactory.getLogger(SysLogAspect.class); 
       /**
        * PointCut注解用于定义切入点,具体方式可以基于特定表达式进行实现。例如
        * 1)bean为一种切入点表达式类型
        * 2)sysUserServiceImpl 为spring容器中的一个bean的名字
        * 	这里的涵义是当sysUserServiceImpl对象中的任意方法执行时,都由本切面
        * 	对象的通知方法做功能增强。
        */
	   @Pointcut("bean(sysUserServiceImpl)")
	  //注解方式的切入点表达式
	  //@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
	  public void doLogPointCut() {}//此方法中不需要写任何代码
	   
	   /**由@Around注解描述的方法为一个环绕通知方法,我们可以在此方法内部
	          * 手动调用目标方法(通过连接点对象ProceedingJoinPoint的proceed方法进行调用)
	          * 环绕通知:此环绕通知使用的切入点为bean(sysUserServiceImpl)
	          * 环绕通知特点:
	     	1)编写:
	           a)方法的返回值为Object.
	           b)方法参数为ProceedingJoinPoint类型.
	           c)方法抛出异常为throwable.
	        2)应用:
	           a)目标方法执行之前或之后都可以进行功能拓展
	           b)相对于其它通知优先级最高。
	     @param jp 为一个连接对象(封装了正在要执行的目标方法信息)
	     @return 目标方法的执行结果
	     */
	   @Around(value="doLogPointCut()")
	   public Object around(ProceedingJoinPoint jp)throws Throwable{
		    System.out.println("[email protected]");
		    long start=System.currentTimeMillis();
		    log.info("method start {}",start);
		   try {
		    Object result=jp.proceed();//最终会执行目标方法(sysUserServiceImpl对象中的方法)
		    long end=System.currentTimeMillis();
		    log.info("method end {}",end);
		    System.out.println("SysLogAspect.Around.after");
		    //将用户的正常的行为信息写入到数据库
		    saveUserLog(jp,(end-start));
		    return result;
		   }catch(Throwable e) {
		    log.error("method error {},error msg is {}",
				     System.currentTimeMillis(),e.getMessage());
				      // saveUserException(jp,e,(end-start)); 记录异常日志
		    throw e;
		   }
	   }
	   @Autowired
	   private SysLogService sysLogService;
	   private void saveUserLog(ProceedingJoinPoint jp,long time)throws Exception {
		   //1.获取用户行为日志信息
		   //获取目标对象(要执行的那个目标业务对象)类型
		   Class<?> targetCls=jp.getTarget().getClass();
		   //获取方法签名对象(此对象中封装了要执行的目标方法信息)
		   MethodSignature ms=(MethodSignature) jp.getSignature();
		   //获取目标方法对象,基于此对象获取方法上的RequiredLog注解,进而取到操作名
		   Method targetMethod=targetCls.getMethod(ms.getName(),ms.getParameterTypes());
		   RequiredLog required=targetMethod.getAnnotation(RequiredLog.class);
		   //获取操作名
		   String operation="operation";
		   if(required!=null) {//注解方式的切入点无须做此判断
			   operation=required.value();
		   }
		   //获取目标方法类全名
		   String targetMethodName=targetCls.getName()+"."+ms.getName();
		   //2.构建用户行为日志对象封装用户行为信息
		   SysLog log=new SysLog();
		   log.setIp(IPUtils.getIpAddr());
		   log.setUsername("liuqing");//将来的登陆用户,现在可以先写个假数据
		   log.setOperation(operation);//不知道该写什么
		   log.setMethod(targetMethodName);//类全名.方法名
		   log.setParams(Arrays.toString(jp.getArgs()));
		   log.setTime(time);
		   log.setCreatedTime(new java.util.Date());
		   //3.用户行为日志写入到数据库
		   sysLogService.saveObject(log);
	   }
	   
}

SysLogDao接口中

int insertObject(SysLog entity);

SysLogMapper.xml中添加insertObject元素

<insert id="insertObject">

       insert into sys_logs

       (username,operation,method,params,time,ip,createdTime)

       values

(#{username},#{operation},#{method},#{params},#{time},#{ip},#{createdTime})

</insert>

Service接口及实现类
第一步:在SysLogService接口中,添加保存日志信息的方法。关键代码如下:

void saveObject(SysLog entity)

第二步:在SysLogServiceImpl类中添加,保存日志的方法实现。关键代码如下:

@Override

        public void saveObject(SysLog entity) {

          sysLogDao.insertObject(entity);

}

原理分析,如图
AOP切面编程(动吧旅游项目) part6_第12张图片

spring中事务管理

**事务(**Transaction)是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更好的保证业务的正确性。
事务具备ACID特性,分别是:

  • 原子性(Atomicity):一个事务中的多个操作要么都成功要么都失败。
  • 一致性(Consistency): (例如存钱操作,存之前和存之后的总钱数应该是一致的。
  • 隔离性(Isolation):事务与事务应该是相互隔离的。
  • 持久性(Durability):事务一旦提交,数据要持久保存。
    Spring框架中提供了一种声明式事务的处理方式,此方式基于AOP代理,可以将具体业务逻辑与事务处理进行解耦。也就是让我们的业务代码逻辑不受污染或少量污染,就可以实现事务控制。
    在SpringBoot项目中,其内部提供了事务的自动配置,当我们在项目中添加了指定依赖spring-boot-starter-jdbc时,框架会自动为我们的项目注入事务管理器对象,最常用的为DataSourceTransactionManager对象。
    基于@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 SysUserServiceImpl implements SysUserService {

        @Transactional(readOnly = true)

    @Override

         public PageObject<SysUserDeptVo> findPageObjects(

                        String username, Integer pageCurrent) {}

}

@Transactional注解用于描述类或方法,告诉spring框架我们要在此类的方法执行时进行事务控制,其具体说明如下:。

  • 当@Transactional注解应用在类上时表示类中所有方法启动事务管理,并且一般用于事务共性的定义。

  • 当@Transactional描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的注解一般用于事务特性的定义。
    @Transactional 常用属性应用说明:

  • timeout:事务的超时时间,默认值为-1,表示没有超时显示。如果配置了具体时间,则超过该时间限制但事务还没有完成,则自动回滚事务。timeOut是从事务开启之后,sql语句执行之前。

  • read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。对于添加、修改、删除操作,read-only为false,查询时read-only为true。

  • rollback-for:用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。

  • no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

  • isolation事务的隔离级别,默认值采用 DEFAULT。

  • 事务的隔离级别:当多个事务并发执行时可能出现脏读,不可重复读,幻读等现象,设置事务隔离级别(隔离级别越高,并发就会越小,性能就会越差)。

  • READ_UNCOMMITTED:读未提交

  • READ_COMMITTED:读已提交

  • REPEATABLE_READ:可重复读

  • SERIALIZABLE:串行化
    Spring 中事务控制过程分析,如图
    AOP切面编程(动吧旅游项目) part6_第13张图片

    Spring事务管理是基于接口代理(JDK)或动态字节码(CGLIB)技术,然后通过AOP实施事务增强的。当我们执行添加了事务特性的目标方式时,系统会通过目标对象的代理对象调用DataSourceTransactionManager对象,在事务开始的时,执行doBegin方法,事务结束时执行doCommit或doRollback方法。
    Spring 中事务传播特性
    事务传播(Propagation)特性指"不同业务(service)对象"中的事务方法之间相互调用时,事务的传播方式,如图
    AOP切面编程(动吧旅游项目) part6_第14张图片
    @Transactional(propagation=Propagation.REQUIRED) 。
    如果没有事务创建新事务, 如果当前有事务参与当前事务, Spring 默认的事务传播行为是PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:

Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。如图
AOP切面编程(动吧旅游项目) part6_第15张图片

@Transactional(propagation = Propagation.REQUIRED)

        @Override

        public List<Node> findZtreeMenuNodes() {

                return sysMenuDao.findZtreeMenuNodes();

        }

当有一个业务对象调用如上方法时,此方法始终工作在一个已经存在的事务方法,或者是由调用者创建的一个事务方法中。
@Transactional(propagation=Propagation.REQUIRES_NEW)。
必须是新事务, 如果有当前事务, 挂起当前事务并且开启新事务
AOP切面编程(动吧旅游项目) part6_第16张图片

 @Transactional(propagation = Propagation.REQUIRES_NEW)

        @Override

        public void saveObject(SysLog entity) {

          sysLogDao.insertObject(entity);

        }

Spring AOP异步操作实现

异步场景分析
在开发系统的过程中,通常会考虑到系统的性能问题,提升系统性能的一个重要思想就是“串行”改“并行”。说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用Spring的@Async的异步注解。
Spring 业务的异步实现
1.启动异步配置
在基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,Spring Boot版的项目中,将@EnableAsync注解应用到启动类上,代码示例如下:

 @EnableAsync //spring容器启动时会创建线程池

   @SpringBootApplication

   public class Application {

        public static void main(String[] args) {

                SpringApplication.run(Application.class, args);

        }

}

2.在需要异步执行的业务方法上,使用@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 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(rows);

        }

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

 execution:
      pool:
        core-size: 10 #核心线程数,当池中线程数没达到core-size时,每来一个请求都创建一个新的线程
        queue-capacity: 256 #队列容量,当核心线程都在忙,再来新的任务,会将任务放到队列
        max-size: 128 #当核心线程都在忙,队列也满了,再来新的任务,此时会创建新的线程,直到达到maxSize
        keep-alive: 60 #当任务高峰过后,有些线程会空闲下来,这空闲现线程达到一定的时间会被释放。
        allow-core-thread-timeout: false
      thread-name-prefix: db-service-task-  

application.yml配置

#server
server:
  port: 80
  servlet:
    context-path: /
  tomcat:
    threads:
      max: 1000
#spring 
spring:
  datasource:
    url: jdbc:mysql://localhost:3307/dbsys?serverTimezone=GMT%2B8&characterEncoding=utf8
    username: root
    password: root
  thymeleaf:
    cache: false
    prefix: classpath:/templates/pages/
    suffix: .html
  aop:
    proxy-target-class: true
  task:
    execution:
      pool:
        core-size: 10 #核心线程数,当池中线程数没达到core-size时,每来一个请求都创建一个新的线程
        queue-capacity: 256 #队列容量,当核心线程都在忙,再来新的任务,会将任务放到队列
        max-size: 128 #当核心线程都在忙,队列也满了,再来新的任务,此时会创建新的线程,直到达到maxSize
        keep-alive: 60 #当任务高峰过后,有些线程会空闲下来,这空闲现线程达到一定的时间会被释放。
        allow-core-thread-timeout: false
      thread-name-prefix: db-service-task-  
#mybatis

mybatis:
  mapper-locations:
  - classpath:/mapper/*/*.xml

#log 
logging:
  level:
    com.cy: debug    

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

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中连接器池的关键配置如下:

Spring AOP中Cache操作实现

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

package com.cy;

/**

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

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

 */

@EnableCaching

@SpringBootApplication

public class Application {

        public static void main(String[] args) {

                SpringApplication.run(Application.class, args);

        }


}

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

@Cacheable(value = "deptCache")

@Transactional(readOnly = true)

public List> findObjects() {

....

}

其中,value属性的值表示要使用的缓存对象,名字自己指定,其中底层为一个map对象,当向cache中添加数据时,key默认为方法实际参数的组合。
第二步:在相关模块更新时,清除指定缓存数据,关键代码如下:

 @CacheEvict(value="deptCache",allEntries=true)

    @Override

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

AOP切面编程(动吧旅游项目) part6_第17张图片
Spring中自定义缓存的实现
在Spring中默认cache底层实现是一个Map对象,假如此map对象不能满足我们实际需要,在实际项目中我们可以将数据存储到第三方缓存系统中.

package com.cy.java.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadPoolTests {

	public static void main(String[] args) throws InterruptedException {
		BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(2);
		ThreadFactory threadFactory=new ThreadFactory() {
			AtomicLong al=new AtomicLong(1);
			@Override
			public Thread newThread(Runnable r) {
				//设置线程名
				String name="cgb2004-thread-"+al.getAndIncrement();
				return new Thread(r,name);
			}
		};
		ThreadPoolExecutor tExecutor=
		new ThreadPoolExecutor(
				2,//corePoolSize 核心线程数
				3,//maximumPoolSize最大线程数
				2,//keepAliveTime线程空闲多长时间被释放
				TimeUnit.SECONDS,//unit 时间单位
				workQueue,//workQueue任务队列
				threadFactory,//threadFactory线程工厂
				new CallerRunsPolicy());//拒绝执行任务的一种策略,CallerRunsPolicy表示由调用者线程去执行
		
		tExecutor.execute(new Runnable() {
			@Override
			public void run() {
				
				String tName=Thread.currentThread().getName();
				System.out.println(tName+"->task-01");
				try{Thread.sleep(10000);}catch(Exception e) {}
			}
		});
		tExecutor.execute(new Runnable() {
			@Override
			public void run() {
				String tName=Thread.currentThread().getName();
				System.out.println(tName+"->task-02");
				try{Thread.sleep(10000);}catch(Exception e) {}
			}
		});
		tExecutor.execute(new Runnable() {
			@Override
			public void run() {
				String tName=Thread.currentThread().getName();
				System.out.println(tName+"->task-03");
			}
		});
		
		tExecutor.execute(new Runnable() {
			@Override
			public void run() {
				String tName=Thread.currentThread().getName();
				System.out.println(tName+"->task-04");
			}
		});
		tExecutor.execute(new Runnable() {
			@Override
			public void run() {
				String tName=Thread.currentThread().getName();
				System.out.println(tName+"->task-05");
				//try{Thread.sleep(10000);}catch(Exception e) {}
			}
		});
	}
}

Spring AOP原生方式实现

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

  • JDK代理。
  • CGLIB代理。
  • org.aopalliance包下的拦截体系。
    AOP切面编程(动吧旅游项目) part6_第18张图片
    其中DefaultAdvisorAutoProxyCreator这个类功能更为强大,这个类的奇妙之处是他实现BeanPostProcessor接口,当ApplicationContext读取所有的Bean配置信息后,这个类将扫描上下文,寻找所有的Advisor对象(一个Advisor由切入点和通知组成),将这些Advisor应用到所有符合切入点的Bean中。
    创建SpringBoot项目,并基于Spring原生AOP的实现为特定业务对象添加简易日志实现。
    定义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;

        }

}

日志Advice对象定义

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对象,基于此对象方法可以对目标方法进行拦截。
创建日志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可以直接继承此类进行资源整合。

基于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进行了功能的扩展和实现。

知识点:
1.两种方式取method得区别:
AOP切面编程(动吧旅游项目) part6_第19张图片

2.将要存储的信息转化为json格式字符串:
AOP切面编程(动吧旅游项目) part6_第20张图片
3.缓存中key的存取
AOP切面编程(动吧旅游项目) part6_第21张图片
AOP切面编程(动吧旅游项目) part6_第22张图片
4.jp获取对象的区别
a)jp.getThis():获取正在执行的对象
AOP切面编程(动吧旅游项目) part6_第23张图片
b)jp.getTarget():从目标对象中获取
AOP切面编程(动吧旅游项目) part6_第24张图片
5. ThreadPoolExcutor
AOP切面编程(动吧旅游项目) part6_第25张图片
ExecutorService(ThreadPoolExecutor的顶层接口)使用线程池中的线程执行每个提交的任务,通常我们使用Executors的工厂方法来创建ExecutorService。
线程池解决了两个不同的问题:
1.提升性能:它们通常在执行大量异步任务时,由于减少了每个任务的调用开销,并且它们提供了一种限制和管理资源(包括线程)的方法,使得性能提升明显;
2.统计信息:每个ThreadPoolExecutor保持一些基本的统计信息,例如完成的任务数量。

  • Executors.newCachedThreadPool(无界线程池,自动线程回收)

  • Executors.newFixedThreadPool(固定大小的线程池);

  • Executors.newSingleThreadExecutor(单一后台线程);
    AOP切面编程(动吧旅游项目) part6_第26张图片
    当在execute(Runnable)方法中提交新任务并且少于corePoolSize线程正在运行时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。 如果有多于corePoolSize但小于maximumPoolSize线程正在运行,则仅当队列已满时才会创建新线程。 通过设置corePoolSize和maximumPoolSize相同,您可以创建一个固定大小的线程池。 通过将maximumPoolSize设置为基本上无界的值,例如Integer.MAX_VALUE,您可以允许池容纳任意数量的并发任务。 通常,核心和最大池大小仅在构建时设置,但也可以使用setCorePoolSize和setMaximumPoolSize进行动态更改。
    AOP切面编程(动吧旅游项目) part6_第27张图片
    AOP切面编程(动吧旅游项目) part6_第28张图片
    AOP切面编程(动吧旅游项目) part6_第29张图片
    AOP切面编程(动吧旅游项目) part6_第30张图片
    防止空闲线程在关闭之前终止,可以使用如下方法:

setKeepAliveTime(Long.MAX_VALUE,TimeUnit.NANOSECONDS);

AOP切面编程(动吧旅游项目) part6_第31张图片
主要有三种队列策略:
1.Direct handoffs 直接握手队列
2.Unbounded queues 无界队列
3.Bounded queues 有界队列

你可能感兴趣的:(架构,业务)