表现层在最外面,异常在这层处理。
SpringBoot处理异常的简单实现
把error文件夹放在templates文件夹下,html命名为状态吗:
修改404.html和500.html为模版(注意图片路径修改为动态)
更细粒度的处理异常的方式
-
@ControllerAdvice
- 用于修饰类,表示该类是Controller的全局配置类。
- 在此类中,可以对Controller进行如下三种全局配置:
异常处理方案、绑定数据方案、绑定参数方案。
-
@ExceptionHandler
- 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常。
-
@ModelAttribute
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数。
-
@DataBinder
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。
示例
- 在HomeController中添加如下方法:
@RequestMapping(path = "/error", method = RequestMethod.GET)
public String getErrorPage() {return "/error/500";
}
- 在controller包下新加一个包叫Advice,new一个class ExceptionAdvice
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {private static final Logger logger = getLogger(ExceptionAdvice.class);@ExceptionHandler({Exception.class})public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {logger.error("服务器发生异常:" + e.getMessage());for(StackTraceElement element : e.getStackTrace()) {logger.error(element.toString());}String xRequestedWith = request.getHeader("x-requested-with");if("XMLHttpRequest".equals(xRequestedWith)) {response.setContentType("application/plain;charset=utf-8");PrintWriter writer = response.getWriter();writer.write(CommunityUtil.getJsonString(1, "服务器异常"));} else {response.sendRedirect(request.getContextPath() + "/error");}}
}
- annotations = Controller.class是@ControllerAdvice注解的一个属性,它用于指定这个advice应该应用于哪些类。在您的代码中,annotations = Controller.class表示这个advice应该应用于所有带有@Controller注解的类。
- 。getLogger(ExceptionAdvice.class)是一个静态方法,它返回一个与指定类(在这里是ExceptionAdvice类)关联的Logger实例。(.class不要漏)
- 异步请求的request.getHeader(“x-requested-with”) 是XMLHttpRequest
- getWriter()是HttpServletResponse接口中的一个方法,它返回一个PrintWriter对象,可以用来向客户端发送字符文本。这个PrintWriter对象会自动使用响应的字符编码(通过setCharacterEncoding方法设置),所以你可以直接写入字符串,而不需要转换为字节。
AOP编程
- Aspect Oriented Programing, 即面向方面(切面)编程。
- AOP是一种编程思想,是对OOP的补充, 可以进一步提高编程的效率。
(相当于一刀切开,在每一层都用到)
- Target:在AOP编程中,目标(Target)是指被一个或多个切面(Aspect)所通知(advise)的对象。也就是说,目标是包含业务逻辑的类,这些类的某些方法将被切面中的通知所增强。
- Joinpoint:连接点(Joinpoint)是指在程序执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。在Spring AOP中,一个连接点总是表示一个方法的执行。
- Aspect:切面(Aspect)是一个模块,它包含一些通知和切入点。通知(Advice)是切面在特定的连接点(Joinpoint)上执行的代码,切入点(Pointcut)则定义了这些通知应该在何处执行。切面的作用是将通用的功能从业务逻辑中分离出来,以实现代码的重用和解耦。
- Weaving:织入(Weaving)是将切面插入到目标对象以创建一个被通知的对象的过程。这个过程可以在编译时(如AspectJ的编译器),加载时或运行时完成。Spring AOP默认在运行时进行织入。
- Pointcut:切入点(Pointcut)是指在哪些Joinpoint(连接点)上应用Advice(通知)。它是一个表达式,用于匹配方法执行的点。例如,你可以定义一个切入点来匹配所有在某个特定类中的方法,或者所有的setter方法,等等。在Spring AOP中,切入点表达式通常使用AspectJ的切入点表达式语言。
- Advice:通知(Advice)是切面(Aspect)在特定的连接点(Joinpoint)上执行的代码。它是切面的主要内容,定义了切面要完成的工作。在Spring AOP中,通知可以是以下五种类型之一:前置通知(Before),后置通知(After),返回通知(After-returning),异常通知(After-throwing)和环绕通知(Around)。前置通知在连接点之前执行,后置通知在连接点之后执行,返回通知在连接点正常返回后执行,异常通知在连接点抛出异常后执行,环绕通知可以在连接点前后都执行。
AOP的实现
AspectJ(学习代价高)
- AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。
- AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。
Spring AOP(常用性价比最高)
- Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
- Spring AOP在运行时通过代理的方式织入代码(记住!!!),只支持方法类型的连接点。
- Spring支持对AspectJ的集成。
Spring AOP
JDK动态代理(Spring默认,必须有接口)
- Java提供的动态代理技术,可以在运行时创建接口的代理实例。
- Spring AOP默认采用此种方式,在接口的代理实例中织入代码。
CGLib动态代理
- 采用底层的字节码技术,在运行时创建子类代理实例。
- 当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。
AOP示例
在Controller下创建一个新的包Aspect,创建类AlphaAspect:
@Component
@Aspect
public class AlphaAspect {//定义切点//service包下的所有方法、所有参数、所有返回值@Pointcut("execution(* com.newcoder.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("before obj");Object obj = joinPoint.proceed();System.out.println("after obj");return obj;}
}
- @Pointcut(“execution(* com.newcoder.community.service..(…))”)注解表示制定生效的路径,这里是service包下的所有方法、所有参数、所有返回值;
- @Before(“pointcut()”)在函数之前、@After(“pointcut()”)在函数之后、@AfterReturning(“pointcut()”)函数返回之后、@AfterThrowing(“pointcut()”)抛出异常之后;
- 注意@Around(“pointcut()”)实在执行之间,通过Object obj = joinPoint.proceed();这个就相当于执行业务组件了:
这段代码是一个环绕通知(Around Advice),它是AOP(面向切面编程)中的一种通知类型。环绕通知可以在方法调用前后都执行一些代码,并且可以决定是否执行目标方法。
@Around(“pointcut()”)注解表示这个通知应用于pointcut()定义的切入点。在这个例子中,pointcut()定义的切入点是com.newcoder.community.service包下的所有方法。 around方法的参数ProceedingJoinPoint joinPoint是一个特殊的JoinPoint,它代表了切入点,也就是要被通知的目标方法。
在around方法中,首先执行System.out.println(“before obj”);打印一条消息,然后调用joinPoint.proceed()执行目标方法,并将结果保存在obj变量中。**joinPoint.proceed()方法的调用是必须的,否则目标方法不会被执行。**然后执行System.out.println(“after obj”);打印一条消息,最后返回目标方法的结果。 所以,这个环绕通知在目标方法执行前后都打印了一条消息,并且返回了目标方法的结果。
运行后可以发现输出了很多:
统一记录日志
需求:对所有的组件及日志(这是系统需求与业务需求耦合这样不好)
解决:面向AOP切面编程。
创建ServiceLogApect.java:
@Component
@Aspect
public class ServiceLogAspect {private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);//定义切点//service包下的所有方法、所有参数、所有返回值@Pointcut("execution(* com.newcoder.community.service.*.*(..))")public void pointcut() {}@Before("pointcut()")public void before(JoinPoint joinPoint) {//用户[1.2.3.4],在[xxx]时间,访问了[com.newcoder.community.service.xxx()]。logger.debug("before");ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String ip = request.getRemoteHost();String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();logger.info(String.format("用户[%s], 在[%s], 访问了[%s]", ip, now, target));}
}
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes():得到当前hold的request的属性;
- String ip = request.getRemoteHost();得到IP
- String target = joinPoint.getSignature().getDeclaringTypeName() + “.” + joinPoint.getSignature().getName();得到切面切到的JointCut的签名
最后可以看到日志有这些输出: