Web开发时开发中相当重要的一部分,Web开发的核心内容主要包括内嵌Servlet容器和Spring MVCjavascript
Spring Boot提供了spring-boot-starter-web为web开发予以支持,spring-boot-starter-web为咱们提供了潜入之Tomcat以及Spring MVC的依赖。而Web相关的自动配置存储在spring-boot-autoconfigure.jar的org.springframework.boot.web下:css
在这些文件中有以下做用:html
EmbeddedWebServerFactoryCustomizerAutoConfiguration和ServerProperties配置用于嵌入式servlet和响应式web服务器的自定义html5
ServletWebServerFactoryAutoConfiguration和ServerProperties配置用于配置Servlet容器服务器.java
HttpEncodingAutoConfiguration和HttpProperties用来自动配置编码.jquery
MultipartAutoConfiguration和MultipartProperties用来配置文件上传的属性.git
WebMvcAutoConfiguration和WebMvcProperties配置SpringMVCgithub
内嵌的Servlet容器不支持JSP的运行,除此以外Undertow不支持jsp,Spring支持大量的模板引擎,包括FreeMark,Groovy,Thymeleaf,Velocity和Mustache,Spring Boot中推荐使用Thymeleaf模板引擎,由于Thymeleaf提供了完美的SpringMVC支持.web
Thymeleaf时一个Java类库,他是xml/xhtml/html5的模板引擎,能够做为MVC的web应用的View层。Thymeleaf还提供了额外的模块与Spring MVC集成,全部彻底能够替代了JSP。spring
例子:
<html xmlns:th="http://www.thymeleaf.org"> <head> <meta content="text/html;charset=UTF-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> //根据设备自适应 <meta name="viewport" content="width=device-width, initial-scale=1"/> //引入静态资源 <link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/> <link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/> </head> <body> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">访问model</h3> </div> <div class="panel-body"> <span th:text="${singlePerson.name}"></span> </div> </div> <div th:if="${not #lists.isEmpty(people)}"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">列表</h3> </div> <div class="panel-body"> <ul class="list-group"> <li class="list-group-item" th:each="person:${people}"> <span th:text="${person.name}"></span> <span th:text="${person.age}"></span> <button class="btn" th:onclick="'getName(\'' + ${person.name} + '\');'">得到名字</button> </li> </ul> </div> </div> </div> <script th:src="@{jquery-1.10.2.min.js}" type="text/javascript"></script><!-- 2 --> <script th:src="@{bootstrap/js/bootstrap.min.js}"></script><!-- 2 --> <script th:inline="javascript"> var single = [[${singlePerson}]]; console.log(single.name + "/" + single.age) function getName(name) { console.log(name); } </script> </body> </html>
经过xmlns:th=http://www.thymeleaf.org命名空间,将镜头页面转换为动态的视图。须要进行动态处理的元素将使用“th:”为前缀;
经过@{}引用Web静态资源,这个JSP下时极易出错。
访问model中的数据 经过${}访问model中的属性,这和JSP极为类似。
<div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">访问model</h3> </div> <div class="panel-body"> //使用<span th:text="${singlePerson.name}"/>访问model中的singlePerson的name手续ing。 //须要处理的动态内容须要加上th:前缀。 <span th:text="${singlePerson.name}"></span> </div> </div>
model中的数据迭代
Thymeleaf中的数据迭代和JSP的写法很类似。
<div th:if="${not #lists.isEmpty(people)}"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">列表</h3> </div> <div class="panel-body"> <ul class="list-group"> //th:each来作循环迭代,th:each="person:${people}"person做为迭代元素使用 <li class="list-group-item" th:each="person:${people}"> <span th:text="${person.name}"></span> <span th:text="${person.age}"></span> <button class="btn" th:onclick="'getName(\'' + ${person.name} + '\');'">得到名字</button> </li> </ul> </div> </div> </div>
数据判断 经过${not#lists.isEmpty(people)}表达式判断people是否为空。Thymeleaf支持>,<,>=,<===,!=做为比较条件,同时也支持SpEL表达式
<div th:if="${not #lists.isEmpty(people)}">
在JavaScript中访问model
在项目中,咱们须要在JavaScript访问model中的值,在Thymeleaf里的代码以下:
<script th:inline="javascript"> var single = [[${singlePerson}]]; console.log(single.name + "/" + single.age) function getName(name) { console.log(name); } </script>
经过th:inline="javascript"添加到script标签,这样JavaScript代码便可访问model中的属性。
经过[[${}]]格式获取实际的值。
SpringBoot经过org.springframework.boot.autoconfigure.thymeleaf包对Thymeleaf进行了自动配置i
@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"; /** * Whether to check that the template exists before rendering it. */ private boolean checkTemplate = true; /** * Whether to check that the templates location exists. */ private boolean checkTemplateLocation = true; /** * Prefix that gets prepended to view names when building a URL. */ private String prefix = DEFAULT_PREFIX; /** * Suffix that gets appended to view names when building a URL. */ private String suffix = DEFAULT_SUFFIX; /** * Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum. */ private String mode = "HTML"; /** * Template files encoding. */ private Charset encoding = DEFAULT_ENCODING; /** * Whether to enable template caching. */ private boolean cache = true; /** * Order of the template resolver in the chain. By default, the template resolver is * first in the chain. Order start at 1 and should only be set if you have defined * additional "TemplateResolver" beans. */ private Integer templateResolverOrder; /** * Comma-separated list of view names (patterns allowed) that can be resolved. */ private String[] viewNames; /** * Comma-separated list of view names (patterns allowed) that should be excluded from * resolution. */ private String[] excludedViewNames; /** * Enable the SpringEL compiler in SpringEL expressions. */ private boolean enableSpringElCompiler; /** * Whether hidden form inputs acting as markers for checkboxes should be rendered * before the checkbox element itself. */ private boolean renderHiddenMarkersBeforeCheckboxes = false; /** * Whether to enable Thymeleaf view resolution for Web frameworks. */ private boolean enabled = true;
在application.yml或者application.properties使用spring.thymeleaf对Thymeleaf进行配置.
WebSocket为浏览器和服务端提供了双工异步的通讯的功能,即服务端和浏览器能够互相发消息。虽然WebSocket协议有异步双工通讯的能力,可是通常不使用它的WebSocket协议开发程序,由于这样显得比较繁琐。通常使用STOMP自协议,这是一个更加高级的协议,是基于帧的风格来定义信息的,和HTTP的request和response相似(具备相似的@RequestMapping的@MessageMapping)。
SpringBoot对内嵌的Tomcat(7以上),Jetty9以上和Undertow使用WebSocket提供了支持。配置源码存在了org.springframework.boot.autoconfigure.websocket包下。
Spring Boot为WebSocket提供的start pom是Spring-boot-start-websocket。
例子:
广播式即服务端有消息时,会将消息发送给全部连接了当前endpoint的浏览器。
配置WebSocket,须要加载配置类上使用@EnableWebSocketMessageBroker开启WebSocket支持,并经过继承AbstractWebSocketMessageBrokerConfigurer类,重写方法来配置WebSocket.
@Configuration //经过@EnableWebSocketMessageBroker注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping同样。 @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { /** * 配置STMOP协议的节点(endpoint),而且配置映射的路径 * @param registry */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/endpointDemo").withSockJS(); } /** * 配置消息代理 * @param registry */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); } }
服务端接受类: public class DemoMessage {
private String name; public DemoMessage() { } public String getName() { return name; } public void setName(String name) { this.name = name; } }
服务端的响应类:
public class DemoResponse { private String responseMessage; public DemoResponse() { } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; } }
控制器类
@Controller public class WsController { //当浏览器想服务端发送请求时,经过@MessageMapping映射/welcome这个地址,相似于@RequestMapping @MessageMapping("/welcome") //当服务端有消息时,会对订阅了@SendTo中的路径的浏览器发送消息 @SendTo("/topic/getResponse") public DemoResponse handle(DemoMessage message) throws InterruptedException { Thread.sleep(3000); DemoResponse demoResponse = new DemoResponse(); demoResponse.setResponseMessage("welcome,"+message.getName()+"!"); return demoResponse; } }
页面代码
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Spring Boot+WebSocket+广播式</title> </head> <body onload="disconnect()"> <noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></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 th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery-1.10.2.min.js}"></script> <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'; $('#response').html(); } function connect() { //连接SockJs的endpoint名称:endpointDemo var socket = new SockJS('/endpointDemo'); //使用STOMP的客户端连接 stompClient = Stomp.over(socket); //连接websocket stompClient.connect({}, function(frame) { setConnected(true); console.log('Connected: ' + frame); //订阅sendTo路径发送的消息 stompClient.subscribe('/topic/getResponse', function(respnose){ showResponse(JSON.parse(respnose.body).responseMessage); }); }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { var name = $("#name").val(); //项目标(/welcome)发送数据 stompClient.send("/welcome", {}, JSON.stringify({ 'name': name })); } function showResponse(message) { var response = $("#response"); response.html(message); } </script> </body> </html>
效果:
广播式有本身的应用场景,可是广播式不能解决咱们一个常见的场景,那就是消息由谁发送,有谁接收.为了可以区分用户,因此这里引入了SpringSecurity的starter pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
SpringSecurity的配置.
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //1根路径和/login路径不拦截,除此以外静态资源的路径也不须要拦截 .antMatchers("/","/login","/bootstrap/**").permitAll() .anyRequest().authenticated() .and() .formLogin() //2登录页面 .loginPage("/login") //3登录成功转向该页面 .defaultSuccessUrl("/chat") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //Spring Security 升级到5版本后密码支持多种加密格式 auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()) .withUser("zhangsan").password("123").roles("USER") .and() .withUser("lisi").password("123").roles("USER"); } //忽略静态资源的拦截 @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/resources/static/**"); } }
以上的地方须要注意的是,升级到Spring5.0以后SpringSecurity就须要支持多种加密方式,所以就须要配置一个加密的类,继承PasswordEncoder,代码以下:
/** * Created with IntelliJ IDEA. * Description:Spring Security 升级到5版本后密码支持多种加密格式 * User: chendom * Date: 2018-12-31 * Time: 10:30 */ public class MyPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence charSequence) { return charSequence.toString(); } @Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } }
控制器类:
/** *经过principal能够获取用户的信息 * @param principal * @param msg */ @MessageMapping("/chat") public void handleMessage(Principal principal,String msg){ if ("zhangsan".equals(principal.getName())){ //这里使用SimpMessagingTemplate向浏览器发送信息 simpMessagingTemplate.convertAndSendToUser("lisi","/queue/notifications",principal.getName()+"-sned:"+msg); }else { simpMessagingTemplate.convertAndSendToUser("zhangsan","/queue/notifications",principal.getName()+"-sned:"+msg); } }
聊天页面:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <meta charset="UTF-8" /> <head> <title>Home</title> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery-1.10.2.min.js}"></script> </head> <body> <p> 聊天室 </p> <form id="wiselyForm"> <textarea rows="4" cols="60" name="text"></textarea> <input type="submit"/> </form> <script th:inline="javascript"> $('#wiselyForm').submit(function(e){ e.preventDefault(); var text = $('#wiselyForm').find('textarea[name="text"]').val(); sendSpittle(text); }); var sock = new SockJS("/endpointChat"); //1 var stomp = Stomp.over(sock); stomp.connect('guest', 'guest', function(frame) { stomp.subscribe("/user/queue/notifications", handleNotification);//2 }); function handleNotification(message) { $('#output').append("<b>Received: " + message.body + "</b><br/>") } function sendSpittle(text) { stomp.send("/chat", {}, text);//3 } $('#stop').click(function() {sock.close()}); </script> <div id="output"></div> </body> </html>
WebSocketConfig须要配置代理和一个endpoint:
//除了原来的topic代理以外还须要为点对点增长一个代理 registry.enableSimpleBroker("/topic","/queue"); //增长一个点对点的endpoint registry.addEndpoint("/endpointChat").withSockJS();
WebMvcConfig.java添加跳转路径
registry.addViewController("/chat").setViewName("/chat");
执行效果: