Spring Boot(五):web模块

主要介绍springboot如何进行web开发的,因为springboot项目是达成jar包运行的,所以他不支持jsp技术,对于这种需要模版引擎技术的支持。

关于springboot对web的功能配置,都是在WebMvcAutoConfiguration中,下面的介绍这些配置。

1. springboot对静态资源的映射规则

sb对静态资源的配置在:

		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
            //对webjars资源的获取,去classpath:/META-INF/resources/webjars/
            //去每个jar包的类路径下去寻找。
			Integer cachePeriod = this.resourceProperties.getCachePeriod();
			if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(
						registry.addResourceHandler("/webjars/**")
								.addResourceLocations(
										"classpath:/META-INF/resources/webjars/")
						.setCachePeriod(cachePeriod));
			}
            //获取静态资源文件夹映射,staticPathPattern是/**
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(
						registry.addResourceHandler(staticPathPattern)
								.addResourceLocations(
										this.resourceProperties.getStaticLocations())
						.setCachePeriod(cachePeriod));
			}
		}
		
	    //配置欢迎页映射路径
		@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(
				ResourceProperties resourceProperties) {
			return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
            //顺着代码一路走过去,映射的地址是/**,对应的资源则是静态资源存放的文件下的index.html文件。
		}

		//静态文件存放的位置。
		private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
					"classpath:/META-INF/resources/", "classpath:/resources/",
					"classpath:/static/", "classpath:/public/" };
		
			private static final String[] RESOURCE_LOCATIONS;
1. webjars

通过上面的配置类,配置了一条映射规则,所有的webjars/**映射,都是去classpath:META-INF/resources/webjars找资源。

webjars是以jar包的方式引入资源,比如我们使用jquery通过webjars引入到项目中,如果前台想要获取这个资源,则访问的路径则是通过 http://xxx/*/webjars/jquery.js.

引入webjars资源
<!‐‐引入jquery‐webjar‐‐>在访问的时候只需要写webjars下面资源的名称即可
<dependency>        
	<groupId>org.webjars</groupId>            
	<artifactId>jquery</artifactId>            
	<version>3.3.1</version>            
</dependency>

我们要向查看我们引入的js资源,按照spring的配置,实际访问位置是META-INF/resources/webjars找资源。所以自动定位到了webjars目录下。

我们启动服务器,访问路径则是:

http://127.0.0.1:8080/webjars/jquery/3.3.1/jquery.js

2. 静态资源

