首页 后台正文

Spring Boot 入门详解教程之基础篇(二)

yuange 后台 2019-03-10 815 0 Spring Boot

一、前言

上一篇《Spring Boot 入门之基础篇(一)》介绍了 Spring Boot 的环境搭建以及项目启动打包等基础内容,本篇继续深入介绍 Spring Boot 与 Web 开发相关的知识。

二、整合模板引擎

由于 jsp 不被 SpringBoot 推荐使用,所以模板引擎主要介绍 Freemarker 和 Thymeleaf。

2.1 整合 Freemarker

2.1.1 添加 Freemarker 依赖


  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-freemarker</artifactId>

  4. </dependency>

2.1.2 添加 Freemarker 模板配置

在 application.properties 中添加如下内容:


  1. spring.freemarker.allow-request-override=false

  2. spring.freemarker.cache=true

  3. spring.freemarker.check-template-location=true

  4. spring.freemarker.charset=UTF-8

  5. spring.freemarker.content-type=text/html

  6. spring.freemarker.expose-request-attributes=false

  7. spring.freemarker.expose-session-attributes=false

  8. spring.freemarker.expose-spring-macro-helpers=false

  9. spring.freemarker.prefix=

  10. spring.freemarker.suffix=.ftl

上述配置都是默认值。

2.1.3 Freemarker 案例演示

在 controller 包中创建 FreemarkerController:


  1. @Controller

  2. @RequestMapping("freemarker")

  3. public class FreemarkerController {


  4. @RequestMapping("hello")

  5. public String hello(Map<String,Object> map) {


  6. map.put("msg", "Hello Freemarker");

  7. return "hello";

  8. }

  9. }

在 templates 目录中创建名为 hello.ftl 文件,内容如下:


  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>Document</title>

  6. <link href="/css/index.css" rel="stylesheet"/>

  7. </head>

  8. <body>

  9. <div>

  10. <h2>${msg}</h2>

  11. </div>

  12. </body>

  13. </html>

结果如下:

image

2.2 整合 Thymeleaf

2.2.1 添加 Thymeleaf 依赖

在 pom.xml 文件中添加:


  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-thymeleaf</artifactId>

  4. </dependency>

2.2.2 添加 Thymeleaf 模板配置

在 application.properties 中添加如下内容:


  1. spring.thymeleaf.cache=true

  2. spring.thymeleaf.prefix=classpath:/templates/

  3. spring.thymeleaf.suffix=.html

  4. spring.thymeleaf.mode=HTML5

  5. spring.thymeleaf.encoding=UTF-8

  6. spring.thymeleaf.content-type=text/html

上述配置都是默认值。

2.2.3 Thymeleaf 案例演示

在 controller 包中创建 ThymeleafController:


  1. @Controller

  2. @RequestMapping("thymeleaf")

  3. public class ThymeleafController {


  4. @RequestMapping("hello")

  5. public String hello(Map<String,Object> map) {

  6. map.put("msg", "Hello Thymeleaf");

  7. return "hello";

  8. }

  9. }

在 template 目录下创建名为 hello.html 的文件,内容如下:


  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>Document</title>

  6. <link href="/css/index.css" rel="stylesheet"/>

  7. </head>

  8. <body>

  9. <div>

  10. <h2 th:text="${msg}"></h2>

  11. </div>

  12. </body>

  13. </html>

结果如下:

image

三、整合 Fastjson

3.1 添加依赖


  1. <dependency>

  2. <groupId>com.alibaba</groupId>

  3. <artifactId>fastjson</artifactId>

  4. <version>1.2.35</version>

  5. </dependency>

3.2 整合 Fastjson

创建一个配置管理类 WebConfig ,如下:


  1. @Configuration

  2. public class WebConfig {


  3. @Bean

  4. public HttpMessageConverters fastJsonHttpMessageConverters() {

  5. FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();


  6. FastJsonConfig fastJsonConfig = new FastJsonConfig();

  7. fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);


  8. fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);


  9. HttpMessageConverter<?> converter = fastJsonHttpMessageConverter;


  10. return new HttpMessageConverters(converter);


  11. }

  12. }


3.3 演示案例:

创建一个实体类 User:


  1. public class User {


  2. private Integer id;


  3. private String username;


  4. private String password;


  5. private Date birthday;


  6. }


getter 和 setter 此处省略。

创建控制器类 FastjsonController :


  1. @Controller

  2. @RequestMapping("fastjson")

  3. public class FastJsonController {


  4. @RequestMapping("/test")

  5. @ResponseBody

  6. public User test() {

  7. User user = new User();


  8. user.setId(1);

  9. user.setUsername("jack");

  10. user.setPassword("jack123");

  11. user.setBirthday(new Date());


  12. return user;

  13. }

  14. }

打开浏览器,访问 http://localhost:8080/fastjson/test,结果如下图:

image

此时,还不能看出 Fastjson 是否正常工作,我们在 User 类中使用 Fastjson 的注解,如下内容:


  1. @JSONField(format="yyyy-MM-dd")

  2. private Date birthday;

再次访问 http://localhost:8080/fastjson/test,结果如下图:

image

日期格式与我们修改的内容格式一致,说明 Fastjson 整合成功。

四、自定义 Servlet

4.1 编写 Servlet


  1. public class ServletTest extends HttpServlet {


  2. @Override

  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

  4. doPost(req, resp);

  5. }


  6. @Override

  7. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

  8. resp.setContentType("text/html;charset=utf-8");

  9. resp.getWriter().write("自定义 Servlet");

  10. }


  11. }

4.2 注册 Servlet

将 Servelt 注册成 Bean。在上文创建的 WebConfig 类中添加如下代码:


  1. @Bean

  2. public ServletRegistrationBean servletRegistrationBean() {

  3. return new ServletRegistrationBean(new ServletTest(),"/servletTest");

  4. }

结果如下:

image

五、自定义过滤器/第三方过滤器

5.1 编写过滤器


  1. public class TimeFilter implements Filter {


  2. @Override

  3. public void init(FilterConfig filterConfig) throws ServletException {

  4. System.out.println("=======初始化过滤器=========");

  5. }


  6. @Override

  7. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

  8. throws IOException, ServletException {


  9. long start = System.currentTimeMillis();


  10. filterChain.doFilter(request, response);


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


  12. }


  13. @Override

  14. public void destroy() {

  15. System.out.println("=======销毁过滤器=========");

  16. }


  17. }

5.2 注册过滤器

要是该过滤器生效,有两种方式:

1) 使用 @Component 注解

2) 添加到过滤器链中,此方式适用于使用第三方的过滤器。将过滤器写到 WebConfig 类中,如下:


  1. @Bean

  2. public FilterRegistrationBean timeFilter() {

  3. FilterRegistrationBean registrationBean = new FilterRegistrationBean();


  4. TimeFilter timeFilter = new TimeFilter();

  5. registrationBean.setFilter(timeFilter);


  6. List<String> urls = new ArrayList<>();

  7. urls.add("/*");

  8. registrationBean.setUrlPatterns(urls);


  9. return registrationBean;

  10. }

结果如下:

image

六、自定义监听器

6.1 编写监听器


  1. public class ListenerTest implements ServletContextListener {


  2. @Override

  3. public void contextInitialized(ServletContextEvent sce) {

  4. System.out.println("监听器初始化...");

  5. }


  6. @Override

  7. public void contextDestroyed(ServletContextEvent sce) {


  8. }


  9. }

6.2 注册监听器

注册监听器为 Bean,在 WebConfig 配置类中添加如下代码:


  1. @Bean

  2. public ServletListenerRegistrationBean<ListenerTest> servletListenerRegistrationBean() {

  3. return new ServletListenerRegistrationBean<ListenerTest>(new ListenerTest());

  4. }

当启动容器时,结果如下:

image

针对自定义 Servlet、Filter 和 Listener 的配置,还有另一种方式:


  1. @SpringBootApplication

  2. public class SpringbootWebApplication implements ServletContextInitializer {


  3. @Override

  4. public void onStartup(ServletContext servletContext) throws ServletException {

  5. // 配置 Servlet

  6. servletContext.addServlet("servletTest",new ServletTest())

  7. .addMapping("/servletTest");

  8. // 配置过滤器

  9. servletContext.addFilter("timeFilter",new TimeFilter())

  10. .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");

  11. // 配置监听器

  12. servletContext.addListener(new ListenerTest());

  13. }


  14. public static void main(String[] args) {

  15. SpringApplication.run(SpringbootWebApplication.class, args);

  16. }

  17. }

七、自定义拦截器

7.1 编写拦截器

使用 @Component 让 Spring 管理其生命周期:


  1. @Component

  2. public class TimeInterceptor implements HandlerInterceptor {


  3. @Override

  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


  5. System.out.println("========preHandle=========");

  6. System.out.println(((HandlerMethod)handler).getBean().getClass().getName());

  7. System.out.println(((HandlerMethod)handler).getMethod().getName());


  8. request.setAttribute("startTime", System.currentTimeMillis());


  9. return true;

  10. }


  11. @Override

  12. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)

  13. throws Exception {


  14. System.out.println("========postHandle=========");

  15. Long start = (Long) request.getAttribute("startTime");

  16. System.out.println("耗时:"+(System.currentTimeMillis() - start));

  17. }


  18. @Override

  19. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)

  20. throws Exception {


  21. System.out.println("========afterCompletion=========");

  22. Long start = (Long) request.getAttribute("startTime");

  23. System.out.println("耗时:"+(System.currentTimeMillis() - start));


  24. System.out.println(exception);

  25. }


  26. }


7.2 注册拦截器

编写拦截器后,我们还需要将其注册到拦截器链中,如下配置:


  1. @Configuration

  2. public class WebConfig extends WebMvcConfigurerAdapter{


  3. @Autowired

  4. private TimeInterceptor timeInterceptor;



  5. @Override

  6. public void addInterceptors(InterceptorRegistry registry) {

  7. registry.addInterceptor(timeInterceptor);

  8. }


  9. }

请求一个 controller ,结果如下:

image

八、配置 AOP 切面

8.1 添加依赖


  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-aop</artifactId>

  4. </dependency>

8.2 编写切面类

使用 @Component,@Aspect 标记到切面类上:


  1. @Aspect

  2. @Component

  3. public class TimeAspect {


  4. @Around("execution(* com.light.springboot.controller.FastJsonController..*(..))")

  5. public Object method(ProceedingJoinPoint pjp) throws Throwable {


  6. System.out.println("=====Aspect处理=======");

  7. Object[] args = pjp.getArgs();

  8. for (Object arg : args) {

  9. System.out.println("参数为:" + arg);

  10. }


  11. long start = System.currentTimeMillis();


  12. Object object = pjp.proceed();


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


  14. return object;

  15. }

  16. }

请求 FastJsonController 控制器的方法,结果如下:

image

九、错误处理

9.1 友好页面

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


  1. @RestController

  2. @RequestMapping("fastjson")

  3. public class FastJsonController {


  4. @RequestMapping("/test")

  5. public User test() {

  6. User user = new User();


  7. user.setId(1);

  8. user.setUsername("jack");

  9. user.setPassword("jack123");

  10. user.setBirthday(new Date());


  11. // 模拟异常

  12. int i = 1/0;


  13. return user;

  14. }

  15. }

浏览器请求:http://localhost:8080/fastjson/test,结果如下:

image

当系统报错时,返回到页面的内容通常是一些杂乱的代码段,这种显示对用户来说不友好,因此我们需要自定义一个友好的提示系统异常的页面。

在 src/main/resources 下创建 /public/error,在该目录下再创建一个名为 5xx.html 文件,该页面的内容就是当系统报错时返回给用户浏览的内容:


  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>系统错误</title>

  6. <link href="/css/index.css" rel="stylesheet"/>

  7. </head>

  8. <body>

  9. <div>

  10. <h2>系统内部错误</h2>

  11. </div>

  12. </body>

  13. </html>

路径时固定的,Spring Boot 会在系统报错时将返回视图指向该目录下的文件。

如下图:

image

上边处理的 5xx 状态码的问题,接下来解决 404 状态码的问题。

