资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Java

Chapter4: SpringBoot与Web开发1

Java 更新时间: 发布时间: 计算机考试归档 最新发布

尚硅谷SpringBoot顶尖教程

1. web准备

首先创建SpringBoot应用,选择我们需要的模块;

SpringBoot已经默认将这些web场景配置好了,只需要在配置文件中指定少量配置就可以运行;

web场景, SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?

  • WebMvcAutoConfiguration:帮我们给容器中自动配置web组件
  • WebMvcProperties:封装配置文件的内容

最后自己编写业务代码即可.

2. SpringBoot对静态资源的映射规则

SpringBoot对静态资源的处理在Web组件WebMvcAutoConfiguration自动配置类中.

@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,		WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,		ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {	// ....		// WebMvcAutoConfigurationAdapter	@Configuration	@Import(EnableWebMvcConfiguration.class)	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })	public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {		// ....        // addResourceHandlers		@Override		public void addResourceHandlers(ResourceHandlerRegistry registry) {			if (!this.resourceProperties.isAddMappings()) {				logger.debug("Default resource handling disabled");				return;			}			Integer cachePeriod = this.resourceProperties.getCachePeriod();			if (!registry.hasMappingForPattern("/webjars                                    this.resourceProperties.getStaticLocations())						.setCachePeriod(cachePeriod));			}		}	}}

2.1 访问/webjars this.resourceProperties.getStaticLocations()) .setCachePeriod(cachePeriod)); } }}

ResourceProperties中追踪this.resourceProperties.getStaticLocations()的源码

// ResourceProperties可以设置和静态资源有关的参数@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)public class ResourceProperties implements ResourceLoaderAware {	private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {			"classpath:/META-INF/resources/", "classpath:/resources/",			"classpath:/static/", "classpath:/public/" };	private static final String[] RESOURCE_LOCATIONS;	static {		RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length				+ SERVLET_RESOURCE_LOCATIONS.length];		System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,				SERVLET_RESOURCE_LOCATIONS.length);        // 将CLASSPATH_RESOURCE_LOCATIONS复制到RESOURCE_LOCATIONS		System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,				SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);	}		private String[] staticLocations = RESOURCE_LOCATIONS;	// ....	// 指向上面的静态资源路径 RESOURCE_LOCATIONS	public String[] getStaticLocations() {		return this.staticLocations;	}    // 可以指定staticLocations	public void setStaticLocations(String[] staticLocations) {		this.staticLocations = appendSlashIfNecessary(staticLocations);	}}	

准备好静态资源

启动应用后, 访问 http://localhost:8082/boot1/a.png, http://localhost:8082/boot1/b , http://localhost:8082/boot1/c.png, 测试结果都能访问到对应的静态资源,会去上面的静态资源目录下去找。

2.3 欢迎页映射

欢迎页,静态资源文件夹下的所有index.html页面;被**映射;

查看欢迎页映射的源码WebMvcAutoConfigurationAdapter#welcomePageHandlerMapping

// WebMvcAutoConfiguration#WebMvcAutoConfigurationAdapter@Configuration@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {	// ....	// welcomePageHandlerMapping	@Bean	public WelcomePageHandlerMapping welcomePageHandlerMapping(			ResourceProperties resourceProperties) {        // 查看下面的WebMvcAutoConfiguration#WelcomePageHandlerMapping		return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),			// this.mvcProperties.getStaticPathPattern()=favicon.ico都是在静态资源文件下查找。

查看源码 WebMvcAutoConfigurationAdapter#FaviconConfiguration

// WebMvcAutoConfiguration#WebMvcAutoConfigurationAdapter@Configuration@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {    // ...    // WebMvcAutoConfigurationAdapter#FaviconConfiguration	@Configuration	@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)	public static class FaviconConfiguration {		private final ResourceProperties resourceProperties;		public FaviconConfiguration(ResourceProperties resourceProperties) {			this.resourceProperties = resourceProperties;		}		@Bean		public SimpleUrlHandlerMapping faviconHandlerMapping() {			SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();			mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);			mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",					faviconRequestHandler()));			return mapping;		}        // 查找favicon.ico的路径 -> resourceProperties.getFaviconLocations()		@Bean		public ResourceHttpRequestHandler faviconRequestHandler() {			ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();			requestHandler					.setLocations(this.resourceProperties.getFaviconLocations());			return requestHandler;		}	}}

ResourceProperties#getFaviconLocations

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)public class ResourceProperties implements ResourceLoaderAware {	private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {			"classpath:/META-INF/resources/", "classpath:/resources/",			"classpath:/static/", "classpath:/public/" };	private static final String[] RESOURCE_LOCATIONS;	static {		RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length				+ SERVLET_RESOURCE_LOCATIONS.length];		System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,				SERVLET_RESOURCE_LOCATIONS.length);		System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,				SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);	}		private String[] staticLocations = RESOURCE_LOCATIONS;        // ...    // favicon.ico也是去项目类路径的静态资源默认目录下去查找    List getFaviconLocations() {		List locations = new ArrayList(				this.staticLocations.length + 1);		if (this.resourceLoader != null) {            // this.staticLocations=RESOURCE_LOCATIONS -> CLASSPATH_RESOURCE_LOCATIONS			for (String location : this.staticLocations) {				locations.add(this.resourceLoader.getResource(location));			}		}		locations.add(new ClassPathResource("/"));		return Collections.unmodifiableList(locations);	}}

