Spring Boot 入門之 Web 篇(二)

原文地址:Spring Boot 入門之 Web 篇(二)
博客地址:http://www.extlight.comjavascript

1、前言

上一篇《Spring Boot 入門之基礎篇(一)》介紹了 Spring Boot 的環境搭建以及項目啓動打包等基礎內容,本篇繼續深刻介紹 Spring Boot 與 Web 開發相關的知識。css

2、整合模板引擎

因爲 jsp 不被 SpringBoot 推薦使用,因此模板引擎主要介紹 Freemarker 和 Thymeleaf。html

2.1 整合 Freemarker

2.1.1 添加 Freemarker 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

2.1.2 添加 Freemarker 模板配置

在 application.properties 中添加以下內容:前端

spring.freemarker.allow-request-override=false
spring.freemarker.cache=true
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
spring.freemarker.prefix=
spring.freemarker.suffix=.ftl

上述配置都是默認值。java

2.1.3 Freemarker 案例演示

在 controller 包中建立 FreemarkerController:jquery

@Controller
@RequestMapping("freemarker")
public class FreemarkerController {

	@RequestMapping("hello")
	public String hello(Map<String,Object> map) {
		
		map.put("msg", "Hello Freemarker");
		return "hello";
	}
}

在 templates 目錄中建立名爲 hello.ftl 文件,內容以下:git

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link href="/css/index.css" rel="stylesheet"/>
</head>
<body>
    <div class="container">
    	<h2>${msg}</h2>
    </div>
</body>
</html>

結果以下:github

image

2.2 整合 Thymeleaf

2.2.1 添加 Thymeleaf 依賴

在 pom.xml 文件中添加:web

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.2.2 添加 Thymeleaf 模板配置

在 application.properties 中添加以下內容:ajax

spring.thymeleaf.cache=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html

上述配置都是默認值。

2.2.3 Thymeleaf 案例演示

在 controller 包中建立 ThymeleafController:

@Controller
@RequestMapping("thymeleaf")
public class ThymeleafController {

	@RequestMapping("hello")
	public String hello(Map<String,Object> map) {
		map.put("msg", "Hello Thymeleaf");
		return "hello";
	}
}

在 template 目錄下建立名爲 hello.html 的文件,內容以下:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link href="/css/index.css" rel="stylesheet"/>
</head>
<body>
    <div class="container">
    	<h2 th:text="${msg}"></h2>
    </div>
</body>
</html>

結果以下:

image

3、整合 Fastjson

3.1 添加依賴

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.35</version>
</dependency>

3.2 整合 Fastjson

建立一個配置管理類 WebConfig ,以下:

@Configuration
public class WebConfig {

	@Bean
	public HttpMessageConverters fastJsonHttpMessageConverters() {
		FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();

		FastJsonConfig fastJsonConfig = new FastJsonConfig();
		fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
		
		fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
		
		HttpMessageConverter<?> converter = fastJsonHttpMessageConverter;
		
		return new HttpMessageConverters(converter);

	}
}

3.3 演示案例:

建立一個實體類 User:

public class User {

	private Integer id;
	
	private String username;
	
	private String password;
	
	private Date birthday;

}

getter 和 setter 此處省略。

建立控制器類 FastjsonController :

@Controller
@RequestMapping("fastjson")
public class FastJsonController {

	@RequestMapping("/test")
	@ResponseBody
	public User test() {
		User user = new User();
		
		user.setId(1);
		user.setUsername("jack");
		user.setPassword("jack123");
		user.setBirthday(new Date());
		
		return user;
	}
}

打開瀏覽器,訪問 http://localhost:8080/fastjson/test,結果以下圖:

image

此時,還不能看出 Fastjson 是否正常工做,咱們在 User 類中使用 Fastjson 的註解,以下內容:

@JSONField(format="yyyy-MM-dd")
private Date birthday;

再次訪問 http://localhost:8080/fastjson/test,結果以下圖:

image

日期格式與咱們修改的內容格式一致,說明 Fastjson 整合成功。

