开发颠覆者SpringBoot实战---------SpringBoot的Web学习
SpringBoot提供了spring-boot-starter-web为Web开发予以支持,Web相关的自动配置存储在org.springframework.boot.autoconfigure.web下,里面有自动配置内嵌Servlet容器、自动配置http编码、自动配置上传文件的属性、配置SpringMVC等
一、Thymeleaf模版引擎
SpringBoot提供了大量的模版引擎,包括FreeMarker、Groovy、Thymeleaf、Velcoity和Mustache,SpringBoot中推荐使用Thymeleaf作为模版引擎,因为Thymeleaf提供了完美的SpringMVC支持。
Thymeleaf是一个java类库,它是一个xml/xthml/html5的模版引擎,可以作为MVC的Web应用的View层。
- 通过xmlns:th=”http://www.thymeleaf.org”命名空间,将页面转换为动态试图,需要进行动态处理的元素使用“th:”作为前缀。
- 通过th:text=”${person.name}”访问model中的属性。
- 通过th:each=”person:${people}”来做循环迭代。
- 通过th:if=”${not #lists.isEmpty(people)}”来判断people是否为空。
- 通过th:src=”@{jquery-3.3.1.min.js}”来访问Web中的静态资源。
- 通过th:inline=”javascript”来添加script标签,这样在JavaScript代码中即可访问model中的属性值。
- 通过“[[${}]]”格式来获得实际值。
在传统的SpringMVC中,若我们需要集成一个模版引擎的话,需要定义ViewResolver,在SpringMVC中讲过。而Thymeleaf提供了一个SpringTemplateEngine的类,用来驱动SpringMVC下使用Thymeleaf模版引擎。而在SpringBoot中对Thymeleaf进行了自动配置,可以在application中以spring.thymeleaf开发来进行配置,不配置的情况下模版的默认目录是templates。
在src/main/resource/static中放入需要引入的静态资源:Bootstrap和jQuery。根据默认的原则在src/main/resource/templates下创建index.html文件。
<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.css}" rel="stylesheet">
<link th:href="@{bootstrap\css\bootstrap-theme.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-3.3.1.min.js}" type="text/javascript"></script>
<script th:src="@{bootstrap\js\bootstrap.js}" type="text/javascript"></script>
<script th:inline="javascript">
var single = [[${singlePerson}]];
console.log(single.name + "/" + single.age);
function getName(name) {
console.log(name);
}
</script>
</body>
</html>
准备数据:
/**
* 模板引擎展示数据
* @author think
*/
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
/**
* 控制器
* @author think
*/
@SpringBootApplication
@Controller
public class Springboot2ThymeleafApplication {
@RequestMapping("/")
public String index(Model model){
Person single = new Person("single", 20);
List<Person> people = new ArrayList<>();
Person p1 = new Person("xx", 20);
Person p2 = new Person("yy", 30);
Person p3 = new Person("zz", 40);
people.add(p1);
people.add(p2);
people.add(p3);
model.addAttribute("singlePerson", single);
model.addAttribute("people", people);
return "index";
}
public static void main(String[] args) {
SpringApplication.run(Springboot2ThymeleafApplication.class, args);
}
}
二、Web相关配置
通过查看WebMvcAutoConfiguration及WebMvcProperties源码,可以发现SpringBoot通过了如下的自动配置:
1、自动配置的ViewResolver
1)ContentNegotiatingViewResolver
这是SpringMVC提供的一个特殊的ViewResolver,ContentNegotiatingViewResolver不是自己处理View,而是代理给不同的ViewResolver来处理不用的view,所以它有最高的优先级。
@Bean
@ConditionalOnBean({ViewResolver.class})
@ConditionalOnMissingBean(
name = {"viewResolver"},
value = {ContentNegotiatingViewResolver.class}
)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
resolver.setOrder(-2147483648);
return resolver;
}
2)BeanNameViewResolver
在控制器中的一个方法返回的字符串值会根据BeanNameViewResolver去查找Bean的名称为返回的字符串的view来进行视图渲染。
/**
* 这是源码
*/
@Bean
@ConditionalOnBean({View.class})
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(2147483637);
return resolver;
}
/**
* 控制器
* @author think
*/
@SpringBootApplication
@Controller
public class Springboot2ThymeleafApplication {
@Bean
public MappingJackson2JsonView jsonView(){
return new MappingJackson2JsonView();
}
@RequestMapping(value = "/json", produces = {MediaType.APPLICATION_JSON_VALUE})
public String index(Model model){
Person single = new Person("single", 20);
model.addAttribute("singlePerson", single);
return "jsonView";//返回的就是上面定义的Bean
}
public static void main(String[] args) {
SpringApplication.run(Springboot2ThymeleafApplication.class, args);
}
}
3)InternalResourceViewResolver
这是一个极为常用的ViewResolver,主要通过设置前缀、后缀、以及控制器中的方法来返回视图名的字符串,以得到实际页面。
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
2、自动配置的静态资源
将静态文件放置在resources/static、resources/public、resources/resources中,直接遍可访问静态资源。
3、接管SpringBoot的Web配置
通常情况下,SpringBoot的自动配置是符合我们大多少要求的,但是想要增加自己的额外配置时候,可以继承WebMvcConfigurerAdapter(貌似已经过时了),不需要使用@EnableWebMvc。
4、注册Servlet、Filter、Listener
当使用嵌入式Servlet容器时,我们通过将Servlet、Filter、Listener声明为Spring Bean的方式,或者注册。
/**
* 直接注册Bean
* @author think
*/
@Bean
public XXServlet xxServlet(){
return new XXServlet();
}
@Bean
public YYFilter yyFilter(){
return new YYFilter();
}
@Bean
public ZZListener zzListener(){
return new ZZListener();
}
/**
* 通过RegistrationBean
* @author think
*/
@Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new XXServlet(), "/xx/*");
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new YYFilter());
bean.setOrder(2);
return bean;
}
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
return new servletListenerRegistrationBean<ZZListener>(new XXServlet());
}
三、tomcat配置
SpringBoot默认内嵌的servlet容器为tomcat,通用的servlet配置都以“servlet”为前缀,而tomcat的配置都以“servlet.tomcat”为前缀。
//配置访问路径
server.servlet.context-path=/
//配置端口号
server.port=8088
//配置session过期时间,以秒为单位
server.servlet.session.timeout=600
//tomcat默认编码
server.tomcat.uri-encoding=UTF-8
SSL配置:
server.ssl.key-store=214525419750767.pfx
server.ssl.key-store-password=214525419750767
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
代码配置tomcat:
@Bean
public ServletWebServerFactory servletFactory(){
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(){
//重载方法设置https访问
@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);
super.postProcessContext(context);
}
};
factory.setPort(8088);
factory.setContextPath("/mySpringBoot");
//配置错误页面,将错误页面放置在resource/static静态文件夹下即可
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
factory.addAdditionalTomcatConnectors(httpConnertor());
return factory;
}
@Bean
public Connector httpConnertor(){
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
https的设置方法与tomcat服务器的server.xml文件设置基本一致,下面是我自己设置过的tomcat服务器,可以做一下比较一下:
<Connector port="8081"
protocol="org.apache.coyote.http11.Http11NioProtocol"
redirectPort="8443"
....
/>
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150"
SSLEnabled="true"
scheme="https"
secure="true"
clientAuth="false"
sslProtocol="TLS"
keystoreFile="F:\server\pingdian\apache-tomcat-7.0.81\conf\cert\214525419750767\214525419750767.pfx"
keystorePass="214525419750767"
keystoreType="PKCS12"/>
四、Favicon设置
SpringBoot提供一个默认的Favicon,每次访问都可以看到,可以在application.properties中设置关闭:
spring.mvc.favicon.enabled=false
需要设置自己Facicon,则只要把自己的文件(文件名不能变)放置在类路径/resource/static下就可以。
五、WebSocket
WebSocket为向浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送消息,服务器也可以向浏览器发送消息。它是通过一个socket来实现双工异步功能,下面的代码中使用它的子协议STOMP,一个更高级的协议,减少开发难度。
1、广播式
广播式即订阅模式,会发送消息给所有连接了当前节点的浏览器。
1)配置WebSocket
/**
* 配置WebSocket
* @author think
*/
@Configuration
@EnableWebSocketMessageBroker//开启使用STOMP协议来传输基于代理的消息
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
//注册STOMP协议节点endpoint,指定使用SockJS协议,并映射指定的url
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpointWisely").withSockJS();
}
/*@Override
//配置消息代理
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}*/
}
2)两种实体类
/**
* 服务端向浏览器发送的消息使用此类接受
* @author think
*/
public class WiselyResponse {
private String responseMessage;
public WiselyResponse(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getResponseMessage() {
return responseMessage;
}
}
/**
* 浏览器向服务端发送的消息使用此类接受
* @author think
*/
public class WiselyMessage {
private String name;
public String getName(){
return name;
}
}
3)控制器
/**
* 控制器
* @author think
*/
@Controller
public class WsController {
@MessageMapping("/welcome")//当浏览器向服务端发送请求时,通过此注解映射welcome的地址,类似@RequestMapping
@SendTo("/topic/getResponse")//当服务端有消息时,会对订阅了此路径的浏览器发送消息
public WiselyResponse say(WiselyMessage message) throws InterruptedException {
Thread.sleep(3000);
return new WiselyResponse("welcome, " + message.getName());
}
//访问路径映射页面
@RequestMapping("/ws")
public String turn2WS(){
return "ws";
}
}
4)在resource/templates下新建ws.html,其中需要两个js文件,可以自己下载
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html" charset="UTF-8">
<title>广播式</title>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #00a0e9">貌似你的浏览器不支持websocket</h2></noscript>
<div>
<div>
<button id="connect" onclick="connect()">连接</button>
<button id="disconnect" 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.js}"></script>
<script th:src="@{stomp.js}"></script>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
//连接SockJS的endpoint名称为'/endpointWisely'
var socket = new SockJS('/endpointWisely');
//使用STOMP子协议的WebSocket客户端
stompClient = Stomp.over(socket);
//连接客户端
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected:' + frame);
//订阅/topic/getResponse
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 = document.getElementById('name').value;
//向服务器发送消息,控制器中定义的
stompClient.send("/welcome", {}, JSON.stringify({'name':name}));
}
function showResponse(message) {
document.getElementById('response').innerHTML = message;
}
</script>
</body>
</html>
运行后即可在所有浏览器窗口接收到其中一个浏览器发送的信息。
2、点对点
即一对一的聊天式,需要先引入Spring Security。
1)Security的简单配置
/**
* Security的简单配置
* @author think
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/login").permitAll()//对“/”和“/login”路径不拦截
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")//登陆后的访问路径是“login”
.defaultSuccessUrl("/chat")//登录成功后转向“/chat”
.permitAll()
.and()
.logout()
.permitAll();
super.configure(http);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置两个用户
auth.inMemoryAuthentication()
.withUser("wyf").password("wyf").roles("USER")
.and()
.withUser("wisely").password("wisely").roles("USER");
super.configure(auth);
}
@Override
public void configure(WebSecurity web) throws Exception {
//对静态资源不拦截
web.ignoring().antMatchers("/resources/static/**");
super.configure(web);
}
}
2)配置WebSocket
/**
* 配置WebSocket
* @author think
*/
@Configuration
@EnableWebSocketMessageBroker//开启使用STOMP协议来传输基于代理的消息
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
//注册STOMP协议节点endpoint,指定使用SockJS协议,并映射指定的url
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/endpointChat").withSockJS();
}
@Override
//配置消息代理
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue");
}
}
3)控制配置
/**
* 控制器
* @author think
*/
@Controller
public class WsController {
//通过SimpMessagingTemplate向浏览器发送消息
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat")
public void handleChat(Principal principal, String msg){
//如果用户是xiaoming,则信息发给xiaowang,反之
if (principal.getName().equals("wyf")){
//发送消息,第一个参数用户,第二个浏览器订阅地址,第三个消息本身
messagingTemplate.convertAndSendToUser("wisely", "/queue/notifications", principal.getName() + "-send:" + msg);
}else {
messagingTemplate.convertAndSendToUser("wyf", "/queue/notifications", principal.getName() + "-send:" + msg);
}
}
//访问路径映射页面
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/chat")
public String chat(){
return "chat";
}
}
4)在resource/templates下新建两个html:login.html和chat.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta content="text/html" 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 xmlns:th="http://www.thymeleaf.org">
<head>
<meta content="text/html" charset="UTF-8">
<title>聊天</title>
</head>
<body>
<p>聊天室</p>
<form id="wiselyForm">
<textarea rows="4" cols="60" name="text"></textarea>
<input type="submit">
</form>
<div id="output"></div>
<script th:src="@{sockjs.js}"></script>
<script th:src="@{stomp.js}"></script>
<script th:inline="javascript">
document.getElementById('wiselyForm').onsubmit(function (e) {
e.preventDefault();
var text = document.getElementById('wiselyForm').getElementsByTagName('textarea').value;
sendSpittle(text);
});
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) {
document.getElementById('output').innerHTML = '<b>Received:' + message.body + '</b><br/>'
}
function sendSpittle(text) {
stomp.send('/chat', {}, text);
}
</script>
</body>
</html>
上一篇: DBCP和C3P0数据源
下一篇: 使用Python调整图像大小
推荐阅读
-
SpringBoot Web开发——拦截器的基本使用
-
Springboot的web开发中static和templates的区别
-
SpringBoot的Web开发之WebSocket(广播式)笔记总结
-
springboot开发简单的web应用前篇
-
springboot在web开发中的应用
-
spring学习笔记(十九)springboot_web开发
-
SpringBoot入门学习笔记十(SpringBoot开发Web项目)
-
spring学习笔记(十七)springboot_web开发
-
SpringBoot入门学习笔记(二、日志与Web开发)
-
SpringBoot学习笔记 - web开发(web相关配置)