Spring MVC(spring-webmvc)之全局数据处理、拦截器、自定义类型转换器等使用指南

简介: Spring MVC(spring-webmvc)之全局数据处理、拦截器、自定义类型转换器等使用指南

Spring MVC 的全局数据处理(@ControllerAdvice)

@ControllerAdvice:定义 Controller 层全局数据处理类。作用在注解了 @RequestMapping 的控制器方法上

包含注解 @Component,可以被扫描到

一般和以下注解搭配使用

  • @ExceptionHandler(异常处理)
  • @ModelAttribute(数据绑定)
  • @InitBinder(数据预处理)
  • 注意:这三个注解都可以在普通的 Controller 类上使用,ControllerAdvice 只是作用范围可以自定义(默认全部)

@ControllerAdvice 支持属性:

  • value / basePackages 属性: 数组类型,指定一个或多个包,用来指定可以作用的基包

    即将对指定的包下面的 Controller 及其子包下面的 Controller 起作用

    若不指定,默认所有被扫描的包

  • basePackageClasses 属性: 是 basePackages 的一种变形, 数组类型,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被该 @ControllerAdvice 管理
  • assignableTypes 属性:数组类型,用来指定具体的 Controller 类型,它可以是一个共同的接口或父类等
  • annotations 属性: 指定一个或多个注解,被这些注解所标记的 Controller 会被该 @ControllerAdvice 管理


异常处理(@ExceptionHandler)

对于异常的处理一般有两种方式:

  • 一种是当前方法处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合。
  • 一种是当前方法不处理,出现异常后直接抛给调用者处理。

    使用 Spring 框架,代码最终是由框架来调用的,即异常最终会抛到框架,然后由框架指定异常处理器来统一处理异常。


方式1:通过@ControllerAdvice 和 @ExceptionHandler 注解定义全局异常处理

使用方式:

  • @ControllerAdvice:定义全局处理类,异常监听。标准在 java 类上
  • 定义一个异常处理方法

    • 返回值String (视图)
    • 参数:Model
    • @ExceptionHandler 注解:标注在方法上

      • value 属性:指定当前异常处理器处理的异常类型
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 定义全局异常处理
 */
@ControllerAdvice
public class MyHandlerException2 {
    
    // @Validated参数校验 ,解析BindingResult的错误信息并返回
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public JsonResult exceptionHandler(BindException e, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        String collect = fieldErrors.stream()
            .map(f -> f.getField()+":"+f.getDefaultMessage())
            .collect(Collectors.joining(","));
        return new JsonResult(JsonResult.Validated_ERROR, collect);
    }

    @ExceptionHandler(value=Exception.class)
    public String handlerException(Model model) {
        model.addAttribute("errorMsg", "运行报错!");
        return "forward:error.jsp";
    }
}


方式2:自定义异常处理器

自定义一个类实现HandlerExceptionResolver接口,重写resolveException方法,将异常处理器交给spring容器管理即可

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义异常处理器
 * 跳转到一个美化的页面,携带错误信息
 */
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg","运行报错!");
        mv.setViewName("forward:error.jsp");
        return mv;
    }
}


方式3:使用web提供的异常机制

在 web.xml 中提供异常处理配置

<!--处理403异常-->
<error-page>
    <error-code>403</error-code>
    <location>/403.jsp</location>
</error-page>
<!--处理404异常-->
<error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>


数据绑定(@ModelAttribute)

全局数据绑定功能可以用来做一些初始化的数据操作,例如可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问到这些数据。

@ModelAttribute 注解标注的方法会在执行目标 Controller 方法之前执行,常与 @ControllerAdvice 配合使用,可以让全局的@RequestMapping 都能获得在此处设置的键值对

@ModelAttribute 注解标注的方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的数据类型,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。

    // 无返回值方法,不指定@ModelAttribute的name属性
    // 键值对直接放入Model中,可以自定义key,value
    @ModelAttribute()
    public void presetParam(Model model) {
        model.addAttribute("globalAttr", "我是全局参数");
    }

    // 有返回值方法,不指定 @ModelAttribute 的 name 属性
    // 返回值是Map类型,绑定的全局数据键值对的 key 就是 "map",value 为方法的返回值
    @ModelAttribute()
    public Map<String, String> presetParam2() {
        Map<String, String> map1 = new HashMap<String, String>();
        map1.put("key1", "value1");
        return map1;
    }

    // 有返回值方法,指定 @ModelAttribute 的 name 属性
    // 绑定的全局数据键值对的 key 就是 name 属性的值,value 为方法的返回值
    @ModelAttribute(name = "map2")
    public Map<String, String> presetParam3() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("key2", "value2");
        return map;
    }

    // 接受请求参数
    @ModelAttribute()
    public void presetParam4(@RequestParam("name") String name,Model model) {
        model.addAttribute("name", name);
    }

