SpringBoot系列_05_SpringBoot与Web开发

SpringMVC相关配置

自动配置原理

现状1:以前要访问jpg\css、js 等这些静态资源文件,需要在web.xml配置 ,在springboot不需要配置,只需要放在约定文件夹中就可以(约定大于配置)
原理1:WebMvcAutoConfiguration中做了以下配置,从而实现当访问/webjars/** 时就会去classpath:/META-INF/resources/webjars/ 对应进行映射

public void addResourceHandlers(ResourceHandlerRegistry registry) {
     if (!this.resourceProperties.isAddMappings()) {
         logger.debug("Default resource handling disabled");
     } else {
         Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
         CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
         if (!registry.hasMappingForPattern("/webjars/**")) {
             this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
         }

         String staticPathPattern = this.mvcProperties.getStaticPathPattern();
         if (!registry.hasMappingForPattern(staticPathPattern)) {
             this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
         }

     }
 }

现状2:静态资源文件放在以下路径"classpath:/META-INF/resources/", “classpath:/resources/”, “classpath:/static/”, "classpath:/public/"便可访问
原理2:WebMvcAutoConfiguration中配置了静态资源的映射地址,可以通过spring.resources.static‐locations属性来指定具体静态资源地址

String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

public String[] getStaticLocations() { 
	return this.staticLocations; 
}

this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

现状3:controller中可以直接返回html的视图,默认html资源地址是"classpath:/META-INF/resources/", “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”,可以通过设置spring.mvc.view.prefix和spring.mvc.view.suffix来设置静态资源前缀和后缀
原理3:WebMvcAutoConfiguration中配置了默认视图

@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.mvcProperties.getView().getPrefix());
    resolver.setSuffix(this.mvcProperties.getView().getSuffix());
    return resolver;
}
//配置properties文件
#spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.html
//示例代码
@Controller
@RequestMapping("/skill")
public class skillController {

    @GetMapping("/hello")
    public String hello3(){
        return "/hello";
    }

}

现状4:如果配置了@EnableWebMvc的话,那么SpringMVC的所有自动配置都将失效
原理4:EnableWebMvc 中@Import({DelegatingWebMvcConfiguration.class}),而WebMvcAutoConfiguration 中配置了@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

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


@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

定制配置

配置1:定制视图控制器、拦截器和跨域配置(跨域配置还可通过@CrossOrigin直接在控制类或者方法指定配置)

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    /**
     * 添加视图控制器
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/testView").setViewName("hello");
    }

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

    /**
     * 全局CORS配置
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/user/*") // 映射服务器中那些http接口运行跨域访问
                .allowedOrigins("http://localhost:8081") // 配置哪些来源有权限跨域
                .allowedMethods("GET","POST","DELETE","PUT"); // 配置运行跨域访问的请求方法
    }
}
public class TimeInterceptor implements HandlerInterceptor {

    LocalDateTime begin;

    Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        begin = LocalDateTime.now();
        logger.info("当前请求:"+request.getRequestURI()+"开始运行!");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LocalDateTime end = LocalDateTime.now();

        Duration between = Duration.between(begin, end);

        logger.info("当前请求:"+request.getRequestURI()+"耗时"+between+"毫秒!");
    }
}

Json开发

常用注解

@JsonIgnore
进行排除json序列化,将它标注在属性上将不会进行json序列化

@JsonFormat(pattern = “yyyy-MM-dd hh:mm:ss”,locale = “zh”)
进行日期格式化

@JsonInclude(JsonInclude.Include.NON_NULL)
当属性值为null时则不进行json序列化

@JsonProperty(“uname”)
来设置别名

自定义转化

@JsonComponent 
public class UserJsonCustom { 
	public static class Serializer extends JsonObjectSerializer<User> { 
		@Override 
		protected void serializeObject(User user, JsonGenerator jgen, SerializerProvider provider) throws IO Exception { 
			jgen.writeObjectField("id",user.getId()); 
			//{"id","xxx"}
			jgen.writeObjectField("uname","xxxxx"); 
			/*jgen.writeFieldName(""); 单独写key 
			jgen.writeObject(); 单独写value 
			*/ 
			// 1. 一次查不出完整的数据返回给客户端, 需要根据需求去做一些个性化调整 
			// 2. 根据不同的权限给他返回不同的序列化数据 
		} 
	} 
	
    public static class Deserializer extends JsonObjectDeserializer<User> { 
    	@Override 
     	protected User deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException { 
	      	User user=new User(); 
	       	user.setId(tree.findValue("id").asInt()); 
	        return user; 
        } 
    } 
}

国际化

国际化自动配置类

MessageSourceAutoConfiguration是国际化自动配置类

国际化实现步骤

①添加国际化资源文件
在这里插入图片描述

②配置资源文件位置
Springboot默认的国际化资源文件位置是类路径下的messages文件夹
在这里插入图片描述
也可以通过spring.messages.basename属性配置国际化资源文件位置

spring.messages.basename=i18n.message

在这里插入图片描述
如果找不到对应的资源文件,那么MessageSourceAutoConfiguration自动配置类将不生效,原理如下:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnMissingBean(
    name = {"messageSource"},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
//@Conditional 自定义条件匹配 会传入一个实现了Condition接口的类——ResourceBundleCondition
// ResourceBundleCondition 会重写matches, 自定义匹配规则 , 如果该方法返回true 就匹配成功
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

matches

public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String classOrMethodName = getClassOrMethodName(metadata);

    try {
        ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
        this.logOutcome(classOrMethodName, outcome);
        this.recordEvaluation(context, classOrMethodName, outcome);
        return outcome.isMatch();
    } catch (NoClassDefFoundError var5) {
        throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
    } catch (RuntimeException var6) {
        throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
    }
}

getMatchOutcome

public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
    ConditionOutcome outcome = (ConditionOutcome)cache.get(basename);
    if (outcome == null) {
        outcome = this.getMatchOutcomeForBasename(context, basename);
        cache.put(basename, outcome);
    }

    return outcome;
}

getMatchOutcomeForBasename

private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
    Builder message = ConditionMessage.forCondition("ResourceBundle", new Object[0]);
    String[] var4 = StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename));
    int var5 = var4.length;

    for(int var6 = 0; var6 < var5; ++var6) {
        String name = var4[var6];
        // 根据basename获取 该类路径下的所有propeties的资源文件
        Resource[] var8 = this.getResources(context.getClassLoader(), name);
        int var9 = var8.length;

        for(int var10 = 0; var10 < var9; ++var10) {
            Resource resource = var8[var10];
            // 只要basename的类路径下有资源文件就会匹配成功
            if (resource.exists()) {
                return ConditionOutcome.match(message.found("bundle").items(new Object[]{resource}));
            }
        }
    }

    return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}

③解析请求头中的accept-language

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
    prefix = "spring.mvc",
    name = {"locale"}
)
public LocaleResolver localeResolver() {
	// 当配置spring.mvc.locale‐resolver=fiexd就会使用配置文件中的本地化语言:spring.mvc.locale=en_US 就可以设死本地化语言
    if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    } else {
    	// 默认就是使用AcceptHeaderLocaleResolver 作为本地化解析器
        AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
        //spring.mvc.locale=en_US 作为默认的本地化语言
        localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
        return localeResolver;
    }
}
public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 当Accept‐Languag为null 才会使用使用配置文件中设置的locale:spring.mvc.locale
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
    	// 当Accept‐Languag不为null时,使用request.getLocale();
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

④解析url参数中?local=,随意切换本地语言,进行缓存

添加CookieLocaleResolver,代替自动化配置中的AcceptHeaderLocaleResolver

/**
*配置国际化处理类
* @return
*/
@Bean
public LocaleResolver localeResolver() {
   CookieLocaleResolver localeResolver = new CookieLocaleResolver();
   localeResolver.setCookieMaxAge(60*60*24*30);
   localeResolver.setCookieName("locale");
   return localeResolver;
}

配置LocaleChangeInterceptor拦截器获取请求中的local=,设置到CookieLocaleResolver 中

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LocaleChangeInterceptor())
            .addPathPatterns("/**");
}

⑤通过messageResource 获取国际化信息
可以通过ResourceBundleMessageSource的getMessage方法获取国际化字段信息,获取local時,使用LocaleContextHolder.getLocale(),LocaleContextHolder就是一个Locale持有器,SpringMVC底层会自动将LocaleResovler中的语言设置进去

@Autowired
ResourceBundleMessageSource messageSource;

@GetMapping("/testLocale")
@ResponseBody
public String testLocale(HttpServletRequest request){
    String message = messageSource.getMessage("user.query.success", null, LocaleContextHolder.getLocale());
    return message;
}

异常处理

异常处理自动配置类

ErrorMvcAutoConfiguration是Springboot统一异常处理自动配置类

统一异常处理流程

当异常发生时,如果请求头是text/html,那么请求会交给errorHtml方法处理,其他的交给error方法处理

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {

    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //调用DefaultErrorViewResolver的resolveErrorView方法匹配返回页面然后组装ModelAndView 
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        //如果找不到对应的返回视图,那么直接使用springboot默认错误页面
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
    
    //查询需要从request中获取的错误信息,可以在配置文件中设置server.error.xxx来配置
	protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }

        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(new Include[]{Include.STACK_TRACE});
        }

        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(new Include[]{Include.MESSAGE});
        }

        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(new Include[]{Include.BINDING_ERRORS});
        }

        return options;
    }

DefaultErrorViewResolver的resolveErrorView方法解析

public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
   //查询是否有对应异常的返回视图
   ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
    	//如果找不到返回视图,那么查询是否有4xx和5xx的通用返回视图
        modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
    }

    return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
    String errorViewName = "error/" + viewName;
    //查询视图模板中是否有对应的返回视图
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
    //如果视图模板中没有对应返回视图,那么从this.resourceProperties.getStaticLocations();中对应的资源位置找
    //默认是"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
    return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    String[] var3 = this.resourceProperties.getStaticLocations();
    int var4 = var3.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String location = var3[var5];

        try {
            Resource resource = this.applicationContext.getResource(location);
            resource = resource.createRelative(viewName + ".html");
            if (resource.exists()) {
                return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
            }
        } catch (Exception var8) {
        }
    }

    return null;
}

在这里插入图片描述
在这里插入图片描述

定制统一异常处理

方式一:创建一个继承AbstractErrorController的类就可以了,然后重写errorHtml和error方法就可以了

@Controller 
@RequestMapping("/error") 
public class CustomErrorController extends AbstractErrorController {

方式二:使用@ControllerAdivce注解

 @ControllerAdvice
 public class GeneralExceptionHandler {
 
	@ExceptionHandler(Exception.class)
	public ModelAndView handleException(HttpServletRequest request,HttpServletResponse reponse, Exception ex,HandlerMethod handle){
		System.out.println("全局异常处理");
		// 如果当前请求是ajax就返回json 
		// 1.根据用户请求的处理方法,是否是一个返回json的处理方法 
		//RestController restAnnotation = handle.getClass().getAnnotation(RestController.class); // 获得类上 面的某个注解 
		//ResponseBody responseBody = handle.getMethod().getAnnotation(ResponseBody.class);//获得方法上面的某 个注解 
		// if(restAnnotation!=null || responseBody!=null){ } 
		// 2.可以根据请求头中的类型Content‐Type包含application/json
		if(request.getHeader("Accept").indexOf("application/json")>1){
			// 可以直接输出json reponse.getWriter().write(); 或者集成jackson
			// 集成jackson的方式:
			ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
			// 通常会根据不同的异常返回不同的编码
			modelAndView.addObject("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
			modelAndView.addObject("message",ex.getMessage());
			return modelAndView;
		}else{
			ModelAndView modelAndView = new ModelAndView(); 
			modelAndView.setViewName("error"); 
			modelAndView.addObject("ex", ex); 
			StringWriter sw = new StringWriter(); 
			PrintWriter pw = new PrintWriter(sw); 
			ex.printStackTrace(pw); 
			return modelAndView;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值