SpringBoot(三)-Web开发

一、静态资源映射

1、webjars

webjars官网

被打成jar包的静态资源

/webjars/**:  
所有webjars资源都去 classpath:/META-INF/resources/webjars/  下找

根据webjars官网,添加jquery依赖

访问的时候,只需要写webjars下面的资源名称即可
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.0</version>
</dependency>

资源存放路径如下:
在这里插入图片描述

2、访问当前项目编写的静态资源

“/**”:访问当前文件夹的任何资源(静态资源文件夹)

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

3、欢迎页

静态资源文件夹里的所有index.html都会被映射到/**

4、图标

所有的**/favicon.ico都在静态资源文件夹里找

二、thymeleaf

因为SpringBoot使用了内嵌的tomcat,使用jsp不太容易,官方推荐使用thymeleaf模板引擎

1、引入依赖

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

2、更改版本

因为boot的启动器已经设置好了关于thymeleaf的版本(2.x),所以改版本需要覆盖掉他原有的

   <properties>
        <springboot-thymeleaf.version>3.0.9.RELEASE</springboot-thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
    </properties>

3、视图解析

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

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

	public static final String DEFAULT_SUFFIX = ".html";

会解析 classpath:/templates/ 下的html视图

在这里插入图片描述

4、thymeleaf使用

(1)添加命名空间
xmlns:th="http://www.thymeleaf.org"

不加命名空间不会影响程序,但是不会有提示功能

(2)常用语法
    @RequestMapping("/success")
    public String success(Model model,Map<String,Object> map){

        model.addAttribute("hello","hello3");
        model.addAttribute("users",Arrays.asList("张三","李四","王五"));
        map.put("list",Arrays.asList("数据结构","计组","计网","操作系统"));
        return "success";
    }
<body>
<div th:text="${hello}"></div>
<ul>
    <li th:each="user:${users}" th:text="${user}"></li>
</ul>
<ul>
    <li th:each="obj:${list}">[[${obj}]]</li>
</ul>
</body>

三、MVC配置

WebMvcAutoConfiguration会将自动配置和自己拓展的配置全部组合起来使用

1、拓展mvc配置

配置类添加@Configuration注解,实现WebMvcConfigurer接口表明就是MVC其中的一个组件了

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("success");
    }
}

2、全面接管MVC

在配置类中添加@EnableWebMvc注解
该注解:如果容器中有自己配置的mvc,则自动配置取消,只使用自己的
但不推荐使用,因为默认的自动配置比较全,最好是自动配置和拓展配置组合使用

四、引入资源

  1. 静态资源(公共资源css、js、Jquery、图片等)放在static文件夹下
  2. 视图页面放在template交给thymeleaf模板引擎进行解析
  3. 配置首页
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
   @Override
   public void addViewControllers(ViewControllerRegistry registry) {
       registry.addViewController("/").setViewName("index");
       registry.addViewController("/index.html").setViewName("index");
   }
}

WebMvcConfigurerAdapter已经被淘汰,推荐方法是实现WebMvcConfigurer

  1. 资源路径通过thymeleaf绑定,因为资源路径开始指定的绝对路径,如果资源位置发生改变(contextPath),thymeleaf会自动加上
      href="asserts/css/bootstrap.min.css"
           ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
<link th:href="@{/asserts/css/bootstrap.min.css}"   rel="stylesheet">

或者可以通过webjars直接访问META-INF下的webjars静态资源
@{/webjars/bootstrap/5.0.1/css/bootstrap.css}

如果我更改了context-path

server.servlet.context-path=/web

在这里插入图片描述

通过thymeleaf绑定的资源路径会自动改变,非常方便

五、国际化

1、编写国际化配置文件

文件格式:xxx_语言_国家代码
在这里插入图片描述
在这里插入图片描述

2、配置文件指定国际化文件名

(1)源码
    @Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

	@Bean
	public MessageSource messageSource(MessageSourceProperties properties) {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		if (StringUtils.hasText(properties.getBasename())) {
			//设置国际化文件基础名(去掉语言国家代码)
			messageSource.setBasenames(StringUtils
					.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
		}
		if (properties.getEncoding() != null) {
			messageSource.setDefaultEncoding(properties.getEncoding().name());
		}
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
(2)配置
spring.messages.basename=i18n.login

2、页面获取国际化文件的值

message采用#{}获取值

th:text="#{login.tip}"
th:text="#{login.username}"
th:placeholder="#{login.username}"
<input type="checkbox" value="remember-me"> [[#{login.remember}]]

页面根据语言信息切换了国际化

3、原理

localeResolver(区域信息解析)

WebMvcAutoConfiguration帮我们配置了localeResolver组件

        @Override
		@Bean
		//若容器中没有自己配置,就生效
		@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
		@SuppressWarnings("deprecation")
		public LocaleResolver localeResolver() {
			if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
				return new FixedLocaleResolver(this.webProperties.getLocale());
			}
			if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
				return new FixedLocaleResolver(this.mvcProperties.getLocale());
			}
			AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
			Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
					: this.mvcProperties.getLocale();
			localeResolver.setDefaultLocale(locale);
			return localeResolver;
		}

AcceptHeaderLocaleResolver根据请求头里的语言信息,切换了国际化

我们可以通过@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)特性,
若没有配置,则该组件生效;若配置了,则用用户自己的
自己配置locale来切换国际化(点中文和英文按钮切换)

4、自己配置国际化组件

(1)传识别参数
			<a class="btn btn-sm" th:href="@{/index.html(languege='ch_Cn')}">中文</a>
			<a class="btn btn-sm" th:href="@{/index.html(languege='en_Us')}">English</a>
(2)编写LocalResolver
//传区域信息进行解析
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        System.out.println(1);
        Locale locale=Locale.getDefault();//如果参数没有。就用系统默认的
        String languege = request.getParameter("languege");
        System.out.println(2);
        if(!StringUtils.isEmpty(languege)){
            String[] s = languege.split("_");
                           //语言  国家代码
            System.out.println(s[0]+s[1]);
            locale=new Locale(s[0],s[1]);
        }
        System.out.println(3);
        return locale;
    }

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

    }
}

