四、基础模块AOP搭建(日志与统一异常处理)

一、Spring Boot整合 日志

1.集成日志Log4j2

(1)在resources目录下编写log4j2.xml



    
        
        
        
        
        
        
    
    
        
            
            
            
        
        
        
        
        
            
                
                
            
            
            
                
                
            
        
        
        
            
                
                
            
            
            
                
                
            
        
        
        
            
                
                
            
            
            
                
                
            
        
        
        
            
            
            
                
                
            
        
    
    
        
            
            
            
            
            
        
        
        
        
        
    

 

(2)编写测试类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Test {

    // Logger和LoggerFactory导入的是org.slf4j包
    private final static Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
    public static void main(String[] args) {
        long beginTime = System.currentTimeMillis();

        for(int i = 0; i < 100000; i++) {  
            logger.trace("trace level");  
            logger.debug("debug level");  
            logger.info("info level");  
            logger.warn("warn level");  
            logger.error("error level");
        }

        try {  
            Thread.sleep(1000 * 61);  
        } catch (InterruptedException e) {}  


        logger.info("请求处理结束,耗时:{}毫秒", (System.currentTimeMillis() - beginTime));    //第一种用法
        logger.info("请求处理结束,耗时:" + (System.currentTimeMillis() - beginTime)  + "毫秒");    //第二种用法

    }
}

 

(3)启动测试类观察测试结果

四、基础模块AOP搭建(日志与统一异常处理)_第1张图片

 

2.全局AOP注解日志

使用AOP注解日志后的项目结构

四、基础模块AOP搭建(日志与统一异常处理)_第2张图片

 

(1)添加pom

parent的pom中的dependencyManagement添加

 
      com.alibaba
     fastjson
     1.2.4

子pom添加

 
     org.springframework.boot
     spring-boot-starter-aop


    com.alibaba
    fastjson

(2)定义注解

/**
 *自定义注解 拦截Controller
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemControllerLog {

	String description() default "";

	boolean async() default false;

}

 

/**
 *自定义注解 拦截service
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemServiceLog {

	String description() default "";

	boolean async() default false;

}

(3)定义一个类包含所有需要输出的字段


@Data
public class SystemLogStrategy implements Serializable {

	private boolean async;

	private String threadId;

	private String location;

	private String description;

	private String className;

	private String methodName;

	private String arguments;

	private String result;

	private Long elapsedTime;


	public String format() {
		return "线程ID: {}, 注解位置: {}, 方法描述: {}, 目标类名: {}, 目标方法: {}, 调用参数: {}, 返回结果: {}, 花费时间: {}";
	}

	public Object[] args() {
		return new Object[]{this.threadId, this.location, this.description, this.className, this.methodName, this.arguments, this.result, this.elapsedTime};
	}

}

(4)定义切面

/**
 * 切点类
 * @author zhangsan
 * @since 2019-04-05 
 * @version 1.0
 */
@Aspect
@Component
public class SystemLogAspect {

	private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

	@Pointcut("execution(* com.tmall..*(..)) && !execution(* com.tmall.infrastructure.logging..*(..))")
	public void pointcut() {

	}

	@Around("pointcut()")
	public Object doInvoke(ProceedingJoinPoint pjp) {
		long start = System.currentTimeMillis();

		Object result = null;

		try {
			result = pjp.proceed();
		} catch (Throwable throwable) {
			throwable.printStackTrace();
			LOG.error(throwable.getMessage(), throwable);
			throw new RuntimeException(throwable);
		} finally {
			long end = System.currentTimeMillis();
			long elapsedTime = end - start;

			printLog(pjp, result, elapsedTime);

		}

		return result;
	}

	/**
	 * 打印日志
	 * @param pjp   连接点
	 * @param result    方法调用返回结果
	 * @param elapsedTime   方法调用花费时间
	 */
	private void printLog(ProceedingJoinPoint pjp, Object result, long elapsedTime) {
		SystemLogStrategy strategy = getFocus(pjp);

		if (null != strategy) {
			strategy.setThreadId(ThreadUtils.getThreadId());
			strategy.setResult(JsonUtil.toJSONString(result));
			strategy.setElapsedTime(elapsedTime);
			if (strategy.isAsync()) {
				new Thread(()->LOG.info(strategy.format(), strategy.args())).start();
			}else {
				LOG.info(strategy.format(), strategy.args());
			}
		}
	}