当出现 404 的情况时,用户浏览的页面也不够友好,因此我们也需要自定义一个友好的页面给用户展示。

在 /public/error 目录下再创建一个名为 404.html 的文件:


  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>访问异常</title>

  6. <link href="/css/index.css" rel="stylesheet"/>

  7. </head>

  8. <body>

  9. <div>

  10. <h2>找不到页面</h2>

  11. </div>

  12. </body>

  13. </html>

我们请求一个不存在的资源,如:http://localhost:8080/fastjson/test2,结果如下图:

image

9.2 全局异常捕获

如果项目前后端是通过 JSON 进行数据通信,则当出现异常时可以常用如下方式处理异常信息。

编写一个类充当全局异常的处理类,需要使用 @ControllerAdvice 和 @ExceptionHandler 注解:


  1. @ControllerAdvice

  2. public class GlobalDefaultExceptionHandler {


  3. /**

  4. * 处理 Exception 类型的异常

  5. * @param e

  6. * @return

  7. */

  8. @ExceptionHandler(Exception.class)

  9. @ResponseBody

  10. public Map<String,Object> defaultExceptionHandler(Exception e) {


  11. Map<String,Object> map = new HashMap<String,Object>();

  12. map.put("code", 500);

  13. map.put("msg", e.getMessage());

  14. return map;

  15. }

  16. }


其中,方法名为任意名,入参一般使用 Exception 异常类,方法返回值可自定义。

启动项目,访问 http://localhost:8080/fastjson/test,结果如下图:

image

我们还可以自定义异常,在全局异常的处理类中捕获和判断,从而对不同的异常做出不同的处理。

十、文件上传和下载

10.1 添加依赖


  1. <!-- 工具 -->

  2. <dependency>

  3. <groupId>commons-io</groupId>

  4. <artifactId>commons-io</artifactId>

  5. <version>2.4</version>

  6. </dependency>

10.2 实现

编写一个实体类,用于封装返回信息:


  1. public class FileInfo {


  2. private String path;


  3. public FileInfo(String path) {

  4. this.path = path;

  5. }


  6. public String getPath() {

  7. return path;

  8. }


  9. public void setPath(String path) {

  10. this.path = path;

  11. }


  12. }

编写 Controller,用于处理文件上传下载:


  1. @RestController

  2. @RequestMapping("/file")

  3. public class FileController {


  4. private String path = "d:\\";


  5. @PostMapping

  6. public FileInfo upload(MultipartFile file) throws Exception {


  7. System.out.println(file.getName());

  8. System.out.println(file.getOriginalFilename());

  9. System.out.println(file.getSize());


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


  11. file.transferTo(localFile);


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

  13. }


  14. @GetMapping("/{id}")

  15. public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) {

  16. try (InputStream inputStream = new FileInputStream(new File(path, id + ".jpg"));

  17. OutputStream outputStream = response.getOutputStream();) {


  18. response.setContentType("application/x-download");

  19. response.addHeader("Content-Disposition", "attachment;filename=" + id + ".jpg");


  20. IOUtils.copy(inputStream, outputStream);

  21. } catch (Exception e) {

  22. e.printStackTrace();

  23. }

  24. }

  25. }

基本上都是在学习 javaweb 时用到的 API。

文件上传测试结果如下图:

image

十一、CORS 支持

前端页面:


  1. <!DOCTYPE html>

  2. <html>


  3. <head>

  4. <meta charset="UTF-8">

  5. <title>跨域测试</title>

  6. </head>


  7. <body>

  8. <button id="test">测试</button>

  9. <script type="text/javascript" src="jquery-1.12.3.min.js"></script>

  10. <script type="text/javascript">

  11. $(function() {

  12. $("#test").on("click", function() {

  13. $.ajax({

  14. "url": "http://localhost:8080/fastjson/test",

  15. "type": "get",

  16. "dataType": "json",

  17. "success": function(data) {

  18. console.log(data);

  19. }

  20. })

  21. });

  22. });

  23. </script>

  24. </body>


  25. </html>

通过 http 容器启动前端页面代码,笔者使用 Sublime Text 的插件启动的,测试结果如下:

image

从图中可知,前端服务器启动端口为 8088 与后端服务器 8080 不同源,因此出现跨域的问题。

现在开始解决跨域问题,可以两种维度控制客户端请求。

粗粒度控制

方式一


  1. @Configuration

  2. public class WebConfig {


  3. @Bean

  4. public WebMvcConfigurer corsConfigurer() {

  5. return new WebMvcConfigurerAdapter() {

  6. @Override

  7. public void addCorsMappings(CorsRegistry registry) {

  8. registry.addMapping("/fastjson/**")

  9. .allowedOrigins("http://localhost:8088");// 允许 8088 端口访问

  10. }

  11. };

  12. }

  13. }

方式二


  1. @Configuration

  2. public class WebConfig extends WebMvcConfigurerAdapter{


  3. @Override

  4. public void addCorsMappings(CorsRegistry registry) {

  5. registry.addMapping("/fastjson/**")

  6. .allowedOrigins("http://localhost:8088");// 允许 8088 端口访问

  7. }

  8. }

配置后,重新发送请求,结果如下:

image

细粒度控制

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


  1. @RequestMapping("/test")

  2. @CrossOrigin(origins="http://localhost:8088")

  3. public User test() {

  4. User user = new User();


  5. user.setId(1);

  6. user.setUsername("jack");

  7. user.setPassword("jack123");

  8. user.setBirthday(new Date());


  9. return user;

  10. }

在使用该注解时,需要注意 @RequestMapping 使用的请求方式类型,即 GET 或 POST。

十二、整合 WebSocket

12.1 添加依赖


  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-websocket</artifactId>

  4. </dependency>

12.2 实现方式

方式一:

该方式只适用于通过 jar 包直接运行项目的情况。

WebSocket 配置类:


  1. @Configuration

  2. public class WebSocketConfig {


  3. @Bean

  4. public ServerEndpointExporter serverEndpointExporter() {

  5. return new ServerEndpointExporter();

  6. }


  7. }

WebSocket 处理类:


  1. @ServerEndpoint(value = "/webSocketServer/{userName}")

  2. @Component

  3. public class WebSocketServer {


  4. private static final Set<WebSocketServer> connections = new CopyOnWriteArraySet<>();


  5. private String nickname;

  6. private Session session;


  7. private static String getDatetime(Date date) {

  8. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  9. return format.format(date);

  10. }


  11. @OnOpen

  12. public void start(@PathParam("userName") String userName, Session session) {

  13. this.nickname = userName;

  14. this.session = session;

  15. connections.add(this);

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

  17. broadcast(message);

  18. }


  19. @OnClose

  20. public void end() {

  21. connections.remove(this);

  22. String message = String.format("* %s %s", nickname, "退出聊天!");

  23. broadcast(message);

  24. }


  25. @OnMessage

  26. public void pushMsg(String message) {

  27. broadcast("【" + this.nickname + "】" + getDatetime(new Date()) + " : " + message);

  28. }


  29. @OnError

  30. public void onError(Throwable t) throws Throwable {


  31. }


  32. private static void broadcast(String msg) {

  33. // 广播形式发送消息

  34. for (WebSocketServer client : connections) {

  35. try {

  36. synchronized (client) {

  37. client.session.getBasicRemote().sendText(msg);

  38. }

  39. } catch (IOException e) {

  40. connections.remove(client);

  41. try {

  42. client.session.close();

  43. } catch (IOException e1) {

  44. e.printStackTrace();

  45. }

  46. String message = String.format("* %s %s", client.nickname, "断开连接");

  47. broadcast(message);

  48. }

  49. }

  50. }

  51. }