配置了一个映射/**规则,是访问当前项目的任何资源,都去静态资源文件夹下去找资源

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

localhost:8080/abc === 去静态资源文件夹里面找abc

3. 欢迎页

静态资源文件夹下的index.html页面,都被/**映射

2. 模版引擎

在这里插入图片描述
模版引擎技术包括jsp,Velocity,freemarker,thymeleaf技术,这些技术的特点都是如上图,将静态页面中的引擎语法和数据相整合,然后重新生成一个带数据的新页面。本质就是将表达式用数据替换,将替换好的页面输出。

1. 引入依赖:
<dependency>        
<groupId>org.springframework.boot</groupId>            
<artifactId>spring‐boot‐starter‐thymeleaf</artifactId>            
           2.1.6  
</dependency>        
切换thymeleaf版本
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>        
<!‐‐ 布局功能的支持程序  thymeleaf3主程序  layout2以上版本 ‐‐>        
<!‐‐ thymeleaf2   layout1‐‐>        
<thymeleaf‐layout‐dialect.version>2.2.2</thymeleaf‐layout‐dialect.version>        
  </properties>
2. 使用

只要将静态的html页面,引入到templates目录下,就能自动渲染

@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");

	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";
  1. 在静态页面导入名称空间

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
  2. 语法:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF‐8">
        <title>Title</title>
    </head>
    <body>
        <h1>成功!</h1>
        <!‐‐th:text 将div里面的文本内容设置为 ‐‐>
        <div th:text="${hello}">这是显示欢迎信息</div>
    </body>
    </html>
    

    这样就可以使用了,如果上下文中含有hello对应的值,则可以取出来。

    @Controller
    public class HelloController {
    
        @RequestMapping("/hello")
        public String hello(Map model) {
            model.put("hello", "你好,我来自后台");
            return "hello";
        }
    }
    
  3. 语法规则

    1)、th:text;改变当前元素里面的文本内容;
    th:任意html属性;来替换原生属性的值
    在这里插入图片描述
    语法配置自行百度。

3. SpringMVC自动配置原理

通过查看springmvc的自动配置类,了解配置好的一些组件。

  • 以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)

    • 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?))
    • ContentNegotiatingViewResolver:组合所有的视图解析器的;
    • 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;
  • Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars。

  • Static index.html support. 静态首页访问

  • Custom Favicon support (see below). favicon.ico

  • 自动注册了 of Converter , GenericConverter , Formatter beans.

    • Converter:转换器; public String hello(User user):类型转换使用Converter

    • Formatter 格式化器; 2017.12.17===Date;

      @ConditionalOnProperty(prefix = "spring.mvc", name = "date‐format")//在文件中配置日期格式化的规则       
      public Formatter<Date> dateFormatter() {        
      	return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件            
      }
      

      自己添加的格式化器转换器,我们只需要放在容器中即可

  • Support for HttpMessageConverters (see below).

    • HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User—Json;
    • HttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverter;
      自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中
      (@Bean,@Component)
  • Automatic registration of MessageCodesResolver (see below).定义错误代码生成规则

4. 扩展SpringMVC

/**
 * 通过实现WebMvcConfigurer接口,可以作为springmvc的配置类,
 * adapter是WebMvcConfigurer的适配类,是空实现。
 * 原理是:
 * 在WebMvcAutoConfigurationAdapter被使用时,含有注解@Import(EnableWebMvcConfiguration.class),
 * 导入了类EnableWebMvcConfiguration类,这个类中有一个方法是获取所有的WebMvcConfigurer组件。
 *
 * @author: mahao
 * @date: 2019/11/22
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    /**
     * 添加拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());
    }

    private static class LogInterceptor implements HandlerInterceptor {

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("前置拦截:" + request.getParameterMap());
            return true;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("后置拦截:" + response.getStatus());
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        }
    }
}

项目练习

1. 引入资源

静态资源导入到static文件夹下,需要模版引擎支持的页面导入到templates目录下。

2. 访问首页

对于springboot,我们知道他的默认欢迎页是静态文件夹下的index.html页面,但是现在我们需要将欢迎页指定为其他目录,则可以通过配置映射,将欢迎页的映射配置到我们自己的资源。方式有2种:

  1. 配置首页访问controller

    /**
     * 将项目的欢迎页转发到自己的页面上,springboot默认的欢迎页是index.html,当请求是项目/或者项目/index.html,
     * 都是在访问欢迎页。我们将这两个请求,转发到我们自己的页面。
     *
     * @author: mahao
     * @date: 2019/11/22
     */
    @Controller
    public class IndexController {
    
        @RequestMapping({"/", "index.html"})
        public String index() {
            return "index";
        }
    }
    
  2. 使用配置类,手动添加资源映射

    @Configuration
    public class MainConfig {
    
        /**
         * 手动扩展配置springmvc,通过WebMvcConfigurer接口,在加载springmvc的配置文件时,会加载WebMvcConfigurer
         * 所有的类,可以通过实现这里面的方法,去注册ViewControllers。
         *
         * @return
         */
        @Bean
        public WebMvcConfigurer webMvcConfigurer() {
            //使用的是适配器接口
            return new WebMvcConfigurerAdapter() {
                @Override
                public void addViewControllers(ViewControllerRegistry registry) {
                    registry.addViewController("/").setViewName("index");
                    registry.addViewController("/index.html").setViewName("index");
                }
            };
        }
    
    }
    

3. 国际化

  • 编写配置文件,配置文件的格式是: 文件名_语言-地区;

    一共有三个多个配置文件,
    在这里插入图片描述

login.properties #默认的
login.tip=请登录
login.username=用户名

---------------------------------

login_zh_CN.properties
login.tip=请登录
login.username=用户名

---------------------------------