	/**
	 * 获取注解
	 */
	private SystemLogStrategy getFocus(ProceedingJoinPoint pjp) {
		Signature signature = pjp.getSignature();
		String className = signature.getDeclaringTypeName();
		String methodName = signature.getName();
		Object[] args = pjp.getArgs();
		String targetClassName = pjp.getTarget().getClass().getName();
		try {
			Class clazz = Class.forName(targetClassName);
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				if (methodName.equals(method.getName())) {
					if (args.length == method.getParameterCount()) {

						SystemLogStrategy strategy = new SystemLogStrategy();
						strategy.setClassName(className);
						strategy.setMethodName(methodName);

						SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
						if (null != systemControllerLog) {
							strategy.setArguments(JsonUtil.toJSONString(args));
							strategy.setDescription(systemControllerLog.description());
							strategy.setAsync(systemControllerLog.async());
							strategy.setLocation(AnnotationTypeEnum.CONTROLLER.getName());
							return strategy;
						}
						SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog.class);
						if (null != systemServiceLog) {
							strategy.setArguments(JsonUtil.toJSONString(args));
							strategy.setDescription(systemServiceLog.description());
							strategy.setAsync(systemServiceLog.async());
							strategy.setLocation(AnnotationTypeEnum.SERVICE.getName());
							return strategy;
						}

						return null;
					}
				}
			}
		} catch (ClassNotFoundException e) {
			LOG.error(e.getMessage(), e);
		}
		return null;
	}

}

(5)切面涉及的工具类

public class ThreadUtils {

	private static final ThreadLocal threadLocal = new ThreadLocal<>();

	public static String getThreadId() {
		String threadId = threadLocal.get();
		if (null == threadId) {
			threadId = UUID.randomUUID().toString();
			threadLocal.set(threadId);
		}
		return threadId;
	}

}
public class JsonUtil {

	public static String toJSONString(Object object) {
		return JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
	}

}

 

(6)测试AOP注解日志是否集成成功

四、基础模块AOP搭建(日志与统一异常处理)_第3张图片

启动Spring Boot后访问这两个请求无注解请求没有日志输出,有注解的请求显示结果为

15:16:42.498 [http-nio-8082-exec-1] INFO  com.tmall.infrastructure.logging.SystemLogAspect - 线程ID: c108cde7-33e1-44ab-a5d0-5d7b8297779f, 注解位置: SystemControllerLog, 方法描述: test, 目标类名: com.tmall.ui.controller.Demo, 目标方法: index, 调用参数: [{"name":"FreeMarker 模版引擎 "}], 返回结果: "test", 花费时间: 1

 

(7)多环境配置文件

①将原log4j2.xml更名为log4j2-dev.xml

②编写不同环境配置文件

#application-dev.properties
logging.config=classpath:log4j2-dev.xml

#application-prod.properties
logging.config=classpath:logback-prod.xml

 

3.静态日志方法

public class LogUtils {

	//增加日志连接符
	private static void addLogItem(StringBuilder sb, String key, String value) {
		sb.append(key);
		sb.append("=");
		sb.append("\"");
		sb.append(value);
		sb.append("\"");
		sb.append(" ");
	}

	//用于组装日志信息
	private static String getBatchLogMessage(String logLevel, String className, String methodName,String resultString) {

		StringBuilder sb = new StringBuilder();  //用于存放一条日志信息
		Map logItemMap = new LinkedHashMap(); //用于存一条日志信息key-value的映射
		logItemMap.put("time",""+new Timestamp(System.currentTimeMillis()));

		logItemMap.put("priority", logLevel);
		logItemMap.put("class", className);
		logItemMap.put("method", methodName);
		logItemMap.put("result_string", resultString);



		for (String key : logItemMap.keySet()) {
			LogUtils.addLogItem(sb, key, logItemMap.get(key));
		}
		return sb.toString();
	}

	//用于组装输出日志的信息
	public static void writeLog(Logger logger, String logLevel, String log) {
		if ("INFO".equals(logLevel)) {
			logger.info(log);
		}
		else if ("ERROR".equals(logLevel)) {
			logger.error(log);
		}
		else if ("WARN".equals(logLevel)) {
			logger.warn(log);
		}
		else if ("DEBUG".equals(logLevel)) {
			logger.debug(log);
		}
	}


	public static void writeExceptionLog(Logger logger, Class clazz, String methodName, Exception e) {
		String log = getBatchLogMessage("ERROR", clazz.getName(), methodName,e.getMessage());
		writeLog(logger, "ERROR", log);
	}

	public static void writeInfoLog(Logger logger, Class clazz, String methodName, String infoMessage) {
		String log = getBatchLogMessage("INFO", clazz.getName(), methodName, infoMessage);
		writeLog(logger, "INFO", log);
	}

	public static void writeDebugLog(Logger logger, Class clazz, String methodName, String infoMessage) {
		String log = getBatchLogMessage("DEBUG", clazz.getName(), methodName, infoMessage);
		writeLog(logger, "DEBUG", log);
	}