使用

     //1.使用Model取出
    @GetMapping("model")
    public String methodOne(Model model) {
        Map<String, Object> modelMap = model.asMap();
        System.out.println(modelMap.get("name").toString()); // 传入name的值    
        return modelMap.get("globalAttr").toString();
    }

    //2.使用ModelMap取出
    @GetMapping("modelMap")
    public String methodThree(ModelMap modelMap) {
        return modelMap.get("map").toString();
    }

    //3.@ModelAttribute()指定key,直接取出
    @GetMapping("modelAttribute")
    public String methodTwo(@ModelAttribute("map2") Map map2) {
        return map2.toString();
    }


数据预处理(@InitBinder)

参考:https://blog.csdn.net/wang0907/article/details/108357696

@InitBinder 注解:请求数据预处理,用来设置 WebDataBinder,用于自动绑定前端请求参数到 Model 中

WebDataBinder 中的常用方法有

// 设置字段默认前缀,可用于绑定同属性多对象
public void setFieldDefaultPrefix(String fieldDefaultPrefix)

// 设置无法被接受的字段
public void setDisallowedFields(String... disallowedFields)
    
// 注册自定义编辑器
public void registerCustomEditor(Class<?> var1, PropertyEditor var2)

// 注册校验器
public void addValidators(Validator... validators)


绑定同属性多对象

public class TestController{
    @RequestMapping("/test")
    public String test(User user, Person person){
        System.out.println(user, person);
        return "success"
    }
    
    @InitBinder("user")
    public void initBinderUser(WebDataBinder dataBinder){
        dataBinder.setFieldDefaultPrefix("u.");
    }

    @InitBinder("person")
    public void initBinderPerson(WebDataBinder dataBinder){
        dataBinder.setFieldDefaultPrefix("p.");
    }
}

测试url:http://localhost:8080/test?u.name=aaa&p.name=bbb


注册属性编辑器

在接收参数的时候,对于基础的数据类型,比如接收 String,Int 等类型,SpringMVC 是可以直接处理的,但是对于其他复杂的对象类型,有时候是无法处理的,这时候就需要属性编辑器来进行处理(源数据为String),过程一般就是 String -> 属性编辑器 -> 目标类型。

Spring 提供了一些默认的属性编辑器,例如 org.springframework.beans.propertyeditors.CustomDateEditor,也可以通过继承java.beans.PropertyEditorSuppotr 来根据具体的业务来自定义属性编辑器。

  • 使用默认的属性编辑器

        @InitBinder
        public void initBinder(WebDataBinder dataBinder){
            /*
             * 创建一个字符串微调编辑器
             * 参数{boolean emptyAsNull}: 是否把空字符串("")视为 null
             */
            StringTrimmerEditor trimmerEditor = new StringTrimmerEditor(true);
            /*
             * 注册自定义编辑器
             * 接受两个参数{Class<?> requiredType, PropertyEditor propertyEditor}
             * requiredType:所需处理的类型
             * propertyEditor:属性编辑器,StringTrimmerEditor就是 propertyEditor的一个子类
             */
            dataBinder.registerCustomEditor(String.class, trimmerEditor);
            //日期格式的字符串转换成Date对象
            dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), false));
        }
  • 自定义属性编辑器

        /**
         * @description:  防止xss注入
         * @params:  String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
         */
        @InitBinder
        protected void initBinder2(WebDataBinder binder) {
    
            System.out.println("22222222222222");
    
            //匿名类,自定义属性编辑器 PropertyEditorSupport
            binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
                @Override
                public void setAsText(String text) {
                    setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
                }
                @Override
                public String getAsText() {
                    Object value = getValue();
                    return value != null ? value.toString() : "";
                }
            });
        }