4、自定義 Servlet

4.1 編寫 Servlet

public class ServletTest extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("text/html;charset=utf-8");
		resp.getWriter().write("自定義 Servlet");
	}
	
}

4.2 註冊 Servlet

將 Servelt 註冊成 Bean。在上文建立的 WebConfig 類中添加以下代碼:

@Bean
public ServletRegistrationBean servletRegistrationBean() {
    return new ServletRegistrationBean(new ServletTest(),"/servletTest");
}

結果以下:

image

5、自定義過濾器/第三方過濾器

5.1 編寫過濾器

public class TimeFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("=======初始化過濾器=========");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws IOException, ServletException {

		long start = System.currentTimeMillis();

		filterChain.doFilter(request, response);

		System.out.println("filter 耗時:" + (System.currentTimeMillis() - start));

	}

	@Override
	public void destroy() {
		System.out.println("=======銷燬過濾器=========");
	}

}

5.2 註冊過濾器

要是該過濾器生效,有兩種方式:

  1. 使用 @Component 註解

  2. 添加到過濾器鏈中,此方式適用於使用第三方的過濾器。將過濾器寫到 WebConfig 類中,以下:

@Bean
public FilterRegistrationBean timeFilter() {
	FilterRegistrationBean registrationBean = new FilterRegistrationBean();
	
	TimeFilter timeFilter = new TimeFilter();
	registrationBean.setFilter(timeFilter);
	
	List<String> urls = new ArrayList<>();
	urls.add("/*");
	registrationBean.setUrlPatterns(urls);
	
	return registrationBean;
}

結果以下:

image

6、自定義監聽器

6.1 編寫監聽器

public class ListenerTest implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("監聽器初始化...");
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {

	}

}

6.2 註冊監聽器

註冊監聽器爲 Bean,在 WebConfig 配置類中添加以下代碼:

@Bean
public ServletListenerRegistrationBean<ListenerTest> servletListenerRegistrationBean() {
    return new ServletListenerRegistrationBean<ListenerTest>(new ListenerTest());
}

當啓動容器時,結果以下:

image

針對自定義 Servlet、Filter 和 Listener 的配置,還有另外一種方式:

@SpringBootApplication
public class SpringbootWebApplication implements ServletContextInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    	// 配置 Servlet
        servletContext.addServlet("servletTest",new ServletTest())
        			  .addMapping("/servletTest");
        // 配置過濾器
        servletContext.addFilter("timeFilter",new TimeFilter())
        			  .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
        // 配置監聽器
        servletContext.addListener(new ListenerTest());
    }

	public static void main(String[] args) {
		SpringApplication.run(SpringbootWebApplication.class, args);
	}
}

7、自定義攔截器

7.1 編寫攔截器

使用 @Component 讓 Spring 管理其生命週期:

@Component
public class TimeInterceptor implements HandlerInterceptor {
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		
		System.out.println("========preHandle=========");
		System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
		System.out.println(((HandlerMethod)handler).getMethod().getName());
		
		request.setAttribute("startTime", System.currentTimeMillis());
		
		return true;
	}
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception {

		System.out.println("========postHandle=========");
		Long start = (Long) request.getAttribute("startTime");
		System.out.println("耗時:"+(System.currentTimeMillis() - start));
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
			throws Exception {

		System.out.println("========afterCompletion=========");
		Long start = (Long) request.getAttribute("startTime");
		System.out.println("耗時:"+(System.currentTimeMillis() - start));
		
		System.out.println(exception);
	}

}

7.2 註冊攔截器

編寫攔截器後,咱們還須要將其註冊到攔截器鏈中,以下配置:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
	
	@Autowired
	private TimeInterceptor timeInterceptor;
	

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(timeInterceptor);
	}

}

請求一個 controller ,結果以下:

image

8、配置 AOP 切面

8.1 添加依賴

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

8.2 編寫切面類

使用 @Component,@Aspect 標記到切面類上:

@Aspect
@Component
public class TimeAspect {