3. 模板引擎

JSP、Freemarker、Thymeleaf都是渲染界面的模板引擎技术. view + model :

SpringBoot推荐使用Thymeleaf,语法更简单,功能更强大。

3.1 引入thymeleaf

using-boot-starter 中有使用介绍, 引入thymeleaf的依赖.

	org.springframework.boot	spring-boot-starter-thymeleaf

切换thymeleaf版本

	1.8	3.0.2.RELEASE		2.1.1

3.2 Thymeleaf使用&语法

把html页面放在classpath:/templates/下,thymeleaf就能自动渲染。

@ConfigurationProperties(prefix = "spring.thymeleaf")public class ThymeleafProperties {	private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");	private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");	// classpath:/templates/ 模板html页面放到目录下	public static final String DEFAULT_PREFIX = "classpath:/templates/";	public static final String DEFAULT_SUFFIX = ".html";    // ....    	private String prefix = DEFAULT_PREFIX;	private String suffix = DEFAULT_SUFFIX;    // ....}

编写测试案例, 在classpath:/templates/下添加success.html页面

需要导入thymeleaf的名称空间 xmlns:th="http://www.thymeleaf.org"

th:text 改变当前元素里面的文本内容;

th: 任意html属性;来替换原生属性的值。

					hello				

success


这里是欢迎信息

编写controller映射方法

@Controllerpublic class HelloController {        @RequestMapping("/success")    public String success(Map map) {        //classpath:/templates/success.html        map.put("hello", "

你好

"); //map.put("users", Arrays.asList("zhangsan", "wangwu", "lisi")); return "success"; }}

启动应用,访问 http://localhost:8082/boot1/success ,成功找到success.html页面。

th:id, th:class 替换了原来的属性值.

更多thymeleaf用法可以查看帮助文档

Thymeleaf官方教程

Thymeleaf中文教程

4. SpringMVC自动配置

包 org.springframework.boot.autoconfigure.web下配置了web的所有自动化场景组件.

4.1 Auto-Configuration

[SpringMVC Auto-Configuration 官方文档]( Spring Boot Reference Guide ), 自动配置主要在下面几个方面:

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
    • 自动配置了视图解析器ViewResolver, 根据方法的返回值得到视图对象View, 视图对象决定如何渲染, 可能转发或重定向等等;
    • ContentNegotiatingViewResolver 组合所有的视图解析器;
    • 自定义视图解析器, 只需要给容器中添加一个自定义的视图解析器, SpringMVC会自动将其整合进来;
  • Support for serving static resources, including support for WebJars (see below).
    • 支持静态资源文件夹路径, webjars的访问
  • Automatic registration of Converter, GenericConverter, Formatter beans.
    • Convert 转换器, 可以实现类型转换
    • Formatter 格式化器, 日期格式化等.
  • Support for HttpMessageConverters (see below).
    • HttpMessageConvert 用来转换http请求和响应的;
    • 自己给容器中添加HttpMessageConvert, 只需要将自定义组件注册到容器中(@Bean, @Component)
  • Automatic registration of MessageCodesResolver (see below).
    • 定义错误代码生成规则
  • Static index.html support. 支持静态首页的访问配置
  • Custom Favicon support (see below). 支持自定义 favicon图标
  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).
    • 配置ConfigurableWebBindingInitializer可以替换默认的web初始化器, 使用自定义的.

4.2 扩展SpringMVC

编写一个JavaConfig类, 继承WebMvcConfigurerAdapter, 从而来实现Web功能扩展开发.

注意: 不能在该配置类上标注@EnableWebMvc, 否则它会全面接管默认的MVC配置, 使用自定义的.

@Configurationpublic class MyMvcConfig extends WebMvcConfigurerAdapter {    @Override    public void addViewControllers(ViewControllerRegistry registry) {        super.addViewControllers(registry);        // 浏览器发送/atguigu请求来到success页面        registry.addViewController("/atguigu").setViewName("success");    }}

上面的配置对应到xml是这样的:

controller编写

@Controllerpublic class HelloController {        @RequestMapping("/success")    public String success(Map map) {        //classpath:/templates/success.html        map.put("hello", "

你好

"); map.put("users", Arrays.asList("zhangsan", "wangwu", "lisi")); return "success"; }}

打开浏览器, 访问 http://localhost:8082/boot1/atguigu, 会转发到success.html界面.

4.3 MVC自动配置原理

WebMvcAutoConfiguration是SpringMVC的自动配置类, 在自动配置WebMvcAutoConfigurationAdapter时会导入 EnableWebMvcConfiguration, 包含我们的扩展配置, 容器中所有的WebMvcConfigurer都会起作用。

@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,		WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,		ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {	// ...	// WebMvcAutoConfigurationAdapter	@Configuration	@Import(EnableWebMvcConfiguration.class) // 	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })	public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {		// ...		}		// Configuration equivalent to {@code @EnableWebMvc}.	@Configuration	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {		// ...        //从DelegatingWebMvcConfiguration继承过来的配置,容器中所有的WebMvcConfigurer都会起作用        private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();                // 从容器中获取所有的WebMvcConfigurer        @Autowired(required = false)		public void setConfigurers(List configurers) {            if (!CollectionUtils.isEmpty(configurers)) {                this.configurers.addWebMvcConfigurers(configurers);            }		}                // 将所有的注册到容器的,包含自定义的viewController添加到WebMvcConfigurer        @Override        protected void addViewControllers(ViewControllerRegistry registry) {            this.configurers.addViewControllers(registry);        }	}}

4.4 全面接管SpringMVC

如果SpringBoot不适用SpringMVC的默认自动配置,转而使用我们自定义的配置,只需要在配置类上添加@EnableWebMvc即可, 它会导致所有的SpringMVC的自动配置都失效。

@EnableWebMvc // 全面接管SpringMVC的自动配置@Configurationpublic class MyMvcConfig extends WebMvcConfigurerAdapter {    // ...}

为什么添加@EnableWebMvc注解后,会导致所有的SpringMVC的自动配置都失效呢 ?

从源码分析EnableWebMvc导入了DelegatingWebMvcConfiguration

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(DelegatingWebMvcConfiguration.class)public @interface EnableWebMvc {}

DelegatingWebMvcConfiguration对WebMvc各个场景的基本功能配置提供了支持。

@Configurationpublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();	@Autowired(required = false)	public void setConfigurers(List configurers) {		if (!CollectionUtils.isEmpty(configurers)) {			this.configurers.addWebMvcConfigurers(configurers);		}	}  // ....   }    

SpringMvc的自动配置类WebMvcAutoConfiguration只会在容器中没有WebMvcConfigurationSupport组件时才会生效, 所以如果在自定义配置类上添加了@EnableWebMvc注解, 就相当于给容器中注册了WebMvcConfigurationSupport组件, 会导致mvc默认的自动配置类WebMvcAutoConfiguration失效.

@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,		WebMvcConfigurerAdapter.class })// 容器中没有WebMvcConfigurationSupport时, 自动配置类才会生效.@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,		ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {    // ...}

5. 如何修改SpringBoot的默认配置

1)SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component),如果有就用用户自己配置的;如果没有,才自动配置;如果有些组件可以使用多个(比如ViewResolver),就会将用户配置的和系统默认的组合起来。

2)在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置。

3)在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置。