前端页面:


  1. <!DOCTYPE html>

  2. <html>


  3. <head>

  4. <meta charset="UTF-8">

  5. <link rel="stylesheet" href="css/bootstrap.min.css">

  6. <link rel="stylesheet" href="css/bootstrap-theme.min.css">

  7. <script src="js/jquery-1.12.3.min.js"></script>

  8. <script src="js/bootstrap.js"></script>

  9. <style type="text/css">

  10. #msg {

  11. height: 400px;

  12. overflow-y: auto;

  13. }


  14. #userName {

  15. width: 200px;

  16. }


  17. #logout {

  18. display: none;

  19. }

  20. </style>

  21. <title>webSocket测试</title>

  22. </head>


  23. <body>

  24. <div>

  25. <div id="tou">webSocket及时聊天Demo程序</div>

  26. <p id="logout">

  27. <button class="btn btn-danger" id="logout-btn">退出</button>

  28. </p>

  29. <div id="msg"></div>

  30. <div>

  31. <div>

  32. <input type="text" placeholder="发送信息..." id="message"> <span>

  33. <button class="btn btn-default" type="button" id="send"

  34. disabled="disabled">发送</button>

  35. </span>

  36. </div>

  37. <div>

  38. <input id="userName" type="text" name="userName" placeholder="输入您的用户名" />

  39. <button class="btn btn-default" type="button" id="connection-btn">建立连接</button>

  40. </div>

  41. <!-- /input-group -->

  42. </div>

  43. <!-- /.col-lg-6 -->

  44. </div>

  45. <!-- /.row -->

  46. </div>

  47. <script type="text/javascript">

  48. $(function() {

  49. var websocket;

  50. $("#connection-btn").bind("click", function() {

  51. var userName = $("#userName").val();

  52. if (userName == null || userName == "") {

  53. alert("请输入您的用户名");

  54. return;

  55. }

  56. connection(userName);

  57. });


  58. function connection(userName) {

  59. var host = window.location.host;

  60. if ('WebSocket' in window) {

  61. websocket = new WebSocket("ws://" + host +

  62. "/webSocketServer/" + userName);

  63. } else if ('MozWebSocket' in window) {

  64. websocket = new MozWebSocket("ws://" + host +

  65. "/webSocketServer/" + userName);

  66. }

  67. websocket.onopen = function(evnt) {

  68. $("#tou").html("链接服务器成功!")

  69. $("#send").prop("disabled", "");

  70. $("#connection-btn").prop("disabled", "disabled");

  71. $("#logout").show();

  72. };

  73. websocket.onmessage = function(evnt) {

  74. $("#msg").html($("#msg").html() + "<br/>" + evnt.data);

  75. };

  76. websocket.onerror = function(evnt) {

  77. $("#tou").html("报错!")

  78. };

  79. websocket.onclose = function(evnt) {

  80. $("#tou").html("与服务器断开了链接!");

  81. $("#send").prop("disabled", "disabled");

  82. $("#connection-btn").prop("disabled", "");

  83. $("#logout").hide();

  84. }

  85. }


  86. function send() {

  87. if (websocket != null) {

  88. var $message = $("#message");

  89. var data = $message.val();

  90. if (data == null || data == "") {

  91. return;

  92. }

  93. websocket.send(data);

  94. $message.val("");

  95. } else {

  96. alert('未与服务器链接.');

  97. }

  98. }


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

  100. send();

  101. });


  102. $(document).on("keypress", function(event) {

  103. if (event.keyCode == "13") {

  104. send();

  105. }

  106. });


  107. $("#logout-btn").on("click", function() {

  108. websocket.close(); //关闭TCP连接

  109. });

  110. });

  111. </script>

  112. </body>


  113. </html>


演示图如下:

image

如果使用该方式实现 WebSocket 功能并打包成 war 运行会报错:


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

方式二:

该方式适用于 jar 包方式运行和 war 方式运行。

WebSocket 配置类:


  1. @Configuration

  2. @EnableWebSocket

  3. public class WebSocketConfig implements WebSocketConfigurer {

  4. @Override

  5. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

  6. registry.addHandler(webSocketServer(), "/webSocketServer/*");

  7. }


  8. @Bean

  9. public WebSocketHandler webSocketServer() {

  10. return new WebSocketServer();

  11. }

  12. }