	@Around("execution(* com.light.springboot.controller.FastJsonController..*(..))")
	public Object method(ProceedingJoinPoint pjp) throws Throwable {

		System.out.println("=====Aspect處理=======");
		Object[] args = pjp.getArgs();
		for (Object arg : args) {
			System.out.println("參數爲:" + arg);
		}

		long start = System.currentTimeMillis();

		Object object = pjp.proceed();

		System.out.println("Aspect 耗時:" + (System.currentTimeMillis() - start));

		return object;
	}
}

請求 FastJsonController 控制器的方法,結果以下:

image

9、錯誤處理

9.1 友好頁面

先演示非友好頁面,修改 FastJsonController 類中的 test 方法:

@RestController
@RequestMapping("fastjson")
public class FastJsonController {

	@RequestMapping("/test")
	public User test() {
		User user = new User();
		
		user.setId(1);
		user.setUsername("jack");
		user.setPassword("jack123");
		user.setBirthday(new Date());
		
		// 模擬異常
		int i = 1/0;
		
		return user;
	}
}

瀏覽器請求:http://localhost:8080/fastjson/test,結果以下:

image

當系統報錯時,返回到頁面的內容一般是一些雜亂的代碼段,這種顯示對用戶來講不友好,所以咱們須要自定義一個友好的提示系統異常的頁面。

在 src/main/resources 下建立 /public/error,在該目錄下再建立一個名爲 5xx.html 文件,該頁面的內容就是當系統報錯時返回給用戶瀏覽的內容:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>系統錯誤</title>
    <link href="/css/index.css" rel="stylesheet"/>
</head>
<body>
    <div class="container">
    	<h2>系統內部錯誤</h2>
    </div>
</body>
</html>

路徑時固定的,Spring Boot 會在系統報錯時將返回視圖指向該目錄下的文件。

以下圖:

image

上邊處理的 5xx 狀態碼的問題,接下來解決 404 狀態碼的問題。

當出現 404 的狀況時,用戶瀏覽的頁面也不夠友好,所以咱們也須要自定義一個友好的頁面給用戶展現。

在 /public/error 目錄下再建立一個名爲 404.html 的文件:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>訪問異常</title>
    <link href="/css/index.css" rel="stylesheet"/>
</head>
<body>
    <div class="container">
    	<h2>找不到頁面</h2>
    </div>
</body>
</html>

咱們請求一個不存在的資源,如:http://localhost:8080/fastjson/test2,結果以下圖:

image

9.2 全局異常捕獲

若是項目先後端是經過 JSON 進行數據通訊,則當出現異常時能夠經常使用以下方式處理異常信息。

編寫一個類充當全局異常的處理類,須要使用 @ControllerAdvice 和 @ExceptionHandler 註解:

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

	/**
	 * 處理 Exception 類型的異常
	 * @param e
	 * @return
	 */
	@ExceptionHandler(Exception.class)
	@ResponseBody
	public Map<String,Object> defaultExceptionHandler(Exception e) {
		
		Map<String,Object> map = new HashMap<String,Object>();
		map.put("code", 500);
		map.put("msg", e.getMessage());
		return map;
	}
}

其中,方法名爲任意名,入參通常使用 Exception 異常類,方法返回值可自定義。

啓動項目,訪問 http://localhost:8080/fastjson/test,結果以下圖:

image

咱們還能夠自定義異常,在全局異常的處理類中捕獲和判斷,從而對不一樣的異常作出不一樣的處理。

10、文件上傳和下載

10.1 添加依賴

<!-- 工具 -->
<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.4</version>
</dependency>

10.2 實現

編寫一個實體類,用於封裝返回信息:

public class FileInfo {

	private String path;
	
	public FileInfo(String path) {
		this.path = path;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
	
}

編寫 Controller,用於處理文件上傳下載:

@RestController
@RequestMapping("/file")
public class FileController {

	private String path = "d:\\";

	@PostMapping
	public FileInfo upload(MultipartFile file) throws Exception {

		System.out.println(file.getName());
		System.out.println(file.getOriginalFilename());
		System.out.println(file.getSize());

		File localFile = new File(path, file.getOriginalFilename());

		file.transferTo(localFile);

		return new FileInfo(localFile.getAbsolutePath());
	}

	@GetMapping("/{id}")
	public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) {
		try (InputStream inputStream = new FileInputStream(new File(path, id + ".jpg"));
				OutputStream outputStream = response.getOutputStream();) {

			response.setContentType("application/x-download");
			response.addHeader("Content-Disposition", "attachment;filename=" + id + ".jpg");

			IOUtils.copy(inputStream, outputStream);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

基本上都是在學習 javaweb 時用到的 API。

文件上傳測試結果以下圖:

image

11、CORS 支持

前端頁面:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>跨域測試</title>
</head>

<body>
    <button id="test">測試</button>
    <script type="text/javascript" src="jquery-1.12.3.min.js"></script>
    <script type="text/javascript">
    $(function() {
        $("#test").on("click", function() {
            $.ajax({
                "url": "http://localhost:8080/fastjson/test",
                "type": "get",
                "dataType": "json",
                "success": function(data) {
                    console.log(data);
                }
            })
        });
    });
    </script>
</body>

</html>

經過 http 容器啓動前端頁面代碼,筆者使用 Sublime Text 的插件啓動的,測試結果以下:

image

從圖中可知,前端服務器啓動端口爲 8088 與後端服務器 8080 不一樣源,所以出現跨域的問題。

如今開始解決跨域問題,能夠兩種維度控制客戶端請求。

粗粒度控制

方式一

@Configuration
public class WebConfig {
    
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
          @Override
          public void addCorsMappings(CorsRegistry registry) {
              registry.addMapping("/fastjson/**")
                      .allowedOrigins("http://localhost:8088");// 容許 8088 端口訪問
          }
        };
    }
}

方式二

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/fastjson/**")
              .allowedOrigins("http://localhost:8088");// 容許 8088 端口訪問
    }
}

配置後,從新發送請求,結果以下:

image

細粒度控制

在 FastJsonController 類中的方法上添加 @CrossOrigin(origins="xx") 註解:

@RequestMapping("/test")
@CrossOrigin(origins="http://localhost:8088")
public User test() {
	User user = new User();
	
	user.setId(1);
	user.setUsername("jack");
	user.setPassword("jack123");
	user.setBirthday(new Date());
	
	return user;
}

在使用該註解時,須要注意 @RequestMapping 使用的請求方式類型,即 GET 或 POST。

12、整合 WebSocket

12.1 添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

12.2 實現方式

方式一:

該方式只適用於經過 jar 包直接運行項目的狀況。

WebSocket 配置類:

@Configuration
public class WebSocketConfig {
	
	@Bean
	public ServerEndpointExporter serverEndpointExporter() {
		return new ServerEndpointExporter();
	}

}

WebSocket 處理類:

@ServerEndpoint(value = "/webSocketServer/{userName}")
@Component
public class WebSocketServer {
	
	private static final Set<WebSocketServer> connections = new CopyOnWriteArraySet<>();

	private String nickname;
	private Session session;

	private static String getDatetime(Date date) {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return format.format(date);
	}

	@OnOpen
	public void start(@PathParam("userName") String userName, Session session) {
		this.nickname = userName;
		this.session = session;
		connections.add(this);
		String message = String.format("* %s %s", nickname, "加入聊天!");
		broadcast(message);
	}

	@OnClose
	public void end() {
		connections.remove(this);
		String message = String.format("* %s %s", nickname, "退出聊天!");
		broadcast(message);
	}

	@OnMessage
	public void pushMsg(String message) {
		broadcast("【" + this.nickname + "】" + getDatetime(new Date()) + " : " + message);
	}

	@OnError
	public void onError(Throwable t) throws Throwable {

	}

	private static void broadcast(String msg) {
		// 廣播形式發送消息
		for (WebSocketServer client : connections) {
			try {
				synchronized (client) {
					client.session.getBasicRemote().sendText(msg);
				}
			} catch (IOException e) {
				connections.remove(client);
				try {
					client.session.close();
				} catch (IOException e1) {
					e.printStackTrace();
				}
				String message = String.format("* %s %s", client.nickname, "斷開鏈接");
				broadcast(message);
			}
		}
	}
}

前端頁面:

<!DOCTYPE html>
<html>

<head lang="zh">
    <meta charset="UTF-8">
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/bootstrap-theme.min.css">
    <script src="js/jquery-1.12.3.min.js"></script>
    <script src="js/bootstrap.js"></script>
    <style type="text/css">
    #msg {
        height: 400px;
        overflow-y: auto;
    }

    #userName {
        width: 200px;
    }

    #logout {
        display: none;
    }
    </style>
    <title>webSocket測試</title>
</head>

<body>
    <div class="container">
        <div class="page-header" id="tou">webSocket及時聊天Demo程序</div>
        <p class="text-right" id="logout">
            <button class="btn btn-danger" id="logout-btn">退出</button>
        </p>
        <div class="well" id="msg"></div>
        <div class="col-lg">
            <div class="input-group">
                <input type="text" class="form-control" placeholder="發送信息..." id="message"> <span class="input-group-btn">
                    <button class="btn btn-default" type="button" id="send"
                        disabled="disabled">發送</button>
                </span>
            </div>
            <div class="input-group">
                <input id="userName" type="text" class="form-control" name="userName" placeholder="輸入您的用戶名" />
                <button class="btn btn-default" type="button" id="connection-btn">創建鏈接</button>
            </div>
            <!-- /input-group -->
        </div>
        <!-- /.col-lg-6 -->
    </div>
    <!-- /.row -->
    </div>
    <script type="text/javascript">
    $(function() {
        var websocket;
        $("#connection-btn").bind("click", function() {
            var userName = $("#userName").val();
            if (userName == null || userName == "") {
                alert("請輸入您的用戶名");
                return;
            }
            connection(userName);
        });

        function connection(userName) {
            var host = window.location.host;
            if ('WebSocket' in window) {
                websocket = new WebSocket("ws://" + host +
                    "/webSocketServer/" + userName);
            } else if ('MozWebSocket' in window) {
                websocket = new MozWebSocket("ws://" + host +
                    "/webSocketServer/" + userName);
            }
            websocket.onopen = function(evnt) {
                $("#tou").html("連接服務器成功!")
                $("#send").prop("disabled", "");
                $("#connection-btn").prop("disabled", "disabled");
                $("#logout").show();
            };
            websocket.onmessage = function(evnt) {
                $("#msg").html($("#msg").html() + "<br/>" + evnt.data);
            };
            websocket.onerror = function(evnt) {
                $("#tou").html("報錯!")
            };
            websocket.onclose = function(evnt) {
                $("#tou").html("與服務器斷開了連接!");
                $("#send").prop("disabled", "disabled");
                $("#connection-btn").prop("disabled", "");
                $("#logout").hide();
            }
        }

        function send() {
            if (websocket != null) {
                var $message = $("#message");
                var data = $message.val();
                if (data == null || data == "") {
                    return;
                }
                websocket.send(data);
                $message.val("");
            } else {
                alert('未與服務器連接.');
            }
        }

        $('#send').bind('click', function() {
            send();
        });

        $(document).on("keypress", function(event) {
            if (event.keyCode == "13") {
                send();
            }
        });

        $("#logout-btn").on("click", function() {
            websocket.close(); //關閉TCP鏈接
        });
    });
    </script>
</body>

</html>

演示圖以下:

image

若是使用該方式實現 WebSocket 功能並打包成 war 運行會報錯:

javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path

方式二:

該方式適用於 jar 包方式運行和 war 方式運行。

WebSocket 配置類:

@Configuration  
@EnableWebSocket  
public class WebSocketConfig implements WebSocketConfigurer {  
    @Override  
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {  
        registry.addHandler(webSocketServer(), "/webSocketServer/*"); 
    }  
  
    @Bean
    public WebSocketHandler webSocketServer() {  
        return new WebSocketServer();  
    }  
}

WebSocket 處理類:

public class WebSocketServer extends TextWebSocketHandler {

	private static final Map<WebSocketSession, String> connections = new ConcurrentHashMap<>();

	private static String getDatetime(Date date) {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return format.format(date);
	}
	
	/**
	 * 創建鏈接
	 */
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {

		String uri = session.getUri().toString();
		String userName = uri.substring(uri.lastIndexOf("/") + 1);
		
		String nickname = URLDecoder.decode(userName, "utf-8");

		connections.put(session, nickname);
		String message = String.format("* %s %s", nickname, "加入聊天!");

		broadcast(new TextMessage(message));
	}

	/**
	 * 斷開鏈接
	 */
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		String nickname = connections.remove(session);
		String message = String.format("* %s %s", nickname, "退出聊天!");
		
		broadcast(new TextMessage(message));
	}

	/**
	 * 處理消息
	 */
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		String msg = "【" + connections.get(session) + "】" + getDatetime(new Date()) + " : " + message.getPayload();
		
		broadcast(new TextMessage(msg));
	}

	private static void broadcast(TextMessage msg) {
		// 廣播形式發送消息
		for (WebSocketSession session : connections.keySet()) {
			try {
				synchronized (session) {
					session.sendMessage(msg);
				}
			} catch (Exception e) {
				connections.remove(session);
				try {
					session.close();
				} catch (Exception e2) {
					e2.printStackTrace();
				}
				String message = String.format("* %s %s", connections.get(session), "斷開鏈接");
				broadcast(new TextMessage(message));
			}
		}
	}
}

運行結果與上圖一致。

十3、整合 Swagger2

13.1 添加依賴

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.7.0</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.7.0</version>
</dependency>

13.2 配置

從新建立一個配置類,以下:

@Configuration
@EnableSwagger2
public class Swagger2Configuration {
	
	@Bean
	public Docket accessToken() {
		return new Docket(DocumentationType.SWAGGER_2)
				.groupName("api")// 定義組
			    .select() // 選擇那些路徑和 api 會生成 document
			    .apis(RequestHandlerSelectors.basePackage("com.light.springboot.controller")) // 攔截的包路徑
			    .paths(PathSelectors.regex("/*/.*"))// 攔截的接口路徑
			    .build() // 建立
			    .apiInfo(apiInfo()); // 配置說明
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()//
				.title("Spring Boot 之 Web 篇")// 標題
				.description("spring boot Web 相關內容")// 描述
				.termsOfServiceUrl("http://www.extlight.com")//
				.contact(new Contact("moonlightL", "http://www.extlight.com", "445847261@qq.com"))// 聯繫
				.version("1.0")// 版本
				.build();
	}
}

爲了能更好的說明接口信息,咱們還能夠在 Controller 類上使用 Swagger2 相關注解說明信息。

咱們以 FastJsonController 爲例:

@Api(value = "FastJson測試", tags = { "測試接口" })
@RestController
@RequestMapping("fastjson")
public class FastJsonController {

	@ApiOperation("獲取用戶信息")
	@ApiImplicitParam(name = "name", value = "用戶名", dataType = "string", paramType = "query")
	@GetMapping("/test/{name}")
	public User test(@PathVariable("name") String name) {
		User user = new User();

		user.setId(1);
		user.setUsername(name);
		user.setPassword("jack123");
		user.setBirthday(new Date());

		return user;
	}
}

注意,上邊的方法是用 @GetMapping 註解,若是隻是使用 @RequestMapping 註解,不配置 method 屬性,那麼 API 文檔會生成 7 種請求方式。

啓動項目,打開瀏覽器訪問 http://localhost:8080/swagger-ui.html。結果以下圖:

image

十4、參考資料

相關文章
相關標籤/搜索