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

相关文章
相关标签/搜索