Locale locale=Locale.getDefault();//如果参数没有。就用系统默认的(根据请求头语言参数判断)
如果返回locale为空会出现异常

(3)交给容器
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
    //容器托管
    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}

用户自己配置了区域信息解析器组件,自动配置就不生效了

配置好后重启服务器,就可以通过按钮切换国际化

六、登录拦截

1、开发技巧

开发过程中,修改的thymeleaf页面要实时生效:

  1. 禁用thymeleaf缓存
spring.thymeleaf.cache=false
  1. 页面修改后ctrl+f9重新编译

2、视图解析


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
  		registry.addViewController("/").setViewName("login");
        registry.addViewController("/index.html").setViewName("login");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/main").setViewName("dashboard");
    }

3、controller

@Controller
public class LoginController {

    @PostMapping("/user/login")
    public String login(String username, String password, Map<String,Object> map, HttpSession session){
        if(username.equals("admin")&&password.equals("12345")){
            session.setAttribute("token",username);
            return "redirect:/main";
        }
        map.put("msg","用户密码错误");
        return "login";
    }
}

登录成功,防止表单重复提交,可以使用重定向跳转

4、信息显示

                                               如果msg不为空 显示msg
<p style="color:red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

5、编写拦截器

public class LoginHandlerIntercepter implements HandlerInterceptor {
	//执行请求前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object token = request.getSession().getAttribute("token");
        if(token==null){
            //登录失败
            request.setAttribute("msg","没有权限");//不要用session,thymeleaf不能通过strings判定session
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else{
            //登录成功
            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 {

    }
}

不要session存放msg,strings.isEmpty不能判断session

6、WebMvcConfigurer添加拦截器

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
                                               //使用自己编写的拦截器拦截所有资源
        registry.addInterceptor(new LoginHandlerIntercepter()).addPathPatterns("/**").
        		//排除入口和登录
                excludePathPatterns("/user/login","/index.html","/").
                //排除静态
                excludePathPatterns("/asserts/**");
    }

注意:springboot1不用排除静态,但是现在的版本好像要自己排除了

7、Restful风格

CRUD满足Rest风格
URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
同样的资源名称,根据资源标识和请求方式的不同进入不同请求
在这里插入图片描述
在这里插入图片描述

七、抽取公共页

1、公共片段

<nav th:fragment="topbar" class="col-md-2 d-none d-md-block bg-light sidebar">

给公共片段取名
 th:fragment="topbar"
公共页替换           ~{模板名::片段名}
<div th:replace="~{dashboard::topbar}"></div>
取id sidebar
<nav id="sidebar" class="col-md-2 d-none d-md-block bg-light sidebar">

~{模板名::选择器}

                       ~{模板名::#id名}
<div th:replace="~{dashboard::#sidebar}"></div>

2、点击高亮

通过公共片段添加判断语句来增加高亮
th:class="${activeUri=='main'?'nav-link active':'nav-link'}"

main高亮
<div th:replace="~{commons/bar::#sidebar(activeUri='main')}"></div>
emps高亮
<div th:replace="~{commons/bar::#sidebar(activeUri='emps')}"></div>

3、日期数据

mvc默认的格式yyyy/MM/dd
其他的格式会报错
这时候就需要配置文件中添加格式

spring.mvc.date-format=yyyy-MM-dd

4、添加修改两用页面

  1. 去添加页面get
  2. 添加请求post
  3. 去修改同去添加
  4. 修改请求用put
   @GetMapping("/emp")
    public String toAdd(Model model){
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);
        return "emp/add";
    }
    @PostMapping("/emp")
    public String add(Employee e){
        employeeDao.save(e);
        return "redirect:/emps";
    }

    @GetMapping("/emp/{id}")
    public String toUpdate(@PathVariable("id") Integer id, Model model){
        Employee employee = employeeDao.get(id);
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("emp",employee);
        model.addAttribute("depts",departments);
//        添加修改两用
        return "/emp/add";
    }