注册校验器

  • 自定义校验器

    直接实现 org.springframework.validation.Validator,该接口只有两个方法:

    • 一个是校验是否支持校验的 support(Class<?> clazz) 方法
    • 一个是进行具体校验的 validate(Object target, Errors errors) 方法
    /**
     * 定义一个校验器
     * 该校验器校验用户录入的userName长度是否大于8,并给出响应的错误信息,
     * 错误信息直接设置到errors中,最终会设置到org.springframework.validation.BindingReuslt,
     * 在接口中直接定义该对象则会自动注入对象值,从而可以获取到对应的错误信息。
     */
    @Component
    public class UserValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> clazz) {
            // 只支持User类型对象的校验
            return User.class.equals(clazz);
        }
    
        @Override
        public void validate(Object target, Errors errors) {
            User user = (User) target;
            String userName = user.getUserName();
            if (StringUtils.isEmpty(userName) || userName.length() < 8) {
                errors.rejectValue("userName", "valid.userNameLen",
                        new Object[] { "minLength", 8 }, "用户名不能少于{1}位");
            }
        }
    }
        @Autowired
        private UserValidator userValidator;
    
        // 注册校验器
        @InitBinder
        private void initBinder(WebDataBinder binder) {
            binder.addValidators(userValidator);
        }


Spring MVC 拦截器

拦截器是 Spring MVC 提供的一种技术, 它的功能似于过滤器 Filter,它可以在请求进入 Controller 之前,离开 Controller 之后以及页面渲染完毕之后进行拦截

image-20200728084404447.png

拦截器和过滤器的区别

  • 过滤器是 Servlet 规范中的一部分,任何 java web 工程都可以使用

    拦截器是 Spring MVC 框架的,只有使用了 Spring MVC 框架的工程才能用

  • 过滤器在 url-pattern 中配置了 /* 之后,可以对所有要访问的资源拦截

    拦截器只会拦截访问的控制器方法,如果访问的是 jsp,js,html,css,image 等资源,它是不拦截的


方式1:实现接口 HnadlerInterceptor(推荐)

  • 实现 HnadlerInterceptor 接口
  • 实现其中的三个方法(三个拦截点)

    // 进入controller方法之前执行的内容(对请求拦截处理)
        // 返回值 boolean。true :放行(继续向后执行,进入到controller),false :拦截过滤
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    
    // 执行了controller方法之后,执行的内容(对象响应进行拦截处理)
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView)
    
    // 页面渲染完成之后,执行(一般不用)
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex)


基于URL实现的拦截器

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义拦截器
 * 实现HnadlerInterceptor接口。实现其中的三个方法(三个拦截点)
 */
public class MyInterceptor01 implements HandlerInterceptor {

    /**
     * 进入controller方法之前执行的内容(对请求拦截处理)
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getServletPath();
        if (path.matches(Const.NO_INTERCEPTOR_PATH)) {
            //不需要的拦截直接过
            return true;
        } else {
            // 这写你拦截需要干的事儿,比如取缓存,SESSION,权限判断等
            System.out.println("====================================");
            return true;
        }
    }

    /**
     * 执行了controller方法之后,执行的内容(对象响应进行拦截处理)
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行MyInterceptor01的postHandle方法");
    }

    /**
     * 页面渲染完成之后,执行(不用)
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行MyInterceptor01的afterCompletion方法");
    }
}

关键代码:path.matches(Const.NO_INTERCEPTOR_PATH 就是基于正则匹配的url

/**
 * 常量类
 */
public class Const {

    public static final String SUCCESS = "SUCCESS";
    public static final String ERROR = "ERROR";
    public static final String FIALL = "FIALL";
    /**********************对象和个体****************************/
    public static final String SESSION_USER = "loginedAgent"; // 用户对象
    public static final String SESSION_LOGINID = "sessionLoginID"; // 登录ID
    public static final String SESSION_USERID = "sessionUserID"; // 当前用户对象ID编号

    public static final String SESSION_USERNAME = "sessionUserName"; // 当前用户对象ID编号
    public static final Integer PAGE = 10; // 默认分页数
    public static final String SESSION_URL = "sessionUrl"; // 被记录的url
    public static final String SESSION_SECURITY_CODE = "sessionVerifyCode"; // 登录页验证码
    // 时间 缓存时间
    public static final int TIMEOUT = 1800;// 秒
    public static final String ON_LOGIN = "/logout.htm";
    public static final String LOGIN_OUT = "/toLogout";
    // 不验证URL anon:不验证/authc:受控制的
    public static final String NO_INTERCEPTOR_PATH =".*/((.css)|(.js)|(images)|(login)|(anon)).*";
}


方式2:继承 HandlerInterceptorAdapter

注意:继承 HandlerInterceptorAdapter 类方式已过时

1.创建注解:

/**
 * 在需要登录验证的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})    // 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)    // 运行时有效
public @interface LoginRequired {
    
}

2.创建拦截器:

public class MyInterceptor02 extends HandlerInterceptorAdapter{
    
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 方法注解级拦截器
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 判断接口是否需要登录
        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
        // 有 @LoginRequired 注解,需要认证
        if (methodAnnotation != null) {
            // 拦截需要干的事儿,比如取缓存,SESSION,权限判断等
            System.out.println("====================================");
            return true;
        }
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("进入到拦截器中:postHandle() 方法中");
        System.out.println(request.getRequestURI());
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("进入到拦截器中:afterCompletion() 方法中");
        System.out.println(request.getServletPath());
    }
}


注册拦截器:实现接口 WebMvcConfigurer

  • 配置类方式(实现接口 WebMvcConfigurer)

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import com.*.*.interceptor.AdminInterceptor;
    
    /**
     * 拦截器配置
     */
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册MyInterceptor01拦截器
            InterceptorRegistration registration = registry.addInterceptor(getMyInterceptor01());
            registration.addPathPatterns("/**");                      //所有路径都被拦截
            registration.excludePathPatterns(                         //添加不拦截路径
                                             "登陆路径",                //登录
                                             "/**/*.html",            //html静态资源
                                             "/**/*.js",              //js静态资源
                                             "/**/*.css",             //css静态资源
                                             "/**/*.woff",
                                             "/**/*.ttf"
                                             );    
        
            // 注册MyInterceptor02拦截器。拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要拦截
            registry.addInterceptor(getMyInterceptor02()).addPathPatterns("/**");
        }
        
        @Bean
         public MyInterceptor01 getMyInterceptor01() {
             return new MyInterceptor01();
         }
         
         @Bean
         public MyInterceptor02 getMyInterceptor02() {
             return new MyInterceptor02();
         }
    }
  • xml方式

    在SpringMVC的配置文件中,添加拦截器配置(配置拦截器对应需要拦截的URL和方法规则)

        <!--配置SpringMVC的拦截器-->
        <mvc:interceptors>
            <!--配置具体的拦截器和拦截器的拦截规则-->
            <mvc:interceptor>
                <!-- mapping : 配置拦截规则。 /** 表示拦截所有  -->
                <mvc:mapping path="/**"/>
                <!-- exclude-mapping: 配置不拦截的规则 -->
                <mvc:exclude-mapping path="/hello/demo2"/>
                <!--创建对象:在当前拦截器中有效-->
                <bean class="cn.test.interceptors.MyInterceptor01"></bean>
            </mvc:interceptor>
        </mvc:interceptors>


自定义拦截器链

开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。

开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。

配置拦截器:多个拦截器链的执行顺序和配置顺序有关

    <!--配置SpringMVC的拦截器-->
    <mvc:interceptors>
        <!--配置具体的拦截器和拦截器的拦截规则-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/hello/demo2"/>
            <bean class="cn.test.interceptors.MyInterceptor01"></bean>
        </mvc:interceptor>

        <!--配置具体的拦截器和拦截器的拦截规则-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/hello/demo2"/>
            <bean class="cn.test.interceptors.MyInterceptor02"></bean>
        </mvc:interceptor>
    </mvc:interceptors>


拓展

自定义类型转换器

1.自定义一个类型转换器,实现类型转换的方法

import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 自定义的类型转换器
 * 实现Converter接口,<原始类型,目标类型>
 */
public class StringToDateConverter implements Converter<String, Date> {

    /**
     * 日期转化
     *   参数:请求参数中的原始数据
     *   返回值:转换好的数据(目标类型)
     */
    public Date convert(String source) {
        Date date = null;
        try {
            date = new SimpleDateFormat("yyyy-MM-dd").parse(source);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return date;
    }
}

2.将自定义的类型转换注册到SpringMvc的转换服务中,然后再将服务注册到SpringMVC的注解驱动

  • xml方式
    <!--将自定义的类型转化器加入Springmvc的转化器ConversionService的集合中-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!--使用集合传入自定义的converter-->
        <property name="converters">
            <set>
                <!--一个bean对应一个类型转换器-->
                <bean class="cn.test.converter.StringToDateConverter"></bean>
            </set>
        </property>
    </bean>

    <!--将Springmvc的转化器注册到springmvc驱动中-->
    <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
  • 配置类方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.GenericConversionService;

@Configuration
public class ConvertConfig {
    @Bean
    public ConversionService genericConversionService(GenericConversionService genericConversionService){
        genericConversionService.addConverter(new StringToDateConverter());
        return genericConversionService;
    }
}


Restful 风格及@PathVariable注解

restful简称REST,全称是Representational State Transfer。

REST是一种软件架构风格, 其强调HTTP应当以资源为中心 ( URL中尽量不要出现动词 )。

它制定了HTTP请求四个动作,分别表示对资源的CRUD操作: GET(获取)、POST(新建)、PUT(更新)、DELETE(删除)

restful 使用方式:

  • 同一个URL,根据不同的请求方式,做不一样的业务处理
  • 自定义地址参数 /PATH/{自定义地址参数名称}
  • 接收REST风格请求地址中占位符的值:@PathVariable(value="地址参数名称") 注解
  • 请求方式:GET(获取),POST(新建),PUT(更新),DELETE(删除)
原来 Restful
保存 /saveUser POST /user
修改 /udpateUser?uid=1 PUT /user/1
删除 /deleteUser?uid=1 DELETE /user/1
查询一个 /findUserByUid?uid=1 GET /user/1
查询所有 /findUser GET /user
@Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 根据id查询
     *  请求地址:/user/5
     *  请求方式:GET
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public String findById(@PathVariable(value="id") String id) {
        System.out.println("根据id查询,id=" + id);
        return "success";
    }

    /**
     * 根据id删除
     *  请求地址:/user/5
     *  请求方式:DELETE
     */
    @RequestMapping(value="/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable(value="id") String id) {
        System.out.println("根据id删除,id=" + id);
        return "success";
    }

    /**
     * 根据id更新
     *  请求地址:/user/5
     *  请求方式:PUT
     */
    @RequestMapping(value="/{id}", method = RequestMethod.PUT)
    public String update(@PathVariable(value="id") String id) {
        System.out.println("根据id更新,id=" + id);
        return "success";
    }

    /**
     * 保存
     *  请求地址:/user
     *  请求方式:POST
     */
    @RequestMapping(value="/", method = RequestMethod.POST)
    public String save() {
        System.out.println("保存");
        return "success";
    }
}


@RestController注解说明

restful风格多用于前后端绝对分离的项目开发中,这时同步请求将无法使用,所有的处理器都将成为返回数据的异步请求。

这种情况,可以将所有处理器方法上的 @ResponseBody 注解提取到类上去。

然后,进一步可以使用 @RestController 注解来替代 @Controller 和 @ResponseBody 两个注解。

写法如下:

//@Controller
//@ResponseBody
@RestController
@RequestMapping("/day02")
public class Day02Controller {
    ...
}


处理中文乱码

SpringMVC 在使用 post 提交请求时,对于中文参数是有乱码问题的, 针对这种情况它提供了一个中文乱码过滤器,只需要进行配置一下即可。

web.xml配置SpringMVC中文乱码过滤器

    <!--在web.xml中配置过滤器,设置编码即可 CharacterEncodingFilter-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


RequestContextHolder:工具类

介绍及使用示例

RequestContextHolder:持有上下文的 Request 容器

在 Web 开发中,service 层或者某个工具类中需要获取到 HttpServletRequest 对象还是比较常见的。

  • 一种方式是将 HttpServletRequest 作为方法的参数从 controller 层一直放下传递,不过这种有点费劲,且做起来不是优雅
  • 另一种方式则是通过 RequestContextHolder 类

    在 spring mvc 中,通过 RequestContextHolder 类的静态方法 getRequestAttributes(),可以返回一个 ServletRequestAttributes 对象,这是个 servlet 容器的描述对象,通过这个对象,就可以轻松获得请求和响应等对象。

    可以类比理解为 ServletRequestAttributes 对象有 class 文件对象的作用,RequestContextHolder 类比理解为一个类加载器。

    RequestContextHolder 是基于 ThreadLocal 实现的,基于本地线程提供了Getter/Setter方法,但如果跨线程则丢失变量值。