	public static void writeWarnLog(Logger logger, Class clazz, String methodName, String warnMessage) {
		String log = getBatchLogMessage("WARN", clazz.getName(), methodName, warnMessage);
		writeLog(logger, "WARN", log);
	}

	public static void writeErrorLog(Logger logger, Class clazz, String methodName, String errorMessage) {
		String log = getBatchLogMessage("ERROR", clazz.getName(), methodName, errorMessage);
		writeLog(logger, "ERROR", log);
	}
	
}

 

/**
 * 用于LOG的显示。
 *
 */
public class LogConstants {
	public static final String LEVEL_INFO = "INFO";

	public static final String LEVEL_WARN = "WARN";

	public static final String LEVEL_ERROR = "ERROR";

	public static final String LEVEL_DEBUG = "DEBUG";
}

 

二、统一异常处理

1.建立返回值规范类

@Data 
@NoArgsConstructor 
public class ResultBean implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final int NO_LOGIN = -1;
    public static final int SUCCESS = 1;
    public static final int FAIL = 0;
    public static final int NO_PERMISSION = 2;
    public static final int USERNAME_EXIST = -909;
   private String msg = "success";
   private int code = SUCCESS;
   private T data;
   public ResultBean(T data) {
        super();
         this.data = data;
     }
   public ResultBean(Throwable e) {
         super();
         this.msg = e.toString();
         this.code = FAIL;
     }
   
   public ResultBean setMsg(String msg) {
        this.msg = msg;
        return this;
     }
     
    public ResultBean setCode(int code) {
        this.code = code;
        return this;
     }
   
   public ResultBean setData(T data) {
        this.data = data;
        return this;
    }
}
@Getter
public class PageResultBean extends ResultBean implements Serializable {
     // 总记录数
     private long totalRecord;
     //总页数
     private int pageCount;
     //当前页码
     private int pageNo;
     //当前页的记录数量
     private int pageSize;
     public PageResultBean(PageInfo pageInfo) {
         super.setData((T) pageInfo.getList());
         this.setPageNo(pageInfo.getPageNum())
                 .setPageSize(pageInfo.getPageSize())
                 .setPageCount(pageInfo.getPages())
                 .setTotalRecord(pageInfo.getTotal());
     }
     
    public PageResultBean setTotalRecord(long totalRecord) {
        this.totalRecord = totalRecord;
        return this;
     }
     
    public PageResultBean setPageCount(int pageCount) {
        this.pageCount = pageCount;
         return this;
     }
     
     public PageResultBean setPageNo(int pageNo) {
         this.pageNo = pageNo;
         return this;
     }
     
     public PageResultBean setPageSize(int pageSize) {
         this.pageSize = pageSize;
         return this;
     }
     
     @Override
     public String toString() {
         return "PageResultBean{" +
                 "totalRecord=" + totalRecord +
                 ", pageCount=" + pageCount +
                 ", pageNo=" + pageNo +
                 ", pageSize=" + pageSize +
                 '}';
     }
     
     @Override
     public PageResultBean setMsg(String msg) {
         super.setMsg(msg);
         return this;
     }
     @Override
     public PageResultBean setCode(int code) {
         super.setCode(code);
         return this;
    }
    
     @Override
     public PageResultBean setData(T data) {
         super.setData(data);
         return this;
     }
 }

 

2.逻辑执行统一异常处理

(1)创建自定义异常

根据需求创建一定数量的自定义异常

public class QueryException extends RuntimeException{

	// 错误码
	private Integer code;

	public QueryException(String message){
		super(message);
	}

	public QueryException(Integer code, String message){
		super(message);
		this.code = code;
	}


	public Integer getCode(){
		return code;
	}

	public void setCode(Integer code){
		this.code = code;
	}
}

(2)创建结果枚举信息类

该类用于定义结果的返回代码、异常处理种类信息以及提示级错误的代码和错误信息。

public enum ResultEnum{
	SUCCESS(200,"成功"),

	/**系统异常. ErrorCode : -1  */
	SYSTEM_EXCEPTION(-1,"系统异常"),

	/**未知异常. ErrorCode : 01*/
	UN_KNOWN_EXCEPTION(01,"未知异常"),

	/** 服务异常. ErrorCode : 02 */
	SERVICE_EXCEPTION(02,"服务异常"),

	/**查询异常. ErrorCode : 03*/
	QUERY_EXCEPTION(03,"查询异常"),

	/**数据库操作异常. ErrorCode : 05 */
	DB_EXCEPTION(05,"数据库操作异常"),

