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");
執行效果: