springboot+thymleaf学习

本文详细介绍了Spring Boot中如何配置Thymeleaf模板引擎,包括静态资源处理、错误页面定制和异常处理。通过设置`spring.thymeleaf`属性禁用缓存,并配置Thymeleaf的前缀和后缀。同时,讲解了Spring Boot的静态资源映射,包括`/static/**`、`/resources/**`、`/public/**`等路径。在错误处理方面,讨论了`BasicErrorController`和`DefaultErrorViewResolver`的角色,以及如何自定义错误页面和异常处理器。此外,还提到了使用`@ControllerAdvice`进行全局异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:个人理解是springboot+thymleaf多用于前后端不分离的,thymleaf也是springboot推荐的模板引擎,结合紧密,所以这两个内容放在一起

springboot不带数据源启动时出错解决

注:启动类上加入数据源自动配置类即可

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MyblogApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyblogApplication.class, args);
    }
}

springboot静态资源访问出错

可能原因:
1、springboot2.x版本对静态资源有拦截,添加静态资源映射

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

2、静态资源文件路径错误

3、编译target文件中没有静态资源,clean清除后重新编译

thymleaf

注:
spring-boot-autoconfigure下的thymleaf文件夹中有一些自动配置相关信息
摘取一段ThymeleafProperties 源码,方便配置文件中thymleaf属性设置的理解
开发阶段:cache多禁用

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    private Integer templateResolverOrder;
    private String[] viewNames;
    private String[] excludedViewNames;
    private boolean enableSpringElCompiler;
    private boolean renderHiddenMarkersBeforeCheckboxes;
    private boolean enabled;
    private final ThymeleafProperties.Servlet servlet;
    private final ThymeleafProperties.Reactive reactive;
    //...
}

thymleaf的常见命令

参考自己平时使用总结:Thymeleaf常用知识点

springboot

springboot版本是2.3.4

注:
1、主要关注静态资源文件夹staticLocations,理解查找顺序
2、从上面依次往下面找。所有静态资源类似css、js、图标等都可以放在static下
3、注意下面的resources都是指resources根目录(有特殊标记)下的resources文件夹。
如果在资源根目录resources下没有新建resources文件夹,则会跳过。
除非修改静态路径了,那就稍微麻烦。

“classpath:/META-INF/resources/”,
“classpath:/resources/”,
“classpath:/static/”,
“classpath:/public/”

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    private String[] staticLocations;
    private boolean addMappings;
    private final ResourceProperties.Chain chain;
    private final ResourceProperties.Cache cache;

    public ResourceProperties() {
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS; // 这里
        this.addMappings = true;
        this.chain = new ResourceProperties.Chain();
        this.cache = new ResourceProperties.Cache();
    }
    
    public void setStaticLocations(String[] staticLocations) {
        this.staticLocations = this.appendSlashIfNecessary(staticLocations);
    }

    private String[] appendSlashIfNecessary(String[] staticLocations) {
        String[] normalized = new String[staticLocations.length];

        for(int i = 0; i < staticLocations.length; ++i) {
            String location = staticLocations[i];
            normalized[i] = location.endsWith("/") ? location : location + "/";
        }

        return normalized;
    }
    //...
}

注:WebMvcAutoConfiguration的静态资源webjars,以后引入一些js会用到

        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));
                }

            }
        }

扩展springMVC

注:
下面是WebMvcConfigurer接口,里面全部都是默认方法,可以重写实现想要扩展的功能。

不推荐利用WebMvcConfigurerAdapter 类,因为已废弃。可以看出WebMvcConfigurerAdapter 也是实现了WebMvcConfigurer接口。

@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer
public interface WebMvcConfigurer {
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }

    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }

    default void addFormatters(FormatterRegistry registry) {
    }

    default void addInterceptors(InterceptorRegistry registry) {
    }

    default void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    default void addCorsMappings(CorsRegistry registry) {
    }

    default void addViewControllers(ViewControllerRegistry registry) {
    }

    default void configureViewResolvers(ViewResolverRegistry registry) {
    }

    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    }

    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    }

    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

国际化配置

注:全局配置文件中可以配置spring.messages.basename 国际化文件路径

public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }
    //...
}
// spring.messages.basename  配置文件路径
public class MessageSourceProperties {
    private String basename = "messages";
    private Charset encoding;
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration cacheDuration;
    private boolean fallbackToSystemLocale;
    private boolean alwaysUseMessageFormat;
    private boolean useCodeAsDefaultMessage;

    public MessageSourceProperties() {
        this.encoding = StandardCharsets.UTF_8; // 默认编码utf8
        this.fallbackToSystemLocale = true;
        this.alwaysUseMessageFormat = false;
        this.useCodeAsDefaultMessage = false;
    }

注:可以在url连接上携带区域信息,比如?l=zh_CN

/**
 * 可以在url连接上携带区域信息
 */
public class MyLocaleResolver implements LocaleResolver {

    /**
     * l=zh_CN  l=en_US
     * Locale的构造函数,根据语言+国家。后面还有一个参数不明
     * public Locale(String language, String country) {
     *         this(language, country, "");
     *     }
     * @param request
     * @return
     */
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if(!StringUtils.isEmpty(l)){
            String[] split = l.split("_");
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    }
}

错误配置页面

注:
ErrorProperties 里面存在错误路径
BasicErrorController 控制
DefaultErrorViewResolver 默认错误视图解析

public class ErrorProperties {
    @Value("${error.path:/error}")
    private String path = "/error";
    private boolean includeException;
    private ErrorProperties.IncludeStacktrace includeStacktrace;
    private ErrorProperties.IncludeAttribute includeMessage;
    private ErrorProperties.IncludeAttribute includeBindingErrors;
    private final ErrorProperties.Whitelabel whitelabel;
    //...
}

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

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }
    //...
    // 这里存在两种返回方式,根据请求头如果是text/html,表示浏览器请求则返回html,否则返回json
        @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());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        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);
        }
    }
    //...
}

// 关注:resolveErrorView、resolve、resolveResource
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
    private static final Map<Series, String> SERIES_VIEWS;
    private ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final TemplateAvailabilityProviders templateAvailabilityProviders;
    private int order = 2147483647;
    //...
    
    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())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
    //默认SpringBoot可以去找到一个页面? error/404
        String errorViewName = "error/" + viewName;
        //模板引擎可以解析这个页面地址就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //模板引擎可用的情况下返回到errorViewName指定的视图地址
        //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
        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;
    }
    //...
}    

总结:
1、有模板引擎的情况下error/状态码;
1.1 将错误页面命名为 errorCode.html 放在模板引擎templates/error下,发生此状态码的错误就会来到对应的页面
1.2 使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)

2、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找
3、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
在这里插入图片描述

定制错误信息或者说做一些更改
首先,在控制层添加一个控制通知来处理传到控制层的异常

@ControllerAdvice
public class MyExceptionHandler {

    //1、浏览器客户端返回的都是json
//    @ResponseBody
//    @ExceptionHandler(UserNotExistException.class)
//    public Map<String,Object> handleException(Exception e){
//        Map<String,Object> map = new HashMap<>();
//        map.put("code","user.notexist");
//        map.put("message",e.getMessage());
//        return map;
//    }
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx ,若不设置状态码,那就被认为此页面已经异常处理了,返回的就是200
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message","用户出错啦");
        request.setAttribute("ext",map);
        //转发到/error
        return "forward:/error";
    }
}

其次,注册自定义的MyErrorAttributes ,它继承DefaultErrorAttributes
注意:
DefaultErrorAttributes 类中
getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace)已经弃用
替换方法:
getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options)

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        // 这里相当于将默认处理的信息拿出来,添加点自定义信息
        Map<String, Object> map =  super.getErrorAttributes(webRequest, options);
        map.put("company","atguigu");
        //我们的异常处理器携带的数据
        Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }

/*    // 以前的处理方法,因为已经deprecated,所以换成上面的
        // 返回值的map就是页面和json能获取的所有字段
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes((WebRequest) requestAttributes, includeStackTrace);
        map.put("company","atguigu");
        //我们的异常处理器携带的数据
        Map<String,Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }*/
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值