欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

开发颠覆者SpringBoot实战---------SpringBoot的Web学习

程序员文章站 2022-05-24 11:07:25
...

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>
相关标签: springboot web