login_en_US.properties
login.tip=Please sigin in
login.username=username
  • 使用ResourceBundleMessageSource管理国际化资源文件

    由于是使用springboot,已经配置好了管理国际化资源文件的组件了MessageSourceAutoConfiguration

    	//将国际化管理的组件添加到spring中	
    	@Bean
    	public MessageSource messageSource() {
    		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    		if (StringUtils.hasText(this.basename)) {
    	//获取所有的资源,通过basename指定文件名,这个basename是去掉语言的基础名,默认的是messages,所以,我们定义国际化文件的基础名为messages,则可以直接使用,而不需要配置了。
                messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
    					StringUtils.trimAllWhitespace(this.basename)));
    		}
    		if (this.encoding != null) {
    			messageSource.setDefaultEncoding(this.encoding.name());
    		}
    		messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
    		messageSource.setCacheSeconds(this.cacheSeconds);
    		messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
    		return messageSource;
    	}
    

    我们可以在配置文件中,指定basename为login,作为我们的国际化配置文件,

  • 页面使用thremleaf模版引擎获取值

    th:text=#{login.username} //取出模版引擎的值,
    
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
    <label class="sr-only" th:text="#{login.username}">Username</label>
    
  • 中英文切换

    需要完成点击按钮,去切换中英文。

    国际化的原理就是Local(区域信息对象);LocalResolver(获取区域信息对象);

    在springmvc中配置了LocalResolver;

    		@Bean
    		@ConditionalOnMissingBean
    		@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
    		public LocaleResolver localeResolver() {
    			//用默认配置的LocalResolver
    			if (this.mvcProperties
    					.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
    				return new FixedLocaleResolver(this.mvcProperties.getLocale());
    			}
    			//用根据请求头中信息,生成国际化
    			AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    			localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    			return localeResolver;
    		}
    

    要想实现点击链接实现国际化切换,则可以在连接上携带参数,国际化的参数;

     /**
         * 替换掉系统使用的LocalResolver,使用自定义的localResolver;
         * 根据请求中携带的信息,判断使用哪种国际化.
         */
        @Bean
        public LocaleResolver localeResolver() {
            return new LocaleResolver() {
                //如果指定了使用哪一种国际化,则使用,没有的话,使用默认的;
                @Override
                public Locale resolveLocale(HttpServletRequest request) {
    
                    String l = request.getParameter("l");
                    System.out.println(l);
                    if (!StringUtils.isEmpty(l)) {
                        String[] split = l.split("_");
                        return new Locale(split[0], split[1]);
                    } else {
                        return Locale.getDefault();
                    }
                }
    
                @Override
                public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    
                }
            };
        }
    

4. 登录

@Controller
public class LoginController {

    @PostMapping("/login")
    public String login(String name, String pwd, HttpServletRequest request) {
        if ("zs".equalsIgnoreCase(name) && "123456".equalsIgnoreCase(pwd)) {
            request.getSession().setAttribute("user", "zs");
            return "dashboard";
        } else {
            return "index";
        }
    }
}

新创建一个controller,处理登录功能。如果失败,返回登录页面。如果成功了,去模版引擎下的dashboard.html页面,但是这个操作是转发,浏览器的地址栏仍然是login操作。所以要使用重定向,定向到dashboard.html页面,但是这个要经过模版引擎解析,如果只是直接返回的是 redirect:dashboard,则页面会访问http://xxx/dashboard.html,但是在模版引擎目录下,无法访问。所以,需要配置一个资源映射,作为主页面的映射。

可以在之前使用的添加登录页面的WebmvcConfiguration中配置。

	@PostMapping("/login")
    public String login(String name, String pwd, HttpServletRequest request) {
        if ("zs".equalsIgnoreCase(name) && "123456".equalsIgnoreCase(pwd)) {
            request.getSession().setAttribute("user", "zs");
            return "redirect:main.html";
        } else {
            return "index";
        }
    }

	@Bean
    public WebMvcConfigurer webMvcConfigurer() {
        //使用的是适配器接口
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("index");
                registry.addViewController("/index.html").setViewName("index");
                registry.addViewController("/main.html").setViewName("dashboard");
            }
        };
    }

5. 登录拦截器

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String user = (String) request.getSession().getAttribute("user");
        if (user == null) {
            //重定向回去,改变路径;
            //request.getRequestDispatcher("/index.html").forward(request, response);
            response.sendRedirect("index.html");
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

6. restful 风格crud

在这里插入图片描述
具体代码忽略;

7. 错误处理机制

1. springboot默认的错误处理机制

制造一个错误,当分页传入的页数为负数时,会发生数组越界错误;

  • 浏览器,返回一个默认的错误页面
    在这里插入图片描述

  • 如果是其他客户端,默认响应一个json数据

    {
        "timestamp": 1574489861201,
        "status": 500,
        "error": "Internal Server Error",
        "exception": "java.lang.ArrayIndexOutOfBoundsException",
        "message": "-4",
        "path": "/crud/emps"
    }
    
2. 原理

配置文件是 ErrorMvcAutoConfiguration,错误处理的自动配置;

	//错误信息
	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}
	
	//返回错误页面跳转的controller,帮我们处理/error请求
	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				this.errorViewResolvers);
	}
	
	//错误页面扩展,系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则
	@Bean
	public ErrorPageCustomizer errorPageCustomizer() {
		return new ErrorPageCustomizer(this.serverProperties);
	}

自动配置类中注册了3个组件,ErrorPageCustomizer是确定错误页面的请求路径,默认的是 ’/error‘,组件 BasicErrorController则是负责处理这个请求。 DefaultErrorAttributes是封装的错误信息,提供给用户错误的属性的获取。

DefaultErrorAttributes:

	//帮我们在页面共享信息
	@Override
	public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, requestAttributes);
		addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
		addPath(errorAttributes, requestAttributes);
		return errorAttributes;
	}

BasicErrorController

处理/error请求

	//产生html类型的数据;浏览器发送的请求来到这个方法处理
	@RequestMapping(produces = "text/html")
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        //通过resolveErrorView解析获取去那个错误View
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        //如果modelandview为null,创建一个默认的,视图名为error的视图;
		return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
	}

	//产生json数据,其他客户端来到这个方法处理
	@RequestMapping
	@ResponseBody
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

DefaultErrorViewResolver是resolveErrorView()方法;

	//解析方法
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,Map<String, Object> model) {
        //获取modelandview,根据传入的视图名
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
        //获取失败了,判断SERIES_VIEWS中是否包含status.series(),即变成4xx
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
        //如果上面都没有成功,则返回null,就用error视图;
		return modelAndView;
	}

 	//获取modelandview,根据传入的视图名
	private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //将视图拼接成了error/404这种;
		String errorViewName = "error/" + viewName;
        //是否有模版引擎,
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
            //有的话,封装成ModelAndView,路径是errorViewName
			return new ModelAndView(errorViewName, model);
		}
        //否则,继续解析
		return resolveResource(errorViewName, model);
	}
	
 //没有模版引擎解析
	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resourceProperties.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
                    //直接返回对应的静态html页面;
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

步骤:

一旦系统出现4xx,5xx之类的错误,ErrorpageCustomer就会生效(定制错误的响应规则);就会来到/error请求;就会被 BasicErrorController处理:

  • 响应页面:去哪个页面是由DefaultErrorViewResolver解析得到到;

    protected ModelAndView resolveErrorView(HttpServletRequest request,
          HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        //所有的ErrorViewResolver得到ModelAndView
       for (ErrorViewResolver resolver : this.errorViewResolvers) {
          ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
          if (modelAndView != null) {
             return modelAndView;
          }
       }
       return null;
    }
    
  • 解析规则:

    1.如果有模版引擎的情况下,/error/状态码,为解析的视图名;

    2.没有模版引擎的情况下,会直接使用静态资源路径下的状态码.html页面;

    3.如果不存在对应的视图,则是使用默认的 new ModelAndView("error", model)

    private final SpelView defaultErrorView = new SpelView(
    				"<html><body><h1>Whitelabel Error Page</h1>"
    						+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
    						+ "<div id='created'>${timestamp}</div>"
    						+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
    						+ "<div>${message}</div></body></html>");
    
    		@Bean(name = "error")
    		@ConditionalOnMissingBean(name = "error")
    		public View defaultErrorView() {
    			return this.defaultErrorView;
    		}
    

对于json类型的数据响应:

	@RequestMapping
	@ResponseBody
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        //将错误信息,封装成map,向页面json化ResponseEntity类
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

错误信息的获取: DefaultErrorAttributes

//功能就是将request中的错误信息,包括状态码,错误信息,时间封装成map;
@Override
	public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, requestAttributes);
		addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
		addPath(errorAttributes, requestAttributes);
		return errorAttributes;
	}