RequestContextHolder 使用示例

// 获取相关对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

// 底层实现:request.getAttribute(“userId”);
String userId = (String) requestAttributes.getAttribute(“userId”, RequestAttributes.SCOPE_REQUEST);
// 底层实现:session.getAttribute(“userId”);
String userId = (String) requestAttributes.getAttribute(“userId”, RequestAttributes.SCOPE_SESSION);

// 或者转成具体对象
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
HttpSession session = request.getSession();


MyHttpUtils:自定义工具类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class MyHttpUtils {

    private static final ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

    /**
     * 获取 HttpServletRequest
     */
    public static HttpServletRequest getRequest() {
        return requestAttributes.getRequest();
    }

    /**
     * 获取 HttpServletResponse
     */
    public static HttpServletResponse getResponse() {
        return requestAttributes.getResponse();
    }

    /**
     * 获取header
     */
    public static String getHeader(String headerName) {
        return getRequest().getHeader(headerName);
    }

    /**
     * 获取cookie对象
     */
    public static Cookie getCookie(String cookieName) {
        HttpServletRequest request = getRequest();
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (StringUtils.equals(cookieName, cookie.getName())) {
                return cookie;
            }
        }
        return null;
    }

    /**
     * 获取cookie的值
     */
    public static String getCookieValue(String cookieName) {
        return getCookie(cookieName).getValue();
    }

    /**
     * 设置cookie
     */
    public static void setCookie(String cookieName, String cookieValue) {
        Cookie cookie = new Cookie(cookieName, cookieValue);
        getResponse().addCookie(cookie);
    }

    /**
     * 获取访问者IP
     */
    public static String getIP() {
        HttpServletRequest request = getRequest();
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if (index != -1) {
                return XFor.substring(0, index);
            } else {
                return XFor;
            }
        }
        XFor = Xip;
        if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}
相关文章
|
2天前
|
JSON 前端开发 Java
解决Spring MVC中No converter found for return value of type异常
在Spring MVC开发中遇到`No converter found for return value of type`异常,通常是因缺少消息转换器、返回值类型不支持或转换器优先级配置错误。解决方案包括:1) 添加对应的消息转换器,如`MappingJackson2HttpMessageConverter`;2) 自定义消息转换器并实现`HttpMessageConverter`接口,设置优先级;3) 修改返回值类型为如`ResponseEntity`的合适类型。通过这些方法可确保返回值正确转换为响应内容。
42 1
|
2天前
|
JSON 前端开发 Java
【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解(下)
【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解
6 0
|
2天前
|
JSON 前端开发 Java
【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解(上)
【JavaEE】让“单车变摩托”的神级框架—Spring MVC的深入讲解
5 0
|
2天前
|
前端开发 Java 测试技术
Java一分钟之Spring MVC:构建Web应用
【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
51 3
|
2天前
|
前端开发 Java 应用服务中间件
Spring MVC框架概述
Spring MVC 是一个基于Java的轻量级Web框架,采用MVC设计模型实现请求驱动的松耦合应用开发。框架包括DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、ViewResolver核心组件。DispatcherServlet协调这些组件处理HTTP请求和响应,Controller处理业务逻辑,Model封装数据,View负责渲染。通过注解@Controller、@RequestMapping等简化开发,支持RESTful请求。Spring MVC具有清晰的角色分配、Spring框架集成、多种视图技术支持以及异常处理等优点。
18 1
|
2天前
|
前端开发 Java Spring
[AIGC] Spring Interceptor 拦截器详解
[AIGC] Spring Interceptor 拦截器详解
|
2天前
|
数据采集 前端开发 Java
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
23 3
|
2天前
|
存储 前端开发 Java
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
会话锦囊:揭示Spring MVC如何巧妙使用@SessionAttributes
14 1
|
2天前
|
前端开发 Java Spring
数据之桥:深入Spring MVC中传递数据给视图的实用指南
数据之桥:深入Spring MVC中传递数据给视图的实用指南
34 3
|
2天前
|
前端开发 Java 容器
家族传承:Spring MVC中父子容器的搭建与管理指南
家族传承:Spring MVC中父子容器的搭建与管理指南
26 3