SpringBoot提供了
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
为我们提供了嵌入式的Tomcat以及SpringMVC的依赖
和Web相关的自动配置存储在spring-boot-autoconfigure-1.5.3.RELEASE.jar的org.springframework.boot.autoconfigure.web下
开发过程中有什么配置有疑惑了,就去找个包下找相关的自动配置类进行查看即可
Spring Boot中推荐使用Thymeleaf作为模板引擎
内嵌的Tomcat、Jetty是不支持以jar的形式运行 JSP 的
Ubdertow不支持jsp
Thymealeaf是一个Java类库,是一个xml、xhtml、html5的模板引擎,可以作为Web应用的View层
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.zln.learning.spbootgroupId>
<artifactId>spboot-demo02artifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>spboot-demo02name>
<description>Demo project for Spring Bootdescription>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.3.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
当前导入的Thymealeaf版本仍旧是2,
如果想要使用Thymealeaf3,就需要自己配置
<properties>
<thymeleaf.version>3.0.2.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.4thymeleaf-layout-dialect.version>
properties>
仅添加这两个属性配置即可,starter仍旧用的是thymeleaf
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templatemode.*;
@Configuration
public class ThymeleafConfig implements ApplicationContextAware{
private static final String UTF8 = "UTF-8";
private ApplicationContext applicationContext;
private String[] array(String ...args) {
return args;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private TemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver);
return engine;
}
private ITemplateResolver htmlTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("classpath:/templates/");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
@Bean
public ViewResolver htmlViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine(htmlTemplateResolver()));
resolver.setContentType("text/html");
resolver.setCharacterEncoding(UTF8);
resolver.setViewNames(array("*.html"));
resolver.setCache(false);
return resolver;
}
private ITemplateResolver cssTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("classpath:/templates/");
resolver.setTemplateMode(TemplateMode.CSS);
return resolver;
}
@Bean
public ViewResolver cssViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine(cssTemplateResolver()));
resolver.setContentType("text/css");
resolver.setCharacterEncoding(UTF8);
resolver.setViewNames(array("*.css"));
resolver.setCache(false);
return resolver;
}
private ITemplateResolver javascriptTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("classpath:/templates/");//Thymeleaf的HTML文件放在此
resolver.setTemplateMode(TemplateMode.JAVASCRIPT);
return resolver;
}
@Bean
public ViewResolver javascriptViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine(javascriptTemplateResolver()));
resolver.setContentType("application/javascript");
resolver.setCharacterEncoding(UTF8);
resolver.setViewNames(array("*.js"));
resolver.setCache(false);
return resolver;
}
}
见:ThymeleafProperties 类
默认静态首页
如果不想要使用Thymealeaf作为View层,仍旧使用JSP的话,
那么创建好Spring Boot 的 Web 工程后,再导入
<dependency>
<groupId>org.apache.tomcat.embedgroupId>
<artifactId>tomcat-embed-jasperartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jsp.jstlgroupId>
<artifactId>jstl-apiartifactId>
<version>1.2version>
dependency>
在src/main/resources/application.properties文件中配置JSP和传统Spring MVC中和view的关联
spring.view.prefix=/WEB-INF/views/
spring.view.suffix=.jsp
创建src/main/webapp/WEB-INF/views目录,JSP文件就放这里
注意
以上代码pom.xml中的javax.servlet.jsp.jstl是用于支持JSP标签库的,
在Web2.5的容器中没有问题,但当你的容器是Web3.0或以上版本时,就会出问题。这是个非常坑爹的问题。
javax.servlet.jsp.jstl会自动加载依赖servlet-api-2.5.jar,
而且会在实际运行时把支持Web3.0的3.1版本的javax.servlet-api覆盖掉。
即使你在pom.xml显示的在加入3.1版本的javax.servlet-api也没用。导致SpringBoot应用抛出Runtimeexception运行错误。
这是一个不可调和的矛盾,要么不用javax.servlet.jsp.jstl,要么不用Web3.0。
但绝大多数情况下,jstl标签库不是必须的,而Web3.0是必须的。
替代方式就是不用JSP,改用Themeleaf吧
思考: 在前端框架如此兴盛的当下,还有必要使用HTML模板吗?
Web相关配置
详见代码:WebMvcAutoConfiguration、WebMvcProperties
自动配置的ViewResolver
自动配置的静态资源
类路径下的/static、/public、/resources、/META-INF/resources ,
这几个文件夹下的静态文件直接映射为/**,
一般放置js、css文件
可以通过http://localhost:8080/**来访问
webjar的 /META-INF/resources/webjars/ 下的的静态文件映射为 /webjar/**
Thymeleaf具体怎么使用,见Thymeleaf部分的笔记
Formatter和Converters是用于类型转化的
只要我们定义了Converter、GenericConverter、Formatter接口的实现类,
这些Bean就会自动注册到Spring MVC中
编写好自定义的HttpMessageConverters后,注册为Bean,Spring Boot会自动注册
HttpMessageConverters是干嘛的?
答:请求的json字符串被@RequestBody转化为对象,就是HttpMessageConverters干的
@Bean
public HttpMessageConverters customerConverters() {
HttpMessageConverter<?> h1 = ....
HttpMessageConverter<?> h2 = ....
return new HttpMessageConverters(h1,h2);
}
Formatter是对表单请求进行数据格式转换,
如果是JSON数据,可以直接在对象上使用@JsonFormatter、@JsonSerialize等注解
如果Spring Boot的默认配置不符合要求,
则可以通过一个配置类加上@EnableWebMvc注解实现完全自己控制的MVC配置
如何既保留SpringBoot自动配置的便利,又增加自己额外的配置呢?
这个配置类需要继承WebMVCConfigurerAdapter,无需@EnableWebMvc
当然,配置类上@Configuration是肯定要添加的
有两种方法:
@Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new MyServlet(),"/xx/*");
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean reg = new FilterRegistrationBean();
reg.setFilter(new MyFilter());
return reg;
}
@Bean
ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean(){
return new ServletListenerRegistrationBean(MyListener)(new MyListener());
}
spring:
messages:
# 国际化文件
basename: i18n/messages/messages
messages.properties
messages_en_US.properties
messages_zh_CN.properties
package com.zhidianfan.pig.yd.config;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
/**
* 自定义国际化语言解析器
*/
public class MyLocaleResolver implements LocaleResolver {
private static final String I18N_LANGUAGE = "i18n_language";
private static final String I18N_LANGUAGE_SESSION = "i18n_language_session";
@Override
public Locale resolveLocale(HttpServletRequest req) {
String i18n_language = req.getParameter(I18N_LANGUAGE);
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(i18n_language)) {
String[] language = i18n_language.split("_");
locale = new Locale(language[0], language[1]);
//将国际化语言保存到session
HttpSession session = req.getSession();
session.setAttribute(I18N_LANGUAGE_SESSION, locale);
} else {
//如果没有带国际化参数,则判断session有没有保存,有保存,则使用保存的,也就是之前设置的,避免之后的请求不带国际化参数造成语言显示不对
// 在分布式环境中,使用Redis代替Session,Key:账户+language
HttpSession session = req.getSession();
Locale localeInSession = (Locale) session.getAttribute(I18N_LANGUAGE_SESSION);
if (localeInSession != null) {
locale = localeInSession;
}
}
return locale;
}
@Override
public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {
}
}
package com.zhidianfan.pig.yd.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* web端配置信息
*
* @author danda
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* 配置自己的国际化语言解析器
*
* @return
*/
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
/**
* 配置自己的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
请求中,带上 &i18n_language=zh_CN
用于设置当前语言环境
使用国际化
@Autowired
private MessageSource messageSource;
String res = messageSource.getMessage("hello", null, locale);
将session存储改为Redis存储
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.zhidianfan.pig.yd.utils.UserUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.LocaleResolver;
/**
* 自定义国际化语言解析器
*/
public class MyLocaleResolver implements LocaleResolver {
private static final String I18N_LANGUAGE = "i18n_language";
private Logger log = LoggerFactory.getLogger(getClass());
private RedisTemplate redisTemplate;
private String local = "zh_CN";//默认环境 en_US 英文环境
private UserUtils userUtils;
public MyLocaleResolver(RedisTemplate redisTemplate, UserUtils userUtils) {
this.redisTemplate = redisTemplate;
this.userUtils = userUtils;
}
public MyLocaleResolver(RedisTemplate redisTemplate, String local) {
this.redisTemplate = redisTemplate;
this.local = local;
}
@Override
public Locale resolveLocale(HttpServletRequest req) {
String i18n_language = req.getParameter(I18N_LANGUAGE);
Locale locale = null;
String username = userUtils.getUserName();//用户名为唯一键
String k = "LANGUAGE:" + username;
if (!StringUtils.isEmpty(i18n_language)) {//客户请求中设置了值,以设置的国际化信息为准
String[] language = i18n_language.split("_");
locale = new Locale(language[0], language[1]);
redisTemplate.opsForValue().set(k, locale);
log.info("设置当前用户的语言环境:{}", i18n_language);
} else {//客户未在请求中设置国际化参数
Locale v = (Locale) redisTemplate.opsForValue().get(k);
if (v != null) {
locale = v;
log.info("从缓存中获取语言环境:{}", v.getLanguage());
} else {
//设置默认值
String[] language = local.split("_");
locale = new Locale(language[0], language[1]);
redisTemplate.opsForValue().set(k, locale);
log.info("设置当前用户的默认语言环境:{}", local);
}
}
return locale;
}
@Override
public void setLocale(HttpServletRequest req, HttpServletResponse res, Locale locale) {
}
}
首先要把tomcat依赖排除掉。
不过有时候,tomcat的依赖不一定是由web的starter引入进来的,这个需要自己看清楚仔细咯
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
然后引入undertow
的starter
即可
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
# 不要设置过大,如果过大,启动项目会报错:打开文件数过多
server.undertow.io-threads=16
# 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程
# 它的值设置取决于系统线程执行任务的阻塞系数,默认值是IO线程数*8
server.undertow.worker-threads=256
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
server.undertow.buffer-size=1024
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
server.undertow.buffers-per-region=1024
# 是否分配的直接内存(NIO直接分配的堆外内存)
server.undertow.direct-buffers=true
Undertow认为它的运用场景是在IO密集型的系统应用中,并且认为多核机器是一个比较容易满足的点,Undertow初始化假想应用的阻塞系数在0.8~0.9之间,所以阻塞线程数直接乘了个8,当然,如果对应用较精确的估测阻塞系数,可以配置上去
旧版SpringMVC中的控制器参数校验:
@ControllerAdvice
public class ControllerCheckAdvice {
private Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(BindException.class)
public ResponseEntity<Tip> checkRequest(BindException e) {
log.info(e.getMessage());
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "校验失败:";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getField() + ":" + fieldError.getDefaultMessage() + ";";
}
Tip tip = new ErrorTip(400, errorMesssage.substring(0, errorMesssage.length() - 1));
return ResponseEntity.badRequest().body(tip);
}
}
新版WebFlux异常校验
@ControllerAdvice
public class ControllerCheckAdvice {
private Logger log = LoggerFactory.getLogger(getClass());
@ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<Tip> checkRequest(WebExchangeBindException e) {
log.info(e.getMessage());
BindingResult bindingResult = e.getBindingResult();
String errorMesssage = "校验失败:";
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMesssage += fieldError.getField() + ":" + fieldError.getDefaultMessage() + ";";
}
Tip tip = new ErrorTip(400, errorMesssage.substring(0, errorMesssage.length() - 1));
return ResponseEntity.badRequest().body(tip);
}
}
在全局配置文件中
server.port=配置程序默认端口,默认8080
server.session-timeout=session过期时间,秒
server.context-path=配置访问路径,默认/
server.tomcat.uri-encoding=配置编码,默认UTF-8
server.tomcat.compression=是否开启压缩,默认off
所有的tomcat配置都在server.tomcat
下
package org.zln.spb.config.prop;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.http.HttpStatus;
import java.util.concurrent.TimeUnit;
/**
* Created by nbcoolkid on 2017-08-16.
*/
@Component
public class CustomerServletContainer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer configurable) {
configurable.setPort(8080);
configurable.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/404.html"));
//配置session会话过期时间
configurable.setSessionTimeout(10, TimeUnit.MINUTES);
}
}
@Bean
public EmbeddedServletContainerFactory servletContainerFactory(){
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.setPort(8080);
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/404.html"));
factory.setSessionTimeout(10, TimeUnit.MINUTES);
return factory;
}
如果使用的是Jetty,则配置JettyEmbeddedServletContainerFactory;
如果使用的是Undertow,则配置UndertowEmbeddedServletContainerFactory
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
<html lang="en">
<head>
<meta charset="UTF-8">meta>
<title>Titletitle>
head>
<body>
<form method="post" enctype="multipart/form-data" action="/upload">
文件:<input type="file" name="file"/>
<input type="submit" value="上传"/>
form>
body>
html>
##默认支持文件上传.
spring.http.multipart.enabled=true
##支持文件写入磁盘.
spring.http.multipart.file-size-threshold=0
## 上传文件的临时目录
spring.http.multipart.location=/Users/zhangliuning/Gitee/java-ee/tmp
## 最大支持文件大小
spring.http.multipart.max-file-size=100Mb
## 最大支持请求大小
spring.http.multipart.max-request-size=1000Mb
...
import javax.servlet.http.Part;
...
@Controller
@Slf4j
public class WebController {
/**
* 文件上传
*
* @param file
* @return
*/
@RequestMapping("/upload")
@ResponseBody
public String fileUpload(@RequestPart Part file) {
// 获取文件名
String fileName = file.getSubmittedFileName();
log.info("上传的文件名为:" + fileName);
// 获取文件的后缀名
String suffixName = fileName.substring(fileName.lastIndexOf("."));
log.info("上传的后缀名为:" + suffixName);
// 文件上传路径
String filePath = "/Users/zhangliuning/Gitee/java-ee/功能点/test001-sp-upload/";
// 解决中文问题,liunx下中文路径,图片显示问题
fileName = fileName + "-" + UUID.randomUUID() + suffixName;
File dest = new File(filePath, fileName);
// 检测是否存在目录
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.write(dest.getAbsolutePath());
log.info(dest.getAbsolutePath());
return "上传成功";
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
return "上传失败";
}
}
这里使用Part类型参数接收请求,如果使用的是MultipartFile类型,
getSubmittedFileName相当于是getOriginalFilename,
write相当于是transferTo方法,Part类型是Servlet3.0提供的
/**
* 文件上传
* @param part
* @return
* @throws IOException
*/
@RequestMapping("/upload.do")
public String upLoad(@RequestPart("file") MultipartFile part) throws IOException {
File file = new File("C:\\Users\\nbcoolkid\\Documents\\git\\programlearing\\java-web\\web-manager-01\\src\\upLoadDir\\");
if (!file.exists()){
file.mkdir();
}
File file1 = new File(file,UUID.randomUUID().toString()+part.getOriginalFilename());
part.transferTo(file1);
logger.info("文件上传至:"+file1.getAbsolutePath());
return "main";
}
keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
1.-storetype 指定密钥仓库类型
2.-keyalg 生证书的算法名称,RSA是一种非对称加密算法
3.-keysize 证书大小
4.-keystore 生成的证书文件的存储路径
5.-validity 证书的有效期
在application.properties中添加如下代码:
server.ssl.key-store=keystore.p12
server.ssl.key-store-password=111111
server.ssl.keyStoreType=PKCS12
server.ssl.keyAlias:tomcat
第一行指定签名文件,第二行指定签名密码,第三行指定密钥仓库类型,第四个是别名。OK,这样配置完成之后我们就可以通过HTTPS来访问我们的Web了
HTTP自动转向HTTPS
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的端口号
connector.setPort(8080);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号
connector.setRedirectPort(8443);
return connector;
}
这个时候当我们访问http://localhost:8080的时候系统会自动重定向到https://localhost:8443这个地址上。这里的Connector实际就是我们刚刚接触jsp时在xml中配置的Tomcat的Connector节点
将自己的favicon.ico放置在类路径根目录、类路径META-INF/resources/、类路径resources/、类路径static/或类路径public/
关闭:spring.mvc.favicon.enable=false
添加WebSocket的starter
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
一般不会使用WebSocket,而是使用它的子协议STOMP
SpringBoot对内嵌的Tomcat(7、8)、Jetty9、Undertow使用WebSocket提供了支持
package org.zln.spb.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* WebSocket相关配置
* Created by nbcoolkid on 2017-06-26.
*/
@Configuration
//使用STOMP协议来传输基于代理的消息
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
/**
* 注册STOMP协议的节点,并映射指定的URL
* @param stompEndpointRegistry
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
// 注册一个STOMP的endpoint,并且指定使用SocketJS协议
stompEndpointRegistry.addEndpoint("endpointWisely").withSockJS();
}
/**
* 配置消息代理
* @param registry
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 广播式应配置一个topic消息代理
registry.enableSimpleBroker("/topic");
}
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>广播式WebSockettitle>
<script th:src="@{js/sockjs.min.js}">script>
<script th:src="@{js/stomp.js}">script>
<script th:src="@{js/jquery-3.1.1.js}">script>
head>
<body onload="disconnect()">
<noscript><h2 style="color: #e80b0a;">Sorry,浏览器不支持WebSocketh2>noscript>
<div>
<div>
<button id="connect" onclick="connect();">连接button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接button>
div>
<div id="conversationDiv">
<label>输入你的名字label><input type="text" id="name"/>
<button id="sendName" onclick="sendName();">发送button>
<p id="response">p>
div>
div>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById("connect").disabled = connected;
document.getElementById("disconnect").disabled = !connected;
document.getElementById("conversationDiv").style.visibility = connected ? 'visible' : 'hidden';
// $("#connect").disabled = connected;
// $("#disconnect").disabled = !connected;
$("#response").html();
}
function connect() {
var socket = new SockJS('/endpointWisely');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected:' + frame);
stompClient.subscribe('/topic/getResponse', function (response) {
showResponse(JSON.parse(response.body).responseMessage);
})
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log('Disconnected');
}
function sendName() {
var name = $('#name').val();
console.log('name:' + name);
stompClient.send("/welcome", {}, JSON.stringify({'name': name}));
}
function showResponse(message) {
$("#response").html(message);
}
script>
body>
html>
package org.zln.learning.springboot.cfg;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Created by sang on 16-12-22.
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/ws").setViewName("/ws");
}
}
package org.zln.learning.springboot.controller;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.zln.learning.springboot.domain.WiselyMessage;
import org.zln.learning.springboot.domain.WiselyResponse;
/**
* Created by nbcoolkid on 2017-08-17.
*/
@Controller
public class WsController {
@MessageMapping("/welcome")
//SendTo 发送至 Broker 下的指定订阅路径
@SendTo("/topic/getResponse")
public WiselyResponse say(WiselyMessage message) throws InterruptedException {
Thread.sleep(3000);
return new WiselyResponse("Welcome," + message.getName() + " !");
}
}
package org.zln.learning.springboot.cfg;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
/**
* Created by nbcoolkid on 2017-08-17.
*/
@Configuration
//开启使用STOMP协议来传输基于代理的消息
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {//注册STOMP协议的节点,并映射指定URL
// 注册一个STOMP的endpoint,并指定使用SocketJS协议
stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理
// 点对点配置一个消息队列
registry.enableSimpleBroker("/queue");
}
}
package org.zln.learning.springboot.cfg;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Created by sang on 16-12-22.
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//设置拦截规则
.antMatchers("/")
.permitAll()
.anyRequest()
.authenticated()
.and()
//开启默认登录页面
.formLogin()
//默认登录页面
.loginPage("/login")
//默认登录成功跳转页面
.defaultSuccessUrl("/chat")
.permitAll()
.and()
//设置注销
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("lenve").password("111").roles("USER")
.and()
.withUser("sang").password("222").roles("USER");
}
@Override
public void configure(WebSecurity web) throws Exception {
//设置不拦截规则
web.ignoring().antMatchers("/resources/static/**");
}
}
package org.zln.learning.springboot.cfg;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Created by sang on 16-12-22.
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("/login");
registry.addViewController("/chat").setViewName("/chat");
}
}
package org.zln.learning.springboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.zln.learning.springboot.domain.WiselyMessage;
import org.zln.learning.springboot.domain.WiselyResponse;
import java.security.Principal;
/**
* Created by nbcoolkid on 2017-08-17.
*/
@Controller
public class WsController {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat")
public void handleChat(Principal principal, String msg) {
if (principal.getName().equals("sang")) {
messagingTemplate.convertAndSendToUser("lenve", "/queue/notifications", principal.getName() + "给您发来了消息:" + msg);
} else {
messagingTemplate.convertAndSendToUser("sang", "/queue/notifications", principal.getName() + "给您发来了消息:" + msg);
}
}
}
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="UTF-8" />
<title>登录title>
head>
<body>
<div th:if="${param.error}">
无效的账号或密码
div>
<div th:if="${param.logout}">
你已注销
div>
<form th:action="@{/login}" method="post">
<div><label>账号:<input type="text" name="username" />label>div>
<div><label>密码:<input type="password" name="password" />label>div>
<div><input type="submit" value="登录" />div>
form>
body>
html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>聊天室title>
<script th:src="@{js/sockjs.min.js}">script>
<script th:src="@{js/stomp.js}">script>
<script th:src="@{js/jquery-3.1.1.js}">script>
head>
<body>
<p>聊天室p>
<form id="sangForm">
<textarea rows="4" cols="60" name="text">textarea>
<input type="submit" value="发送"/>
form>
<script th:inline="javascript">
$("#sangForm").submit(function (e) {
e.preventDefault();
var textArea = $("#sangForm").find('textarea[name="text"]');
var text = textArea.val();
sendSpittle(text);
textArea.val('');
});
var sock = new SockJS("/endpointChat");
var stomp = Stomp.over(sock);
stomp.connect('guest','guest',function (frame) {
stomp.subscribe("/user/queue/notifications", handleNotification);
});
function handleNotification(message) {
$("#output").append("Received: "+message.body+"
")
}
function sendSpittle(text) {
stomp.send("/chat", {}, text);
}
$("#stop").click(function () {
sock.close();
});
script>
<div id="output">div>
body>
html>
现代B/S系统特点
传统上,web项目最终是一个war,然后部署到web容器中
SpringBoot可以将web项目打成jar。即是包含页面、资源文件的web项目也行
建议打成jar,这种方式更利于在Docker中运行