统一处理异常
@ControllerAdvice
用于修饰类,表示该类是Controller的全局配置类。
在此类中,可以对Controller进行如下三种全局配置:
异常处理方案、绑定数据方案、绑定参数方案。
@ExceptionHandler—异常处理方案
用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常。
@ModelAttribute—绑定数据方案(想象下Page类被自动封装进Model里)
用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数。
@DataBinder—绑定参数方案(想象下Page类的使用)
用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。
其中,数据层和业务层产生异常时,均会向上抛出异常,最终都会在表现层被统一处理。
SpringBoot自动处理方式
1.把报错的错误码作为页面名放到如下目录下[templates],当报出来相关错误会自动显示报错的页面。
@ControllerAdvice和@ExceptionHandler处理异常
1.把报错的错误码作为页面名放到如下目录下[templates]
2.写一个跳转到处理页面的controller,这里在HomeController里写
@RequestMapping(path = "/error",method = RequestMethod.GET)
public String getErrorPage(){
return "/error/500";
}
3.在controller包下新建advice包并创建处理异常类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@ControllerAdvice(annotations = Controller.class) //表示只处理带有Controller注解的类
public class ExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(HandleException.class);
/**
* 处理所有的异常
* @param e 异常
* @param request 用于判断请求是请求页面还是json字符串
* @param response 用于向浏览器返回页面或json字符串
*/
@ExceptionHandler({Exception.class})
public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
//1.首先确定服务器发送异常
logger.error("服务器发送异常"+e.getMessage());
//2.获取异常,将每条异常进行遍历
StackTraceElement[] elements = e.getStackTrace();
for (StackTraceElement element : elements) {
logger.error(element.toString());
}
//3.判断请求是普通请求,还是异步请求
String header = request.getHeader("x-requested-with"); // 获取请求头的请求方式
if (header.equals("XMLHttpRequest")){
//3.1请求是异步请求
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(1,"服务器异常"));
}else {
//3.1请求是普通请求
response.sendRedirect(request.getContextPath() + "/error");
}
}
}
统一记录日志
需求:对所有的service记录日志
AOP
AOP的概念
- Aspect Oriented Programing,即面向方面(切面)编程。
- AOP是一种编程思想,是对OOP的补充,可以进一步提高编程的效率。
Target:已处理完业务逻辑的代码为目标对象
Joinpoint:目标对象上有很多地方能被织入代码叫连接点
Pointcut:切点声明到底织入到哪些位置
Advice:通知声明到底要处理什么样的逻辑
AOP的实现
AspectJ
AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。
AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。
功能强大。但是需要学习新的语言,使用较复杂。
Spring AOP
Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点。
Spring支持对AspectJ的集成。
Spring AOP
JDK动态代理
- Java提供的动态代理技术,可以在运行时创建接口的代理实例。
- Spring AOP默认采用此种方式,在接口的代理实例中织入代码。
CGLib动态代理
- 采用底层的字节码技术,在运行时创建子类代理实例。
- 当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。
示例:
1.新建aspect包写一个AlphaAspect类
@Component
@Aspect
public class AlphaAspect {
// 切点:切点声明到底织入到哪些位置
// com.guo.community.service. 表示在service包下 .* 所有组件 .*所有方法 (..) 所有参数 所有返回值
@Pointcut("execution(* com.guo.community.service.*.*(..))")
//方法名字随便取
public void pointcut(){
}
/* @Before("pointcut()") // 在连接点开始前织入
public void before(){
System.out.println("before");
}
@After("pointcut()") // 在连接点后前织入
public void after(){
System.out.println("after");
}
@AfterReturning("pointcut()") // 在返回值后开始前织入
public void afterReturning(){
System.out.println("afterReturning");
}
@AfterThrowing("pointcut()") // 在异常时织入
public void afterThrowing(){
System.out.println("afterThrowing");
}*/
@Around("pointcut()") // 在连接点前后织入
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("aroundBefore");
Object obj = joinPoint.proceed();
System.out.println("aroundAfter");
return obj;
}
}
@Aspect:切面。表示一个横切进业务的一个对象。它里面包含切入点(Pointcut)和Advice(通知)。
@Pointcut:切入点。表示需要切入的位置,比如某些类或者某些方法,也就是先定一个范围。
@Before:Advice(通知)的一种,切入点的方法体执行之前执行。
@Around:Advice(通知)的一种,环绕切入点执行也就是把切入点包裹起来执行。
@After:Advice(通知)的一种,在切入点正常运行结束后执行。
@AfterReturning:Advice(通知)的一种,在切入点正常运行结束后执行,异常则不执行
@AfterThrowing:Advice(通知)的一种,在切入点运行异常时执行
统一日志:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestConditionHolder;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
//统一日志管理
@Component
@Aspect
public class ServiceLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
//切入点:service下的所有方法和属性
@Pointcut("execution(* com.guo.community.service.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint joinPoint){
//日志格式:
//用户[ip]在[**时间]访问了[**方法]
//1.获取用户的ip:通过request获取
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteHost();
//2.获取时间
String nowTime = new SimpleDateFormat("yyyy-MM-dd, HH-mm-ss").format(new Date());
//3.方法名称
String name = joinPoint.getSignature().getName();
//4.访问路径
String path = joinPoint.getSignature().getDeclaringTypeName();
//5.拼接
String target = name + "." + path;
//6.返回日志
logger.info(String.format("用户[%s]在[%s]访问了[%s]",ip,nowTime,target));
}
}