3. 定制错误响应
  • 对于浏览器端:

    1. 如果有模版引擎的支持,在templates/error/下面对应状态码的html或者,4xx这种模糊开头的页面。
    2. 没有模版引擎的支持,在静态路径下创建对应的html文件,也必须是error目录下,依据上面的源码中有解析。
  • 对于json数据

    如果我们要向实现定制的类型的json数据,我们可以使用springmvc提供的全局异常处理,将controller抛出的错误都捕获;

    通过 @ControllerAdvice 注解,我们可以在一个地方对所有 @Controller 注解的控制器进行管理。
    注解了 @ControllerAdvice 的类的方法可以使用 @ExceptionHandler、 @InitBinder、 @ModelAttribute 注解到方法上,这对所有注解了 @RequestMapping 的控制器内的方法都有效。
    

    @ExceptionHandler:用于捕获所有控制器里面的异常,并进行处理。
    @InitBinder:用来设置 WebDataBinder,WebDataBinder 用来自动绑定前台请求参数到 Model 中。
    @ModelAttribute:@ModelAttribute 本来的作用是绑定键值对到 Model 里,此处是让全局的@RequestMapping 都能获得在此处设置的键值对。
    本文使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理。只要设计得当,就再也不用在 Controller 层进行 try-catch 了!
    我们编写全局异常处理类:

    package cn.mh.sb.webcrud.compont;
    
    import cn.mh.sb.webcrud.common.ApiResult;
    import cn.mh.sb.webcrud.common.ApiStatus;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * 全局异常处理类
     *
     * @author: mahao
     * @date: 2019/11/23
     */
    @ControllerAdvice//标记注解,拦截所有的@Controller类
    public class GlobalExceptionResolver {
    
        /**
         * 处理所有不可知异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public ApiResult handleException(Exception e) {
            System.out.println(e.getMessage());
            return ApiResult.of(ApiStatus.UNKNOWN_ERROR);
        }
    
        /**
         * 处理自定义的异常
         *
         * @param e
         * @return
         */
        @ExceptionHandler(SBException.class)
        @ResponseBody
        public ApiResult handleMyException(Exception e) {
            System.out.println(e.getMessage());
            return new ApiResult(ApiStatus.SUCCESS);
        }
    
    }
    
    

使用springmvc的全局异常处理机制,我们可以完成自定义json数据的返回,但是sprignboot提供的自适应功能我们失去了。如何更好的改变异常处理机制呢。我们可以使用springmvc全局异常处理机制+springboot自适应功能来实现。

我们想一下,在未配置springmvc全局异常处理之前,我们程序出现异常了,会调用ErrorPageCustomizer组件的方法,寻找错误跳转请求,默认是/error,然后会有一个 BaseController根据请求头的不同去判断返回时json还是页面跳转,在跳转前会使用 DefaultErrorAttributes去封装异常信息。所以我们可以使用 springmvc的全局异常处理机制,去跳转到/error请求,让springboot继续自适应,然后我们在跳转前封装我们自己的数据到返回信息中,我们可以自己创建一个ErrorAttributes组件。

//扩展的结果
{
    "timestamp": 1574513432414,
    "status": 508,
    "error": "Loop Detected",
    "exception": "java.lang.ArrayIndexOutOfBoundsException",
    "message": "-4",
    "path": "/crud/emps",
    "ext": {
        "msg": "-4"
    },
    "company": "mh"
}

先使用springmvc的全局异常处理机制,将页面跳转到 /error请求中;

/**
     * 带自适应的异常处理机制,自定义实现数据封装组件,具体的看DefaultErrorAttributes的使用
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public String handleExceptionbySb(Exception e, HttpServletRequest request) {
        //设定我们自定义异常的状态码,会根据这个状态码去寻找错误页面
        request.setAttribute("javax.servlet.error.status_code", 508);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("msg", e.getMessage());
        request.setAttribute("extendMsg",map);
        System.out.println("..................");
        return "forward:/error"; //转发到/error请求
    }

需要注意的是,要设置状态码,否则转发过去后,状态码是200,无法成功,而且状态码必须是400/500这些,否则不成功,设置状态码是为了自定义跳转的页面。这里定义了一个508,是让controller的错误跳转到自定义的错误页面。在这里我们可以向request域中存入我们要在错误中显示的信息。

重新添加 ErrorAttributes组件,替换掉springboot自带的错误解析组件。

/**
 * 自定义实现错误信息的封装,我们继承了DefaultErrorAttributes,
 * 在他的基础上实现我们的扩展信息。
 *
 * @author: mahao
 * @date: 2019/11/23
 */
@Component
public class ExtendErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        //取出旧的信息
        Map<String, Object> attributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        //取出扩展的信息,这些扩展信息是springmvc的全局异常页面跳转时放进去的数据
        Object extendMsg = requestAttributes.getAttribute("extendMsg", RequestAttributes.SCOPE_REQUEST);
        attributes.put("ext", extendMsg);
        attributes.put("company", "mh");
        return attributes;
    }
}

上面就是显示全部了。

如果想要看懂如何实现的自定义json数据,需要看懂springboot的异常处理机制,以及三个组件的作用。

代码地址:
https://github.com/mashenghao/springboot-learn/tree/master/web-crud

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值