SpringBoot(四)

Spring Boot Web開發

Web開發時開發中相當重要的一部分,Web開發的核心內容主要包括內嵌Servlet容器和Spring MVCjavascript

Spring Boot的Web開發支持

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

Thymeleaf模板引擎

內嵌的Servlet容器不支持JSP的運行,除此以外Undertow不支持jsp,Spring支持大量的模板引擎,包括FreeMark,Groovy,Thymeleaf,Velocity和Mustache,Spring Boot中推薦使用Thymeleaf模板引擎,由於Thymeleaf提供了完美的SpringMVC支持.web

Thymeleaf的基礎知識

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>

訪問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的Thymeleaf支持

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協議有異步雙工通訊的能力,可是通常不使用它的WebSocket協議開發程序,由於這樣顯得比較繁瑣。通常使用STOMP自協議,這是一個更加高級的協議,是基於幀的風格來定義信息的,和HTTP的request和response相似(具備相似的@RequestMapping的@MessageMapping)。

Spring Boot對的WebSocket的自動配置.

SpringBoot對內嵌的Tomcat(7以上),Jetty9以上和Undertow使用WebSocket提供了支持。配置源碼存在了org.springframework.boot.autoconfigure.websocket包下。

Spring Boot爲WebSocket提供的start pom是Spring-boot-start-websocket。

例子:

1.廣播式

廣播式即服務端有消息時,會將消息發送給全部連接了當前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>

效果:

2.點對點式

廣播式有本身的應用場景,可是廣播式不能解決咱們一個常見的場景,那就是消息由誰發送,有誰接收.爲了可以區分用戶,因此這裏引入了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");

執行效果:

例子:https://github.com/chenanddom/SpringWebSupport

相關文章
相關標籤/搜索