6. 访问指定的首页

映射器没有映射到指定url会去当前项目静态目录(/resources, /static, /public)下面去查找匹配index.html

如果映射器映射到指定url,会自动匹配映射逻辑指定的视图view。

@Controllerpublic class HelloController {        @RequestMapping(path = {"/", "/index", "/index.html"})    public String index() {        return "login";    }}

浏览器访问 http://localhost:8082/boot1/, http://localhost:8082/boot1/index和 http://localhost:8082/boot1/index.html , 映射器都会去静态资源目录下匹配指定的login.html. 这是SpringMvc中映射器的实现效果.

我们也可以不使用默认的映射器实现上面的效果, 我们首先注释掉 HelloController#index(...)

@Controllerpublic class HelloController {       }

而是使用自定义的映射器去给url指定访问的资源, 可以通过实现WebMvcConfigurerAdapter来扩展SpringMvc的功能.

@Configurationpublic class MyMvcConfig extends WebMvcConfigurerAdapter {         // 添加自定义url映射视图关系    @Bean    public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {        WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {            @Override            public void addViewControllers(ViewControllerRegistry registry) {                // 映射到登录页面login.html                registry.addViewController("/").setViewName("login");                // 映射到登录页面login.html                registry.addViewController("/index.html").setViewName("login");                 // 登录成功后的展示页面dashboard.html                registry.addViewController("/main.html").setViewName("dashboard");             }        };        return adapter;    }}

重启应用后, 浏览器访问 http://localhost:8082/boot1/ , http://localhost:8082/boot1/index.html; 可以看到实现了映射到指定资源.

7. 国际化

SpringBoot应用中实现国际化的操作步骤有:

  • 编写国际化配置文件
  • 使用ResourceBundleMessageSource管理国际化资源文件
  • 在页面使用fmt:message取出国际化内容

编写国际化配置文件,抽取页面需要展示的国际化消息

SpringBoot自动配置好了管理国际化资源文件的组件 MessageSourceAutoConfiguration

@Configuration@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@Conditional(ResourceBundleCondition.class)@EnableConfigurationProperties@ConfigurationProperties(prefix = "spring.messages")public class MessageSourceAutoConfiguration {    // 国际化配置文件可以直接放在类路径下, 定义为messages.properties, 系统默认识别并解析该文件    // 也可以在全局配置文件中使用spring.messages.basename修改国际化文件读取目录    private String basename = "messages";}

修改国际化配置文件的读取基础名

# 配置国际化文件基础名spring.messages.basename=international.login

去页面login.html获取国际化信息的值 , 使用了thymeleaf模板引擎.

                    登录页面                

登录页面




[[#{login.remember}]]

SpringMvc默认根据当前请求头中携带的区域信息locale来进行配置国际化.

WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#localeResolver源码分析:

@Configuration@ConditionalOnWebApplication@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,		WebMvcConfigurerAdapter.class })@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,		ValidationAutoConfiguration.class })public class WebMvcAutoConfiguration {	// ...	// WebMvcAutoConfigurationAdapter	@Configuration	@Import(EnableWebMvcConfiguration.class) // 	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })	public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {		// ...		// LocaleResolver        @Bean		@ConditionalOnMissingBean        // 读取全局配置文件中的spring.mvc.locale区域信息进行国际化配置        // FIXED : Always use the configured locale.        // ACCEPT_HEADER: Use the "Accept-Language" header or the configured locale if the header is not set.		@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")		public LocaleResolver localeResolver() {			if (this.mvcProperties					.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {				return new FixedLocaleResolver(this.mvcProperties.getLocale());			}            // 如果全局配置文件中的spring.mvc.locale没有指定, 就根据请求头中的AcceptHeaderLocale获取区域信息, 进行国际化配置			AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();			localeResolver.setDefaultLocale(this.mvcProperties.getLocale());			return localeResolver;		}	}}

AcceptHeaderLocaleResolver中根据Accept-Language中的区域信息来配置国际化.

public class AcceptHeaderLocaleResolver implements LocaleResolver {    private Locale defaultLocale;    public void setDefaultLocale(Locale defaultLocale) {		this.defaultLocale = defaultLocale;	}    public Locale getDefaultLocale() {		return this.defaultLocale;	}    // resolveLocale    @Override	public Locale resolveLocale(HttpServletRequest request) {		Locale defaultLocale = getDefaultLocale();        // 如果Accept-Language为空,就使用默认的国际化配置		if (defaultLocale != null && request.getHeader("Accept-Language") == null) {			return defaultLocale;		}		Locale requestLocale = request.getLocale();		if (isSupportedLocale(requestLocale)) {			return requestLocale;		}		Locale supportedLocale = findSupportedLocale(request);		if (supportedLocale != null) {			return supportedLocale;		}		return (defaultLocale != null ? defaultLocale : requestLocale);	}}

也可以在界面点击切换链接进行国际化切换,在切换链接后面指定国际化区域信息 /login.html?l=zh_CN

中文 English

在WebMvcConfigurerAdapter中扩展自定义 LocaleResolver

@Configurationpublic class MyMvcConfig extends WebMvcConfigurerAdapter {    // ...    // 使用自定义的MyLocaleResolver    @Bean    public LocaleResolver localeResolver() {        return new MyLocaleResolver();    }}public class MyLocaleResolver implements LocaleResolver {    @Override    public Locale resolveLocale(HttpServletRequest request) {        // 获取/login.html?l=zh_CN中的请求参数l        String l = request.getParameter("l");        Locale locale = null;        if (!StringUtils.isEmpty(l)) {            String[] sp = l.split("_"); // en_US zh_CN            locale = new Locale(sp[0], sp[1]); // language,country        } else {            // 这里必须默认初始化一个bean,如果将该国际化配置到springBoot中,第一次进入界面调用国际化组件没有参数l,返回null,会报空指针            locale = new Locale("zh", "CN");        }        return locale;    }    @Override    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {    }}

重启应用后,访问 http://localhost:8082/boot1/index.html, http://localhost:8082/boot1/index

点击【中文 English】链接切换国际化, 地址栏变化http://localhost:8082/boot1/index.html?l=en_US

开发期间thymeleaf模板引擎页面修改后要实时生效, 可以在全局配置文件禁用缓存

#禁用缓存spring.thymeleaf.cache=false

8. RestfulCRUD

8.1 拦截器

登录提交的校验可以使用拦截器, 下面我们写一个拦截器, 来实现如果用户没登录就不允许访问/main.html

public class LoginHandlerIntercepter implements HandlerInterceptor {        @Override    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {        Object loginUser = httpServletRequest.getSession().getAttribute("loginUser");        System.out.println("拦截器登录校验 loginUser=" + loginUser);        if (loginUser == null) {            // 没有登录,返回登录页面            httpServletRequest.setAttribute("msg", "没有权限,请先登录");       httpServletRequest.getRequestDispatcher("/index.html").forward(httpServletRequest, httpServletResponse);            return false;        }        // 已经登录,放行        return true;    }    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

在配置类中扩展SpringMvc的功能, 增加拦截器.

@Configurationpublic class MyMvcConfig extends WebMvcConfigurerAdapter {        // 添加自定义url映射视图关系    @Bean    public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {        WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {            @Override            public void addViewControllers(ViewControllerRegistry registry) {                registry.addViewController("/").setViewName("login");                registry.addViewController("/index.html").setViewName("login");                // /main.html请求会映射到dashboard.html界面                registry.addViewController("/main.html").setViewName("dashboard");            }			// 添加拦截器            @Override            public void addInterceptors(InterceptorRegistry registry) {                // 	public static final String DEFAULT_METHOD_PARAM = "_method";	private String methodParam = DEFAULT_METHOD_PARAM;		public void setMethodParam(String methodParam) {		Assert.hasText(methodParam, "'methodParam' must not be empty");		this.methodParam = methodParam;	}	@Override	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)			throws ServletException, IOException {		HttpServletRequest requestToUse = request;		// 如果表单提交的是post请求,进入转换逻辑		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {            // 读取_method参数的值			String paramValue = request.getParameter(this.methodParam);            // 判断_method值非空			if (StringUtils.hasLength(paramValue)) {                // 将_method值替换到HttpServletRequest中,实现修改http请求方式的效果				requestToUse = new HttpMethodRequestWrapper(request, paramValue);			}		}        // 过滤器继续执行,HttpServletRequest替换成了包装过的requestToUse		filterChain.doFilter(requestToUse, response);	}		private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {		private final String method;		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {			super(request);            // 初始化将外部传入的_method值给到HttpServletRequest#method			this.method = method.toUpperCase(Locale.ENGLISH);		}        // HttpMethodRequestWrapper父类实现了HttpServletRequest,重写getMethod方法, 最终映射handler读取的HTTP请求方式就是转换包装后的_method		@Override		public String getMethod() {			return this.method;		}	}}public class HttpServletRequestWrapper extends ServletRequestWrapper implements        HttpServletRequest {    // ...    private HttpServletRequest _getHttpServletRequest() {        return (HttpServletRequest) super.getRequest();    }        @Override    public String getMethod() {        return this._getHttpServletRequest().getMethod();    }    }

9. 错误处理机制

9.1 SpringBoot默认的错误处理机制

浏览器效果, 返回一个默认的错误页面

浏览器端发送请求的请求头中Accept中有text/html

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*errorpublic String getServletPrefix() {	String result = this.servletPath;	if (result.contains("*")) {		result = result.substring(0, result.indexOf("*"));	}	if (result.endsWith("/")) {		result = result.substring(0, result.length() - 1);	}	return result;}// ErrorProperties#getPath  获取系统出现异常后,错误处理的url: /errorpublic class ErrorProperties {		@Value("${error.path:/error}")	private String path = "/error";	public String getPath() {		return this.path;	}}

9.2.3 BasicErrorController

当系统出现错误时, springboot会将去寻找处理错误的url (/error), 然后使用/error去找到mvc中映射的handler处理错误.

// 处理错误的handler,默认路由到 /error 来处理@Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController {	private final ErrorProperties errorProperties;    // AbstractErrorController#errorAttributes    private final ErrorAttributes errorAttributes;	// Create a new {@link BasicErrorController} instance.	public BasicErrorController(ErrorAttributes errorAttributes,			ErrorProperties errorProperties, List errorViewResolvers) {		super(errorAttributes, errorViewResolvers);		Assert.notNull(errorProperties, "ErrorProperties must not be null");		this.errorProperties = errorProperties;	}	@Override	public String getErrorPath() {		return this.errorProperties.getPath();	}    // 如果请求头中的Accept=text/html, 会在该方法中处理, 一般处理来自浏览器端的请求	@RequestMapping(produces = "text/html")	public ModelAndView errorHtml(HttpServletRequest request,			HttpServletResponse response) {        // 获取http-status		HttpStatus status = getStatus(request);        // 将异常信息封装到model中		Map model = Collections.unmodifiableMap(getErrorAttributes(				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));        // 设置http请求状态		response.setStatus(status.value());        // 解析model数据并返回view		ModelAndView modelAndView = resolveErrorView(request, response, status, model);		return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);	}    // 其他客户端发送的请求,出现异常时使用该方法处理并产生Json类型的响应数据	@RequestMapping	@ResponseBody // response json	public ResponseEntity> error(HttpServletRequest request)         // 获取异常信息		Map body = getErrorAttributes(request,				isIncludeStackTrace(request, MediaType.ALL));		HttpStatus status = getStatus(request);		return new ResponseEntity>(body, status);	}	// Provide access to the error properties.	protected ErrorProperties getErrorProperties() {		return this.errorProperties;	}        // 从请求参数中获取请求状态 javax.servlet.error.status_code    protected HttpStatus getStatus(HttpServletRequest request) {		Integer statusCode = (Integer) request				.getAttribute("javax.servlet.error.status_code");		if (statusCode == null) {            // 500			return HttpStatus.INTERNAL_SERVER_ERROR;		}		try {			return HttpStatus.valueOf(statusCode);		}		catch (Exception ex) {            // 500			return HttpStatus.INTERNAL_SERVER_ERROR;		}	}        // 获取request中的请求参数    protected Map getErrorAttributes(HttpServletRequest request,			boolean includeStackTrace) {		RequestAttributes requestAttributes = new ServletRequestAttributes(request);        // DefaultErrorAttributes#getErrorAttributes		return this.errorAttributes.getErrorAttributes(requestAttributes,				includeStackTrace);	}}

9.2.4 DefaultErrorViewResolver

BasicErrorController#errorHtml中使用到了resolveErrorView进行错误处理的视图解析.

// 继承自AbstractErrorController的处理异常的视图解析器private final List errorViewResolvers;// BasicErrorController#resolveErrorViewprotected ModelAndView resolveErrorView(HttpServletRequest request,		HttpServletResponse response, HttpStatus status, Map model) {	for (ErrorViewResolver resolver : this.errorViewResolvers) {        // ErrorViewResolver#resolveErrorView 视图解析		ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);		if (modelAndView != null) {			return modelAndView;		}	}	return null;}

DefaultErrorViewResolver#resolveErrorView

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {	private static final Map SERIES_VIEWS;	static {		Map views = new HashMap();        // 按照请求状态码进行匹配 /error/4xx.html, /error/5xx.html		views.put(Series.CLIENT_ERROR, "4xx");		views.put(Series.SERVER_ERROR, "5xx");		SERIES_VIEWS = Collections.unmodifiableMap(views);	}	// ...		@Override	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,			Map model) {		ModelAndView modelAndView = resolve(String.valueOf(status), model);		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);		}		return modelAndView;	}	private ModelAndView resolve(String viewName, Map model) {        //  viewName是请求状态码, 拼接后/error/400.html 		String errorViewName = "error/" + viewName;        // 模板引擎可以解析这个页面地址的话, 就用这个页面地址封装视图  		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders            // TemplateAvailabilityProviders#getProvider				.getProvider(errorViewName, this.applicationContext);		if (provider != null) {			return new ModelAndView(errorViewName, model);		}        // 模板引擎找不到对应的页面,就去类路径下去找		return resolveResource(errorViewName, model);	}	private ModelAndView resolveResource(String viewName, Map model) {        		for (String location : this.resourceProperties.getStaticLocations()) {			try {				Resource resource = this.applicationContext.getResource(location);				resource = resource.createRelative(viewName + ".html");				if (resource.exists()) {                    // 如果找到自定义的错误处理的页面,封装View					return new ModelAndView(new HtmlResourceView(resource), model);				}			}			catch (Exception ex) {			}		}		return null;	}	@Override	public int getOrder() {		return this.order;	}	public void setOrder(int order) {		this.order = order;	}		private static class HtmlResourceView implements View {		private Resource resource;		HtmlResourceView(Resource resource) {			this.resource = resource;		}		@Override		public String getContentType() {			return MediaType.TEXT_HTML_VALUE;		}        // 视图渲染		@Override		public void render(Map model, HttpServletRequest request,				HttpServletResponse response) throws Exception {			response.setContentType(getContentType());			FileCopyUtils.copy(this.resource.getInputStream(),					response.getOutputStream());		}	}}

TemplateAvailabilityProviders#getProvider

public class TemplateAvailabilityProviders {	// 配置的可用模板	private final List providers;	public TemplateAvailabilityProviders(ClassLoader classLoader) {		Assert.notNull(classLoader, "ClassLoader must not be null");        // 从spring.factories中加载Template availability providers //FreeMarkerTemplateAvailabilityProvider,ThymeleafTemplateAvailabilityProvider...		this.providers = SpringFactoriesLoader				.loadFactories(TemplateAvailabilityProvider.class, classLoader);	}		public TemplateAvailabilityProvider getProvider(String view, Environment environment,			ClassLoader classLoader, ResourceLoader resourceLoader) {		// ....		RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(				environment, "spring.template.provider.");		if (!propertyResolver.getProperty("cache", Boolean.class, true)) {            // findProvider 匹配模板			return findProvider(view, environment, classLoader, resourceLoader);		}		TemplateAvailabilityProvider provider = this.resolved.get(view);		if (provider == null) {			synchronized (this.cache) {				provider = findProvider(view, environment, classLoader, resourceLoader);				provider = (provider == null ? NONE : provider);				this.resolved.put(view, provider);				this.cache.put(view, provider);			}		}		return (provider == NONE ? null : provider);	}	private TemplateAvailabilityProvider findProvider(String view,			Environment environment, ClassLoader classLoader,			ResourceLoader resourceLoader) {		for (TemplateAvailabilityProvider candidate : this.providers) {            // 从候选模板中匹配可用的模板页面			if (candidate.isTemplateAvailable(view, environment, classLoader,					resourceLoader)) {				return candidate;			}		}		return null;	}}	

ThymeleafTemplateAvailabilityProvider#isTemplateAvailable

public class ThymeleafTemplateAvailabilityProvider		implements TemplateAvailabilityProvider {	@Override	public boolean isTemplateAvailable(String view, Environment environment,			ClassLoader classLoader, ResourceLoader resourceLoader) {		if (ClassUtils.isPresent("org.thymeleaf.spring4.SpringTemplateEngine",				classLoader)) {			PropertyResolver resolver = new RelaxedPropertyResolver(environment,					"spring.thymeleaf.");			String prefix = resolver.getProperty("prefix",					ThymeleafProperties.DEFAULT_PREFIX); // classpath:/templates/			String suffix = resolver.getProperty("suffix",					ThymeleafProperties.DEFAULT_SUFFIX); // .html            // classpath:/templates/404.html			return resourceLoader.getResource(prefix + view + suffix).exists();		}		return false;	}}

9.2.5 DefaultErrorAttributes

BasicErrorController解析浏览器端的请求或其他客户端请求的errorHtml和error的处理中都有获取异常相关信息的操作, 是从DefaultErrorAttributes中获取.

@Order(Ordered.HIGHEST_PRECEDENCE)public class DefaultErrorAttributes		implements ErrorAttributes, HandlerExceptionResolver, Ordered {	// ....    // 获取异常信息	@Override	public Map getErrorAttributes(RequestAttributes requestAttributes,			boolean includeStackTrace) {		Map errorAttributes = new LinkedHashMap();        // 存入时间戳		errorAttributes.put("timestamp", new Date());        // 存入响应状态码		addStatus(errorAttributes, requestAttributes);        // 存入异常信息		addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);        // 存入请求uri		addPath(errorAttributes, requestAttributes);		return errorAttributes;	}	private void addStatus(Map errorAttributes,			RequestAttributes requestAttributes) {        // 获取异常的响应码javax.servlet.error.status_code的值		Integer status = getAttribute(requestAttributes,				"javax.servlet.error.status_code");		if (status == null) {			errorAttributes.put("status", 999);			errorAttributes.put("error", "None");			return;		}		errorAttributes.put("status", status);		try {			errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());		}		catch (Exception ex) {			// Unable to obtain a reason			errorAttributes.put("error", "Http Status " + status);		}	}	private void addErrorDetails(Map errorAttributes,			RequestAttributes requestAttributes, boolean includeStackTrace) {		Throwable error = getError(requestAttributes);		if (error != null) {			while (error instanceof ServletException && error.getCause() != null) {				error = ((ServletException) error).getCause();			}			errorAttributes.put("exception", error.getClass().getName());			addErrorMessage(errorAttributes, error);			if (includeStackTrace) {				addStackTrace(errorAttributes, error);			}		}        // 获取异常信息javax.servlet.error.message的值		Object message = getAttribute(requestAttributes, "javax.servlet.error.message");		if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)				&& !(error instanceof BindingResult)) {            // 存入异常信息message			errorAttributes.put("message",					StringUtils.isEmpty(message) ? "No message available" : message);		}	}	private void addErrorMessage(Map errorAttributes, Throwable error) {		BindingResult result = extractBindingResult(error);		if (result == null) {			errorAttributes.put("message", error.getMessage());			return;		}		if (result.getErrorCount() > 0) {			errorAttributes.put("errors", result.getAllErrors());			errorAttributes.put("message",					"Validation failed for object='" + result.getObjectName()							+ "'. Error count: " + result.getErrorCount());		}		else {			errorAttributes.put("message", "No errors");		}	}	private void addPath(Map errorAttributes,			RequestAttributes requestAttributes) {        // 获取请求uri		String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");		if (path != null) {			errorAttributes.put("path", path);		}	}	@Override	public Throwable getError(RequestAttributes requestAttributes) {		Throwable exception = getAttribute(requestAttributes, ERROR_ATTRIBUTE);		if (exception == null) {			exception = getAttribute(requestAttributes, "javax.servlet.error.exception");		}		return exception;	}	@SuppressWarnings("unchecked")	private  T getAttribute(RequestAttributes requestAttributes, String name) {		return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);	}}

9.3 定制异常响应页面

9.3.1 有模板引擎的场景

将错误页面命名为 错误状态码.html放在模板引擎下的error文件夹下,发生此状态码的错误就会来到对应的页面。error/状态码.html;

我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确匹配优先(优先寻找精确的errorCode.html页面)。

// DefaultErrorViewResolver#SERIES_VIEWS, 保存请求出现错误时的默认处理页面前缀4xx 5xxprivate static final Map SERIES_VIEWS;static {    Map views = new HashMap();    views.put(Series.CLIENT_ERROR, "4xx");    views.put(Series.SERVER_ERROR, "5xx");    SERIES_VIEWS = Collections.unmodifiableMap(views);}// DefaultErrorViewResolver#resolveErrorView@Overridepublic ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,		Map model) {    // 优先精确匹配, 错误响应状态码 404 ,500	ModelAndView modelAndView = resolve(String.valueOf(status), model);    // 如果精确匹配失败,就按照默认规则找4xx, 5xx	if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {      // status.series()就是用错误响应状态码/100,返回匹配上的枚举, 查看HttpStatus.Series#valueOf       // 然后再从SERIES_VIEWS中获取是4xx还是5xx页面前缀		modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);	}	return modelAndView;}private ModelAndView resolve(String viewName, Map model) {    // viewName就是精确匹配的错误响应状态码或默认匹配的4xx,5xx	String errorViewName = "error/" + viewName; // error/4xx    	TemplateAvailabilityProvider provider = this.templateAvailabilityProviders			.getProvider(errorViewName, this.applicationContext);	if (provider != null) {        // 模板解析完成后, 得到完整的异常处理页面路径 error/4xx.html 		return new ModelAndView(errorViewName, model);	}    // 如果模板引擎找不到, 就去"classpath:/META-INF/resources/", "classpath:/resources/",	// "classpath:/static/", "classpath:/public/" 去找 error/4xx.html 	return resolveResource(errorViewName, model);}

DefaultErrorAttributes使得页面能获取的信息:

  • timestamp 时间戳

  • status 状态码

  • error 错误提示

  • exception 异常

  • message 异常消息

  • path 请求url

/error/4xx.html页面

                    4xx异常                
系统异常
time:[[(${#dates.format(timestamp,'yyyy-MM-dd HH:mm:ss')})]]
status:[[(${status})]]
error:[[(${error})]]
exception:[[(${exception})]]
message:[[(${message})]]
errors:[[(${errors})]]
trace:[[(${trace})]]
path:[[(${path})]]

启动应用后, 访问一个不存在的请求url, 如果没有提供精确匹配的404.html页面, 会继续匹配系统默认的4xx.html界面渲染异常信息.

如果提供了404.html页面, 就会优先精确匹配的404.html页面来渲染异常信息.

9.3.2 无模板引擎的场景

模板引擎找不到这个错误页面,就去静态资源文件夹下找。

"classpath:/META-INF/resources/""classpath:/resources/""classpath:/static/""classpath:/public/" "/" 当前项目的根路径

以上都没有错误页面,就来到SpringBoot默认的错误页面渲染异常信息。

具体源码在ErrorMvcAutoConfiguration中:

@Configuration@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)@Conditional(ErrorTemplateMissingCondition.class)protected static class WhitelabelErrorViewConfiguration {	// 创建默认的异常渲染界面	private final SpelView defaultErrorView = new SpelView(			"

Whitelabel Error Page

" + "

This application has no explicit mapping for /error, so you are seeing this as a fallback.

" + "${timestamp}" + "There was an unexpected error (type=${error}, status=${status})." + "${message}"); // 异常渲染的viewBean @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } // If the user adds @EnableWebMvc then the bean name view resolver from // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment. @Bean @ConditionalOnMissingBean(BeanNameViewResolver.class) public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; }}

9.4 定制异常响应Json

9.4.1 自定义异常处理

自定义用户不存在的异常类

public class UserNotExistException extends Exception {    public UserNotExistException(String message) {        super(message);    }}

模拟触发异常的请求, 只要访问/exception/username?username=aaa就会触发.

@RestController@RequestMapping(value = {"/exception"})public class ExceptionTestController {        @RequestMapping("/username")    @ResponseBody    public String user(@RequestParam("username") String username) throws UserNotExistException {        if ("aaa".equals(username)) {            throw new UserNotExistException("用户不存在!");        }        return username;    }}

使用全局异常处理类来捕获异常及异常处理.

package com.aiguigu.springboot02config.exception.handler;import com.aiguigu.springboot02config.exception.UserNotExistException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;import java.util.HashMap;import java.util.Map;@ControllerAdvice(basePackages = {"com.aiguigu.springboot02config.controller"})public class MyExceptionHandler {    // 自定义异常处理    // 浏览器客户端都返回的json数据,无法实现自适应效果(浏览器返回页面,其他客户端返回json数据)    @ResponseBody    @ExceptionHandler(UserNotExistException.class)    public Map handleException(Exception e) {        Map map = new HashMap<>();        map.put("code", "user.not_exist");        map.put("message", e.getMessage());        return map;    }}

请求 http://localhost:8082/boot1/user?user=aaa ,浏览器和postman请求都返回自定义的json数据, 没有实现自适应响应效果。

{"code":"user.not_exist","message":"用户不存在!"}

9.4.2 自适应响应

可以将异常请求转发到/error进行自适应响应 , 在全局异常处理类中加入自定义异常信息并转发到/error. SpringBoot默认的异常处理请求就是 /error. (在BasicErrorController源码中查看.)

@ControllerAdvice(basePackages = {"com.aiguigu.springboot02config.controller"})public class MyExceptionHandler {	// 对UserNotExistException进行拦截处理    @ExceptionHandler(UserNotExistException.class)    public String handleException2(Exception e, HttpServletRequest request) {        Map map = new HashMap<>();        map.put("code", "user.not_exist");        map.put("message", "用户出错了. " + e.getMessage());                // 传入我们自己的错误状态码, 进入我们指定的错误页面        request.setAttribute("javax.servlet.error.status_code", 500);        // 转发到 /error 实现自适应效果(浏览器返回页面,其他客户端返回json数据) ErrorMvcAutoConfiguration        return "forward:/error";    }}

测试浏览器效果:

其他客户端请求效果:

{  "timestamp": 1682520309898,  "status": 500,  "error": "Internal Server Error",  "exception": "com.aiguigu.springboot02config.exception.UserNotExistException",  "message": "用户不存在!",  "path": "/boot1/exception/username"}

9.4.3 传递定制数据

请求出现异常后,会来到/error请求,会被BasicErrorController处理,响应数据是由getErrorAttributes得到的(父类AbstractErrorController的方法);

  • 编写一个ErrorController的实现类或AbstractErrorController的子类,放在容器中。

    • 异常响应数据是通过errorAttributes.getErrorAttributes方法得到的;容器中的DefaultErrorAttributes#getErrorAttributes()方法,默认进行数据处理。
    • 在全局异常处理类中将需要响应的定制异常信息存入到requst域中.

在全局异常处理类中将需要传递的信息保存到自定义字段ext中.

// 对UserNotExistException进行拦截处理@ExceptionHandler(UserNotExistException.class)public String handleException2(Exception e, HttpServletRequest request) {	Map map = new HashMap<>();	map.put("code", "user.not_exist");	map.put("message", "用户出错了. " + e.getMessage());		// 传入我们自己的错误状态码, 进入我们指定的错误页面	request.setAttribute("javax.servlet.error.status_code", 500);	// 将map信息存入自定义的ext字段	request.setAttribute("ext", map);	// 转发到 /error 实现自适应效果(浏览器返回页面,其他客户端返回json数据) ErrorMvcAutoConfiguration	return "forward:/error";}

自定义MyErrorAttributes,继承DefaultErrorAttributes,重写getErrorAttributes方法,响应数据map可以存入我们自定义的信息, 还能从request域对象中取出之前存入的信息ext。

// 给容器加入自定义的 ErrorAttributes@Componentpublic class MyErrorAttributes extends DefaultErrorAttributes {    // 返回值map就是页面和json能获取的所有字段    @Override    public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {        Map map = super.getErrorAttributes(requestAttributes, includeStackTrace);        map.put("company", "atguigu");        // 我们的异常处理器携带的数据        Map ext = (Map) requestAttributes.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);        map.put("ext", ext);        return map;    }}

浏览器测试效果

其他客户端测试效果, 返回了company, ext自定义字段信息.

{  "timestamp": 1682519269726,  "status": 500,  "error": "Internal Server Error",  "exception": "com.aiguigu.springboot02config.exception.UserNotExistException",  "message": "用户不存在!",  "path": "/boot1/exception/username",  "company": "atguigu",   "ext": {    "code": "user.not_exist",    "message": "用户出错了. 用户不存在!"  }}
转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/1094835.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【Chapter4: SpringBoot与Web开发1】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2