在spring mvc项目中,使用@Aspect截取所有contoller中的exception,并且通过HttpServletResponse输出的一些问题

本文介绍如何在Spring MVC项目中实现全局异常捕获并自定义响应。通过使用AOP技术,确保所有Controller层异常被统一处理并通过HttpServletResponse返回定制化的错误信息。

需求业务场景:

在spring 4.3 的mvc项目中,需要把所有controller层中的exception全部捕获,把错误信息进行处理后,通过HttpServletResponse 输出到浏览器

出现的问题:

2个项目都需要完成这个场景,但是一个项目实现了。另外一个项目通过同样的配置,却无法实现。表现在aop虽然截获了所有的controller中的exception,通过aop中的@autowired注入的HttpServletResponse却无法替换controller中的输出。浏览器界面依然显示的是标准的web 500 出错界面。

解决过程:

通过2个项目比对,发现实现这个功能需要注意以下几点:
1. 对controller中所有的方法进行切面的定义后,如果只是用@AfterThrowing 来通知处理出错内容,并且需要用HttpServletResponse来代替controller输出到浏览器,需要对同样的切面,进行@Around通知处理。
2. @Around 通知,必须优先于 @AfterThrowing通知.
3. 在同一个@Aspect类中,无法做到第二点,所以需要拆分成2个@Aspect类,并且通过@Order来指定顺序。
相关文档可以查看:
http://sishuok.com/forum/blogPost/list/0/2474.html
4. 另外一个项目碰巧可以实现这个功能,是因为@Around通知所在的aop类的包名排名在@AfterThrowing通知所在的aop类的签名。应该是优先载入了,所以在没有显式指定的时候,恰好就完成了这个功能,并且没有错误。
5. 相关AOP代码如下代码:2个代码都是定义同样的切面(切入所有的controller中的方法)
a. 处理@Around 的AOP类代码

/**
 * @Title: SystemLogAspect.java
 * @Package com.ninelephas.meerkat.aspect
 * @Description: 系统日志的AOP类
 *               Copyright: Copyright (c) 2016
 *               Company:九象网络科技(上海)有限公司
 * 
 * @author "徐泽宇"
 * @date 2016年8月1日 下午5:25:15
 * @version V1.0.0
 */

package com.ninelephas.terrier.aspect.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 
 * @ClassName: ArroundControllerAspect
 * @Description: 处理所有controller类中方法的环绕通知,什么事情都不做,就是执行方法,为了和CatchControllerExceptionAspect配合
 *               切面定义和 CatchControllerExceptionAspect 一致,并且优先级要高于CatchControllerExceptionAspect
 * @author 徐泽宇
 * @date 2016年12月10日 上午3:11:39
 *
 */
@Aspect
@Order(1)
@Component("com.ninelephas.terrier.aspect.controller.ArroundControllerAspect")
public class ArroundControllerAspect {

    private static final Logger logger = LogManager.getLogger(ArroundControllerAspect.class);

    /**
     * 
     * runControllerMethod
     * 
     * @Auther 徐泽宇
     * @Date 2016年12月10日 上午3:15:59
     * @Title: logTheController
     * @Description: 定义一个切面,指向所有的controller类中的所有方法
     */
    @Pointcut("execution(* com.ninelephas.terrier.controller..*.*(..)) ")
    public void runControllerMethod() {
        // Nothing to clean up
    }



    /**
     * 
     * runControllerMethod
     * 
     * @Auther 徐泽宇
     * @Date 2016年12月10日 上午3:17:12
     * @Title: runControllerMethod
     * @Description: 什么都不处理,仅仅是增加一个环绕通知.
     * 这里必须返回一个object,把处理过程交回给controller。否则controller中的@ResponseBody不会起作用了
     * @param joinPoint
     */
    @Around("runControllerMethod()")
    private Object runControllerMethod(ProceedingJoinPoint joinPoint) {
        if (logger.isDebugEnabled()) {
            logger.debug("runControllerMethod(ProceedingJoinPoint joinPoint={}) - start", joinPoint); //$NON-NLS-1$
        }

        Object rtnObject = null;
        try {
            rtnObject = joinPoint.proceed();
        } catch (Throwable e) {
            logger.error(e);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("runControllerMethod(ProceedingJoinPoint joinPoint={}) - end", joinPoint); //$NON-NLS-1$
        }
        return rtnObject;
    }
}