WebSocket 处理类:


  1. public class WebSocketServer extends TextWebSocketHandler {


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


  3. private static String getDatetime(Date date) {

  4. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  5. return format.format(date);

  6. }


  7. /**

  8. * 建立连接

  9. */

  10. @Override

  11. public void afterConnectionEstablished(WebSocketSession session) throws Exception {


  12. String uri = session.getUri().toString();

  13. String userName = uri.substring(uri.lastIndexOf("/") + 1);


  14. String nickname = URLDecoder.decode(userName, "utf-8");


  15. connections.put(session, nickname);

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


  17. broadcast(new TextMessage(message));

  18. }


  19. /**

  20. * 断开连接

  21. */

  22. @Override

  23. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

  24. String nickname = connections.remove(session);

  25. String message = String.format("* %s %s", nickname, "退出聊天!");


  26. broadcast(new TextMessage(message));

  27. }


  28. /**

  29. * 处理消息

  30. */

  31. @Override

  32. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

  33. String msg = "【" + connections.get(session) + "】" + getDatetime(new Date()) + " : " + message.getPayload();


  34. broadcast(new TextMessage(msg));

  35. }


  36. private static void broadcast(TextMessage msg) {

  37. // 广播形式发送消息

  38. for (WebSocketSession session : connections.keySet()) {

  39. try {

  40. synchronized (session) {

  41. session.sendMessage(msg);

  42. }

  43. } catch (Exception e) {

  44. connections.remove(session);

  45. try {

  46. session.close();

  47. } catch (Exception e2) {

  48. e2.printStackTrace();

  49. }

  50. String message = String.format("* %s %s", connections.get(session), "断开连接");

  51. broadcast(new TextMessage(message));

  52. }

  53. }

  54. }

  55. }


运行结果与上图一致。

十三、整合 Swagger2

13.1 添加依赖


  1. <dependency>

  2. <groupId>io.springfox</groupId>

  3. <artifactId>springfox-swagger2</artifactId>

  4. <version>2.7.0</version>

  5. </dependency>

  6. <dependency>

  7. <groupId>io.springfox</groupId>

  8. <artifactId>springfox-swagger-ui</artifactId>

  9. <version>2.7.0</version>

  10. </dependency>

13.2 配置

重新创建一个配置类,如下:


  1. @Configuration

  2. @EnableSwagger2

  3. public class Swagger2Configuration {


  4. @Bean

  5. public Docket accessToken() {

  6. return new Docket(DocumentationType.SWAGGER_2)

  7. .groupName("api")// 定义组

  8. .select() // 选择那些路径和 api 会生成 document

  9. .apis(RequestHandlerSelectors.basePackage("com.light.springboot.controller")) // 拦截的包路径

  10. .paths(PathSelectors.regex("/*/.*"))// 拦截的接口路径

  11. .build() // 创建

  12. .apiInfo(apiInfo()); // 配置说明

  13. }


  14. private ApiInfo apiInfo() {

  15. return new ApiInfoBuilder()//

  16. .title("Spring Boot 之 Web 篇")// 标题

  17. .description("spring boot Web 相关内容")// 描述

  18. .termsOfServiceUrl("http://www.extlight.com")//

  19. .contact(new Contact("moonlightL", "http://www.extlight.com", "445847261@qq.com"))// 联系

  20. .version("1.0")// 版本

  21. .build();

  22. }

  23. }

为了能更好的说明接口信息,我们还可以在 Controller 类上使用 Swagger2 相关注解说明信息。

我们以 FastJsonController 为例:


  1. @Api(value = "FastJson测试", tags = { "测试接口" })

  2. @RestController

  3. @RequestMapping("fastjson")

  4. public class FastJsonController {


  5. @ApiOperation("获取用户信息")

  6. @ApiImplicitParam(name = "name", value = "用户名", dataType = "string", paramType = "query")

  7. @GetMapping("/test/{name}")

  8. public User test(@PathVariable("name") String name) {

  9. User user = new User();


  10. user.setId(1);

  11. user.setUsername(name);

  12. user.setPassword("jack123");

  13. user.setBirthday(new Date());


  14. return user;

  15. }

  16. }

注意,上边的方法是用 @GetMapping 注解,如果只是使用 @RequestMapping 注解,不配置 method 属性,那么 API 文档会生成 7 种请求方式。

启动项目,打开浏览器访问 http://localhost:8080/swagger-ui.html。结果如下图:

image


评论

在线客服-可直接交谈

您好!有什么需要可以为您服务吗?