	/**参数验证错误. ErrorCode : 06*/
	PARAM_EXCEPTION(06,"参数验证错误"),

	/**权限验证错误. ErrorCode : 101*/
	NO_LOGIN(101,"未登录"),

	/**权限验证错误. ErrorCode : 101*/
	NO_PERMISSION(102,"无权限"),

	USERNAME_EXIST(103,"用户已存在");

	private Integer code;

	private String msg;

	ResultEnum(Integer code,String msg){
		this.code = code;
		this.msg = msg;
	}

	public Integer getCode(){
		return code;
	}

	public String getMsg(){
		return msg;
	}
}

 

(3)创建结果组装器

public class ResultAssembler {

	/** 操作成功的处理流程 */
	public static ResultBean getSuccess(Object object){
		ResultBean ResultBean = new ResultBean();
		//设置操作成功的返回码
		ResultBean.setCode(ResultConstants.RESULT_CODE_SUCCESS);
		//设置操作成功的消息
		ResultBean.setMsg(ResultConstants.RESULT_MESSAGE_SUCCESS);
		ResultBean.setData(object);
		return ResultBean;
	}

	/** 重载返回成功的方法,因为有时候我们不需要任何的消息数据被返回*/
	public static ResultBean getSuccess(){
		return getSuccess(null);
	}

	/**
	 * 操作失败的处理流程
	 * @param code 错误码
	 * @param msg 错误消息
	 * @param o   错误数据
	 * @return
	 */
	public static ResultBean getError(Integer code, String msg, Object o){
		ResultBean ResultBean = new ResultBean();
		ResultBean.setCode(code);
		ResultBean.setMsg(msg);
		ResultBean.setData(o);
		return ResultBean;
	}

	/**
	 * 重载,操作失败的方法(因为操作失败一般都不需要返回数据内容)
	 * @param code
	 * @param msg
	 * @return
	 */
	public static ResultBean getError(Integer code,String msg){
		return getError(code, msg, null);
	}
}

 

(4)定义异常统一处理类

@ControllerAdvice
public class ExceptionHandle {

	//增加异常日志打印   
	private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);

	//设置异常错误的页面
	public static final String DEFAULT_ERROR_VIEW_404 = "error404";

	public static final String DEFAULT_ERROR_VIEW_500 = "error500";

	@ExceptionHandler(value = QueryException.class)
	@ResponseBody
	public Object QueryExceptionHandler(HttpServletRequest req, QueryException e) throws Exception {
		logger.error("【查询异常】={}",e);
		return ResultAssembler.getError(ResultEnum.QUERY_EXCEPTION.getCode(),ResultEnum.QUERY_EXCEPTION.getMsg());
	}

	@ExceptionHandler(value = ServiceException.class)
	@ResponseBody
	public Object ServiceExceptionHandler(HttpServletRequest req, ServiceException e) throws Exception {
		logger.error("【服务异常】={}",e);
		return ResultAssembler.getError(ResultEnum.SERVICE_EXCEPTION.getCode(),ResultEnum.SERVICE_EXCEPTION.getMsg());
	}

	@ExceptionHandler(value = DBException.class)
	@ResponseBody
	public Object DBExceptionHandler(HttpServletRequest req, DBException e) throws Exception {
		logger.error("【数据库异常】={}",e);
		return ResultAssembler.getError(ResultEnum.DB_EXCEPTION.getCode(),ResultEnum.DB_EXCEPTION.getMsg());
	}

	@ExceptionHandler(value = UnKnownException.class)
	@ResponseBody
	public Object UnKnownExceptionHandler(HttpServletRequest req, UnKnownException e) throws Exception {
		logger.error("【未知异常】={}",e);
		return ResultAssembler.getError(ResultEnum.UN_KNOWN_EXCEPTION.getCode(),ResultEnum.UN_KNOWN_EXCEPTION.getMsg());
	}
}

(5)最终代码结构图

四、基础模块AOP搭建(日志与统一异常处理)_第4张图片

 

(6)测试统一异常处理

四、基础模块AOP搭建(日志与统一异常处理)_第5张图片

 

四、基础模块AOP搭建(日志与统一异常处理)_第6张图片

 

3.404统一异常处理

(1)建立404异常处理类

@Controller
public class NoFoundExceptionHandler implements ErrorController {
	@Override
	public String getErrorPath() {
		return "/error";
	}

	@RequestMapping(value = "/error")
	public String error(HttpServletResponse resp, HttpServletRequest req) {
		// 错误处理逻辑
		return "404error";
	}
}

(2)定义404error.html页面




    
    404error




(3)测试

四、基础模块AOP搭建(日志与统一异常处理)_第7张图片

你可能感兴趣的:(项目实战,java,mysql)