b. 处理@AfterThrowing的AOP类代码(核心功能),处理controller中的exception,并且用HttpServletResponse 输出

package com.ninelephas.terrier.aspect.catchcontroller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @ClassName: CatchControllerExceptionAspect
 * @Description: 捕获所有Controller中的方法的Exception
 *               通过HttpServletResponse输出到浏览器
 * @author 徐泽宇
 * @date 2016年11月1日 下午10:05:29
 *
 */
@Aspect
@Order(2)
@Component("com.ninelephas.terrier.aspect.catchcontroller.CatchControllerExceptionAspect")
public class CatchControllerExceptionAspect {
    /**
     * Logger for this class
     */
    private static final Logger logger = LogManager.getLogger(CatchControllerExceptionAspect.class.getName());

    @Autowired
    private HttpServletResponse response;

    /**
     * catchController
     * 
     * @Auther 徐泽宇
     * @Date 2016年11月1日 下午10:08:50
     * @Title: catchControllerMethod
     * @Description: 切入所有controller中方法的切面
     */
    @Pointcut("execution(* com.ninelephas.terrier.controller..*.*(..)) ")
    public void catchControllerMethod() {
        // Nothing to clean up
    }

    /**
     * writeToHttpResponse
     * 
     * @throws IOException
     * 
     * @throws ServletException
     * 
     * @Auther 徐泽宇
     * @Date 2016年11月1日 下午10:11:24
     * @Title: writeToHttpResponse
     * @Description: 捕捉到切面产生的Exception后,写入HttpServletResponse
     */
    @AfterThrowing(throwing = "ex", pointcut = "catchControllerMethod()")
    private void writeToHttpResponse(Throwable ex) throws IOException {
        response.setStatus(500);
        response.getWriter().write(ex.getMessage());
        logger.debug("浏览器输出完成!");
        logger.error(ex, ex.fillInStackTrace());
    }

}
<think>嗯,用户现在想了解在Spring MVC或MyBatis框架中,Controller、Service、Mapper的分层概念,特别是Service属于哪一层级。我需要结合之前的回答和用户提供的引用内容来详细解答。 首先,回顾之前的回答,应用层和服务层的区别中,服务层属于业务逻辑的实现部分,而应用层负责流程编排。现在用户的问题更具体到Spring MVC或MyBatis的分层,需要明确Service在这些框架中的位置。 用户提供的引用里提到,Service层使用Spring的注解,如@Service,处理业务逻辑和事务管理,比如引用[1]中的UserService例子。引用[2]说明在分层架构中,Controller调用Service,Service调用Mapper,用Entity传递数据。引用[3]和[5]的代码示例也展示了Controller如何调用Service,Service调用Mapper,这符合典型的三层架构。 需要明确的是,在Spring MVC中,分层通常是Controller(表示层)、Service(业务层)、Mapper/DAO(数据访问层)。而MyBatis主要处理数据访问,所以Mapper属于数据访问层。Service层在这里属于业务层,负责处理业务逻辑,调用Mapper进行数据操作,同时处理事务,如引用[4]提到的。 用户可能混淆了服务层在不同架构中的位置,比如在DDD中服务层可能属于领域层,但在传统的三层架构中,Service就是业务层。需要将之前的应用层和服务层的对比与具体框架中的分层结合起来,说明在Spring MVC里,Service属于业务层,位于Controller和Mapper之间,起承上启下的作用。 还要注意用户提到的“Service在Controller、Service、Mapper架构中的具体层次定位”,可能需要解释各层的职责和调用关系,强调Service层的核心地位,处理业务逻辑,确保事务管理,并协调数据访问层和表示层之间的交互。 需要引用用户提供的引用内容来支持回答,例如引用[2]中的流程,引用[4]中的事务管理,引用[5]中的代码示例。同时要指出Service层如何解耦Controller和Mapper,提高代码复用性和维护性,如引用[2]提到的分层解耦的目的。 总结:在Spring MVC或MyBatis架构中,Service层属于业务逻辑层,位于Controller层和Mapper层之间,负责处理业务逻辑、事务管理,并协调数据访问。</think>### Service层在Spring MVC/MyBatis分层架构中的定位 在Spring MVC或MyBatis框架的典型分层设计中,**Service层**是**业务逻辑层**的核心组成部分,位于Controller层与Mapper/DAO层之间,承担业务逻辑的封装与协调职责。以下是具体分层定位分析: --- #### 1. **分层架构的核心层级** 在Spring MVCSpring Boot项目中,标准分层结构通常为: $$ \text{Controller层(表示层)} \rightarrow \text{Service层(业务逻辑层)} \rightarrow \text{Mapper/DAO层(数据访问层)} $$ - **Controller层**: - 职责:接收HTTP请求,解析参数,调用Service层方法,返回响应(如JSON)[^3][^5]。 - 技术依赖:Spring MVC注解(`@RestController`、`@GetMapping`等)。 - 示例:用户注册请求由`UserController`接收并调用`UserService`[^5]。 - **Service层**: - 职责: - **业务逻辑实现**:例如订单创建时的库存校验、支付状态更新[^1][^4]。 - **事务管理**:通过`@Transactional`注解保证数据一致性(如多表操作的原子性)[^4]。 - **协调Mapper/DAO层**:调用Mapper接口操作数据库,组合多个Mapper方法完成复杂业务[^2][^4]。 - 技术依赖:Spring的`@Service`注解、事务管理器[^1][^4]。 - 示例:`UserService`调用`UserMapper`查询用户信息,并处理密码加密逻辑[^1][^4]。 - **Mapper/DAO层**: - 职责:直接操作数据库,执行SQL语句(通过MyBatis的XML或注解实现)[^2][^4]。 - 技术依赖:MyBatis的`@Mapper`接口、SQL映射文件。 - 示例:`UserMapper`定义`SELECT * FROM user WHERE id = #{id}`的SQL查询[^1][^2]。 --- #### 2. **Service层的核心作用** - **业务逻辑封装**: - 将分散的Mapper操作整合为完整业务单元。例如,用户注册需同时插入用户表、发送验证邮件,这一流程由Service层编排[^4][^5]。 - **事务控制**: - 通过`@Transactional`注解管理事务边界,确保多个Mapper操作要么全部成功,要么全部回滚[^1][^4]。 - **解耦与复用**: - 隔离Controller层与数据访问细节,使Controller仅关注参数解析与响应封装[^2][^3]。 - 提供可复用的业务能力,例如支付服务可被多个Controller调用[^1][^5]。 --- #### 3. **Service层在分层中的依赖关系** - **向上依赖**: - 仅被Controller层调用,接收DTO(数据传输对象)或Entity(实体类)参数[^2][^5]。 - **向下依赖**: - 调用Mapper/DAO层接口操作数据库,依赖Entity类传递数据[^2]。 - **横向扩展**: - 可调用其他Service层方法(如`OrderService`调用`PaymentService`),但需避免循环依赖[^4]。 --- #### 4. **代码示例与流程** 以用户查询为例: 1. **Controller层**接收请求: ```java @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ResponseEntity<UserDTO> getUser(@PathVariable String id) { UserDTO user = userService.getUserById(id); return ResponseEntity.ok(user); } } ``` 2. **Service层**处理业务逻辑: ```java @Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(readOnly = true) public UserDTO getUserById(String id) { UserEntity entity = userMapper.selectById(id); return convertToDTO(entity); // 实体转DTO } } ``` 3. **Mapper层**执行SQL: ```java @Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") UserEntity selectById(String id); } ``` --- #### 5. **与其他架构模式的对比** - **传统三层架构**: - Service层对应**业务逻辑层**,与Controller(表示层)、Mapper(数据访问层)严格分层[^2][^4]。 - **DDD分层架构**: - Service层可能属于**领域层**,直接处理领域模型的核心逻辑,而非单纯协调Mapper[^4]。 - **微服务架构**: - Service层可独立为微服务,通过REST或RPC对外暴露接口[^5]。 --- ### 总结 在Spring MVC/MyBatis架构中,**Service层是业务逻辑的核心载体**,定位如下: 1. **层级位置**:介于Controller层与Mapper层之间。 2. **核心职责**:业务逻辑实现、事务管理、数据访问协调。 3. **技术特性**:通过`@Service`和`@Transactional`实现业务封装与事务控制[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值