    @PutMapping("/emp")
    public String update(Employee e){
        employeeDao.save(e);
        return "redirect:/emps";
    }
						<!--发送put请求修改员工数据-->
						<!--
						1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
						2、页面创建一个post表单
						3、创建一个input项,name="_method";值就是我们指定的请求方式
						-->
						<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<!--						修改页面需要传id-->
						<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
修改put请求,需要添加<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
因为修改页面才需要传回emp回显,所以emp!null为修改,==null为添加


回显需要带回value,同样需要判断,因为添加页面emp==null,绑定会报错
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" 
										th:value="${emp!=null}?${emp.lastName}">
										
<option th:selected="${emp.getDepartment().getId()==dept.getId()}" 
				th:value="${dept.getId()}" 
				th:each="dept:${depts}" 
				th:text="${dept.getDepartmentName()}">
</option>

5、删除

delete请求删除员工

    @DeleteMapping("/emp/{id}")
    public String delete(@PathVariable("id") Integer id){
        System.out.println(id);
        employeeDao.delete(id);
        return "redirect:/emps";
    }
	给全局attr传action参数(建的表单没有指定action,delete不能直接访问)
	<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger delBtn">删除</button>

				delete请求,在外建表单(专门为delete请求设置),为按钮绑定事件
				<form id="deleteEmpForm" method="post">
					<input type="hidden" name="_method" value="delete"/>
				</form>
				
				为按钮绑定事件
		<script>
			$(".delBtn").click(function () {
				// alert($(this).attr("del_uri"))
				$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
				return false;
			});
		</script>

发现访问该delete请求一直405,配置文件添加
解释

spring.mvc.hiddenmethod.filter.enabled = true

因为SpringBoot1的hiddenmethod过滤器是默认开启的
但是SpringBoot2的hiddenmethod过滤器是默认关闭的,hiddenmethod过滤器关闭则表单提交的"_method"就不会起作用,就不能访问delete

<input type="hidden" name="_method" value="delete"/>

八、定制错误

1、错误请求头

浏览器错误

在这里插入图片描述

客户端错误

在这里插入图片描述

2、原理:ErrorMvcAutoConfiguration

	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}

	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				errorViewResolvers.orderedStream().collect(Collectors.toList()));
	}

	@Bean
	public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
		return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
	}

		@Bean
		@ConditionalOnBean(DispatcherServlet.class)
		@ConditionalOnMissingBean(ErrorViewResolver.class)
		DefaultErrorViewResolver conventionErrorViewResolver() {
			return new DefaultErrorViewResolver(this.applicationContext, this.resources);
		}
(1) DefaultErrorAttributes

通过解析获取了一堆属性timestamp、status、error、exception、message等

(2) BasicErrorController:处理默认error请求
	//浏览器返回错误ModelAndView进行解析
	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}
	//客户端返回json
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}

该组件是通过读取请求头来判断是浏览器还是客户端

(3) ErrorPageCustomizer
	@Value("${error.path:/error}")
	private String path = "/error";

一旦出现4xx或者5xx错误,ErrorPageCustomizer就会生效,来到/error请求

(4) DefaultErrorViewResolver
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), 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<String, Object> model) {
		//如error/404     .html
		String errorViewName = "error/" + viewName;
		//如果有模板引擎,页面地址就交给模板引擎解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		//如果没有模板引擎就调用下面的方法
		return resolveResource(errorViewName, model);
	}
	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resources.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				//寻找静态文件夹下的errorViewName
				resource = resource.createRelative(viewName + ".html");
				//如果有就返回
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		//没有返回空
		return null;
	}

3、如何定制错误

(1)定制错误页面

DefaultErrorViewResolver的上述方法

  1. 有模板引擎就解析template下的error文件夹下的4xx或者5xx.html
  2. 如果没有就找static下的error/4xx or 5xx static底下的数据不能被模板引擎渲染
  3. 如果都没有,就系统默认的

在这里插入图片描述
根据DefaultErrorAttributes可以获取错误信息

  1. timestamp:时间戳
  2. status:状态码
  3. error:错误提示
  4. exception:异常对象
  5. message:异常信息
    error:JSR303效验都在这
	时间戳信息
<h1 th:text="${timestamp}"></h1>
	状态码信息
<h1 th:text="${status}"></h1>
(2)定制错误JSON

高版本boot取不到exception和message

九、配置嵌入式Servlet容器

曾经的项目都是打成war包,将war包放入tomcat容器,在外部启动tomcat
现在的boot项目,使用的是嵌入式tomcat

1、servlet容器配置修改

(1)修改server有关配置
server.port=8081
server.servlet.context-path=/web

通用方式
server.xxx=
server.tomcat.xxx=
(2)编写EmbeddedServletContainerCustomizer

boot2好像不行了

十、修改SpringBoot默认配置

  1. SpringBoot在配置组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component),如果有就用用户自己配置的组件;没有才自动配置(自动配置带默认值);有些组件可以有多个(ViewResolve),就是将用户自己配置的和自动配置的组合起来使用
  2. SpringBoot中会有很多xxxConfiguration,帮助我们拓展配置
  3. SpringBoot会有xxxCustomizer,帮助我们进行定制配置
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值