SpringBoot在IDEA中实现热部署

1、开启IDEA的自动编译(静态)

具体步骤:打开顶部工具栏 File -> Settings -> Default Settings -> Build -> Compiler 然后勾选 Build project automatically 。
02-SpringBoot进阶 - 图1

2、开启IDEA的自动编译(动态)

具体步骤:同时按住 Ctrl + Shift + Alt + / 然后进入Registry ,勾选自动编译并调整延时参数。

  • compiler.automake.allow.when.app.running -> 自动编译
  • compile.document.save.trigger.delay -> 自动更新文件

PS:网上极少有人提到compile.document.save.trigger.delay 它主要是针对静态文件如JS CSS的更新,将延迟时间减少后,直接按F5刷新页面就能看到效果!
02-SpringBoot进阶 - 图2

3、开启IDEA的热部署策略(非常重要)

具体步骤:顶部菜单- >Edit Configurations->SpringBoot插件->目标项目->勾选热更新。
02-SpringBoot进阶 - 图3

4、在项目添加热部署插件(可选)

温馨提示: 如果因为旧项目十分臃肿,导致每次都自动热重启很慢而影响开发效率,笔者建议直接在POM移除spring-boot-devtools依赖,然后使用Control+Shift+F9进行手工免启动快速更新!!

具体步骤:在POM文件添加热部署插件
02-SpringBoot进阶 - 图4

5、关闭浏览器缓存(重要)

打开谷歌浏览器,打开F12的Network选项栏,然后勾选【✅】Disable cache 。
02-SpringBoot进阶 - 图5

SpringBoot全局异常处理

在SpringBoot项目中,由于我们项目中难免会出现各种异常,一个个处理并不一定能包括全部异常,这时候我们需要一个全局类来处理项目中所抛出的异常。

@ControllerAdvice注解的三种使用场景

@ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

灵活使用这三个功能,可以帮助我们简化很多工作,需要注意的是,这是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用,下面分别来看。

全局异常处理

使用 @ControllerAdvice 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:

  1. @ControllerAdvice
  2. public class MyGlobalExceptionHandler {
  3. @ExceptionHandler(Exception.class)
  4. public ModelAndView customException(Exception e) {
  5. ModelAndView mv = new ModelAndView();
  6. mv.addObject("message", e.getMessage());
  7. mv.setViewName("myerror");
  8. return mv;
  9. }
  10. }

在该类中,可以定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法、专门处理数组越界的方法…,也可以直接向上面代码一样,在一个方法中处理所有的异常信息。
@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。

全局数据绑定

全局数据绑定功能可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据。
使用步骤,首先定义全局数据,如下:

  1. @ControllerAdvice
  2. public class MyGlobalExceptionHandler {
  3. @ModelAttribute(name = "md")
  4. public Map<String,Object> mydata() {
  5. HashMap<String, Object> map = new HashMap<>();
  6. map.put("age", 99);
  7. map.put("gender", "男");
  8. return map;
  9. }
  10. }

使用 @ModelAttribute 注解标记该方法的返回数据是一个全局数据,默认情况下,这个全局数据的 key 就是返回的变量名,value 就是方法返回值,当然开发者可以通过 @ModelAttribute 注解的 name 属性去重新指定 key。
定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据:

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String hello(Model model) {
  5. Map<String, Object> map = model.asMap();
  6. System.out.println(map);
  7. int i = 1 / 0;
  8. return "hello controller advice";
  9. }
  10. }

全局数据预处理

考虑我有两个实体类,Book 和 Author,分别定义如下:

  1. @Data
  2. public class Book {
  3. private String name;
  4. private Long price;
  5. }
  6. @Data
  7. public class Author {
  8. private String name;
  9. private Integer age;
  10. }

此时,如果我定义一个数据添加接口,如下:

  1. @PostMapping("/book")
  2. public void addBook(Book book, Author author) {
  3. System.out.println(book);
  4. System.out.println(author);
  5. }

这个时候,添加操作就会有问题,因为两个实体类都有一个 name 属性,从前端传递时 ,无法区分。此时,通过 @ControllerAdvice 的全局数据预处理可以解决这个问题
解决步骤如下:
1.给接口中的变量取别名

  1. @PostMapping("/book")
  2. public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
  3. System.out.println(book);
  4. System.out.println(author);
  5. }

2.进行请求数据预处理
在 @ControllerAdvice 标记的类中添加如下代码:

  1. @InitBinder("b")
  2. public void b(WebDataBinder binder) {
  3. binder.setFieldDefaultPrefix("b.");
  4. }
  5. @InitBinder("a")
  6. public void a(WebDataBinder binder) {
  7. binder.setFieldDefaultPrefix("a.");
  8. }

@InitBinder(“b”) 注解表示该方法用来处理和Book和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有b前缀.
3.发送请求
请求发送时,通过给不同对象的参数添加不同的前缀,可以实现参数的区分.
image.png

基本用法

定义一个全局异常配置类:

  1. import com.ck.syscheck.utils.JsonResult;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.beans.TypeMismatchException;
  5. import org.springframework.beans.ConversionNotSupportedException;
  6. import org.springframework.http.converter.HttpMessageNotReadableException;
  7. import org.springframework.http.converter.HttpMessageNotWritableException;
  8. import org.springframework.web.HttpMediaTypeNotAcceptableException;
  9. import org.springframework.web.HttpRequestMethodNotSupportedException;
  10. import org.springframework.web.bind.MissingServletRequestParameterException;
  11. import org.springframework.web.bind.annotation.ControllerAdvice;
  12. import org.springframework.web.bind.annotation.ExceptionHandler;
  13. import org.springframework.web.bind.annotation.ResponseBody;
  14. import java.io.IOException;
  15. /**
  16. * 定义全局异常类
  17. */
  18. @ControllerAdvice
  19. @ResponseBody
  20. public class GlobalExceptionHandler {
  21. private static final String logExceptionFormat = "Capture Exception By GlobalExceptionHandler: Code: %s Detail: %s";
  22. private static Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  23. /**
  24. * 运行时异常
  25. *
  26. * @param ex
  27. * @return
  28. */
  29. @ExceptionHandler(RuntimeException.class)
  30. public String runtimeExceptionHandler(RuntimeException ex) {
  31. return resultFormat(1, ex);
  32. }
  33. /**
  34. * 空指针异常
  35. *
  36. * @param ex
  37. * @return
  38. */
  39. @ExceptionHandler(NullPointerException.class)
  40. public String nullPointerExceptionHandler(NullPointerException ex) {
  41. System.err.println("NullPointerException:");
  42. return resultFormat(2, ex);
  43. }
  44. /**
  45. * 类型转换异常
  46. *
  47. * @param ex
  48. * @return
  49. */
  50. @ExceptionHandler(ClassCastException.class)
  51. public String classCastExceptionHandler(ClassCastException ex) {
  52. return resultFormat(3, ex);
  53. }
  54. /**
  55. * IO异常
  56. *
  57. * @param ex
  58. * @return
  59. */
  60. @ExceptionHandler(IOException.class)
  61. public String iOExceptionHandler(IOException ex) {
  62. return resultFormat(4, ex);
  63. }
  64. /**
  65. * 未知方法异常
  66. *
  67. * @param ex
  68. * @return
  69. */
  70. @ExceptionHandler(NoSuchMethodException.class)
  71. public String noSuchMethodExceptionHandler(NoSuchMethodException ex) {
  72. return resultFormat(5, ex);
  73. }
  74. /**
  75. * 数组越界异常
  76. *
  77. * @param ex
  78. * @return
  79. */
  80. @ExceptionHandler(IndexOutOfBoundsException.class)
  81. public String indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
  82. return resultFormat(6, ex);
  83. }
  84. /**
  85. * 400错误
  86. *
  87. * @param ex
  88. * @return
  89. */
  90. @ExceptionHandler({HttpMessageNotReadableException.class})
  91. public String requestNotReadable(HttpMessageNotReadableException ex) {
  92. System.out.println("400..requestNotReadable");
  93. return resultFormat(7, ex);
  94. }
  95. /**
  96. * 400错误
  97. *
  98. * @param ex
  99. * @return
  100. */
  101. @ExceptionHandler({TypeMismatchException.class})
  102. public String requestTypeMismatch(TypeMismatchException ex) {
  103. System.out.println("400..TypeMismatchException");
  104. return resultFormat(8, ex);
  105. }
  106. /**
  107. * 400错误
  108. *
  109. * @param ex
  110. * @return
  111. */
  112. @ExceptionHandler({MissingServletRequestParameterException.class})
  113. public String requestMissingServletRequest(MissingServletRequestParameterException ex) {
  114. System.out.println("400..MissingServletRequest");
  115. return resultFormat(9, ex);
  116. }
  117. /**
  118. * 405错误
  119. *
  120. * @param ex
  121. * @return
  122. */
  123. @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
  124. public String request405(HttpRequestMethodNotSupportedException ex) {
  125. return resultFormat(10, ex);
  126. }
  127. /**
  128. * 406错误
  129. *
  130. * @param ex
  131. * @return
  132. */
  133. @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
  134. public String request406(HttpMediaTypeNotAcceptableException ex) {
  135. System.out.println("406...");
  136. return resultFormat(11, ex);
  137. }
  138. /**
  139. * 500错误
  140. *
  141. * @param ex
  142. * @return
  143. */
  144. @ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
  145. public String server500(RuntimeException ex) {
  146. System.out.println("500...");
  147. return resultFormat(12, ex);
  148. }
  149. /**
  150. * 栈溢出
  151. *
  152. * @param ex
  153. * @return
  154. */
  155. @ExceptionHandler({StackOverflowError.class})
  156. public String requestStackOverflow(StackOverflowError ex) {
  157. return resultFormat(13, ex);
  158. }
  159. /**
  160. * 除数不能为0
  161. *
  162. * @param ex
  163. * @return
  164. */
  165. @ExceptionHandler({ArithmeticException.class})
  166. public String arithmeticException(ArithmeticException ex) {
  167. return resultFormat(13, ex);
  168. }
  169. /**
  170. * 其他错误
  171. *
  172. * @param ex
  173. * @return
  174. */
  175. @ExceptionHandler({Exception.class})
  176. public String exception(Exception ex) {
  177. return resultFormat(14, ex);
  178. }
  179. /**
  180. * 格式化结构,将其转换为JSon格式
  181. * 这里使用alibaba的fastjson依赖
  182. *
  183. * @param code
  184. * @param ex
  185. * @param <T>
  186. * @return
  187. */
  188. private <T extends Throwable> String resultFormat(Integer code, T ex) {
  189. ex.printStackTrace();
  190. log.error(String.format(logExceptionFormat, code, ex.getMessage()));
  191. return JsonResult.failed(code, ex.getMessage());
  192. }
  193. }

JsonResult 是前后端分离数据交互格式

  1. @Data
  2. public class JsonResult implements Serializable {
  3. /**
  4. * 返回码 非0即失败
  5. */
  6. private int code;
  7. /**
  8. * 消息提示
  9. */
  10. private String msg;
  11. /**
  12. * 返回的数据
  13. */
  14. private Map<String, Object> data;
  15. public JsonResult() {
  16. }
  17. public JsonResult(int code, String msg, Map<String, Object> data) {
  18. this.code = code;
  19. this.msg = msg;
  20. this.data = data;
  21. }
  22. public static String success() {
  23. return success(new HashMap(0));
  24. }
  25. public static String success(Map<String, Object> data) {
  26. return JSON.toJSONString(new JsonResult(0, "解析成功", data));
  27. }
  28. public static String failed() {
  29. return failed("解析失败");
  30. }
  31. public static String failed(String msg) {
  32. return failed(-1, msg);
  33. }
  34. public static String failed(int code, String msg) {
  35. return JSON.toJSONString(new JsonResult(code, msg, new HashMap(0)));
  36. }
  37. }

必要注解:
@ControllerAdvice_@ControllerAdvice_ 是对Controller的增强,不用任何的配置,只要把这个类放在项目中,Spring能扫描到的地方。就可以实现全局异常的回调。

@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。
@ControllerAdvice可以和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。
首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。
此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。

SpringBoot中@ControllerAdvice结合@InitBinder、@ModelAttribute、@ExceptionHandler的使用参考:
https://blog.csdn.net/backbug/article/details/105317630

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface ControllerAdvice {
  6. @AliasFor("basePackages")
  7. String[] value() default {};
  8. @AliasFor("value")
  9. String[] basePackages() default {};
  10. Class<?>[] basePackageClasses() default {};
  11. Class<?>[] assignableTypes() default {};
  12. Class<? extends Annotation>[] annotations() default {};
  13. }

高级用法

参考开源项目RuoYi,看看它的全局异常处理是怎么搞的
https://github.com/yangzongzhuan/RuoYi

定义错误页面

RuoYi\ruoyi-admin\src\main\resources\templates\error\404.html
image.png

全局异常处理类

RuoYi\ruoyi-framework\src\main\java\com\ruoyi\framework\web\exception\GlobalExceptionHandler.java

  1. package com.ruoyi.framework.web.exception;
  2. import javax.servlet.http.HttpServletRequest;
  3. import org.apache.shiro.authz.AuthorizationException;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.validation.BindException;
  7. import org.springframework.web.HttpRequestMethodNotSupportedException;
  8. import org.springframework.web.bind.annotation.ExceptionHandler;
  9. import org.springframework.web.bind.annotation.RestControllerAdvice;
  10. import org.springframework.web.servlet.ModelAndView;
  11. import com.ruoyi.common.core.domain.AjaxResult;
  12. import com.ruoyi.common.exception.BusinessException;
  13. import com.ruoyi.common.exception.DemoModeException;
  14. import com.ruoyi.common.utils.ServletUtils;
  15. import com.ruoyi.common.utils.security.PermissionUtils;
  16. /**
  17. * 全局异常处理器
  18. *
  19. * @author ruoyi
  20. */
  21. @RestControllerAdvice
  22. public class GlobalExceptionHandler
  23. {
  24. private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  25. /**
  26. * 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
  27. */
  28. @ExceptionHandler(AuthorizationException.class)
  29. public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e)
  30. {
  31. log.error(e.getMessage(), e);
  32. if (ServletUtils.isAjaxRequest(request))
  33. {
  34. return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
  35. }
  36. else
  37. {
  38. ModelAndView modelAndView = new ModelAndView();
  39. modelAndView.setViewName("error/unauth");
  40. return modelAndView;
  41. }
  42. }
  43. /**
  44. * 请求方式不支持
  45. */
  46. @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
  47. public AjaxResult handleException(HttpRequestMethodNotSupportedException e)
  48. {
  49. log.error(e.getMessage(), e);
  50. return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
  51. }
  52. /**
  53. * 拦截未知的运行时异常
  54. */
  55. @ExceptionHandler(RuntimeException.class)
  56. public AjaxResult notFount(RuntimeException e)
  57. {
  58. log.error("运行时异常:", e);
  59. return AjaxResult.error("运行时异常:" + e.getMessage());
  60. }
  61. /**
  62. * 系统异常
  63. */
  64. @ExceptionHandler(Exception.class)
  65. public AjaxResult handleException(Exception e)
  66. {
  67. log.error(e.getMessage(), e);
  68. return AjaxResult.error("服务器错误,请联系管理员");
  69. }
  70. /**
  71. * 业务异常
  72. */
  73. @ExceptionHandler(BusinessException.class)
  74. public Object businessException(HttpServletRequest request, BusinessException e)
  75. {
  76. log.error(e.getMessage(), e);
  77. if (ServletUtils.isAjaxRequest(request))
  78. {
  79. return AjaxResult.error(e.getMessage());
  80. }
  81. else
  82. {
  83. ModelAndView modelAndView = new ModelAndView();
  84. modelAndView.addObject("errorMessage", e.getMessage());
  85. modelAndView.setViewName("error/business");
  86. return modelAndView;
  87. }
  88. }
  89. /**
  90. * 自定义验证异常
  91. */
  92. @ExceptionHandler(BindException.class)
  93. public AjaxResult validatedBindException(BindException e)
  94. {
  95. log.error(e.getMessage(), e);
  96. String message = e.getAllErrors().get(0).getDefaultMessage();
  97. return AjaxResult.error(message);
  98. }
  99. /**
  100. * 演示模式异常
  101. */
  102. @ExceptionHandler(DemoModeException.class)
  103. public AjaxResult demoModeException(DemoModeException e)
  104. {
  105. return AjaxResult.error("演示模式,不允许操作");
  106. }
  107. }

前后端数据交互 AjaxResult

  1. public class AjaxResult extends HashMap<String, Object>
  2. {
  3. private static final long serialVersionUID = 1L;
  4. /** 状态码 */
  5. public static final String CODE_TAG = "code";
  6. /** 返回内容 */
  7. public static final String MSG_TAG = "msg";
  8. /** 数据对象 */
  9. public static final String DATA_TAG = "data";
  10. /**
  11. * 状态类型
  12. */
  13. public enum Type
  14. {
  15. /** 成功 */
  16. SUCCESS(0),
  17. /** 警告 */
  18. WARN(301),
  19. /** 错误 */
  20. ERROR(500);
  21. private final int value;
  22. Type(int value)
  23. {
  24. this.value = value;
  25. }
  26. public int value()
  27. {
  28. return this.value;
  29. }
  30. }
  31. /**
  32. * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
  33. */
  34. public AjaxResult()
  35. {
  36. }
  37. /**
  38. * 初始化一个新创建的 AjaxResult 对象
  39. *
  40. * @param type 状态类型
  41. * @param msg 返回内容
  42. */
  43. public AjaxResult(Type type, String msg)
  44. {
  45. super.put(CODE_TAG, type.value);
  46. super.put(MSG_TAG, msg);
  47. }
  48. /**
  49. * 初始化一个新创建的 AjaxResult 对象
  50. *
  51. * @param type 状态类型
  52. * @param msg 返回内容
  53. * @param data 数据对象
  54. */
  55. public AjaxResult(Type type, String msg, Object data)
  56. {
  57. super.put(CODE_TAG, type.value);
  58. super.put(MSG_TAG, msg);
  59. if (StringUtils.isNotNull(data))
  60. {
  61. super.put(DATA_TAG, data);
  62. }
  63. }
  64. /**
  65. * 方便链式调用
  66. *
  67. * @param key 键
  68. * @param value 值
  69. * @return 数据对象
  70. */
  71. @Override
  72. public AjaxResult put(String key, Object value)
  73. {
  74. super.put(key, value);
  75. return this;
  76. }
  77. /**
  78. * 返回成功消息
  79. *
  80. * @return 成功消息
  81. */
  82. public static AjaxResult success()
  83. {
  84. return AjaxResult.success("操作成功");
  85. }
  86. /**
  87. * 返回成功数据
  88. *
  89. * @return 成功消息
  90. */
  91. public static AjaxResult success(Object data)
  92. {
  93. return AjaxResult.success("操作成功", data);
  94. }
  95. /**
  96. * 返回成功消息
  97. *
  98. * @param msg 返回内容
  99. * @return 成功消息
  100. */
  101. public static AjaxResult success(String msg)
  102. {
  103. return AjaxResult.success(msg, null);
  104. }
  105. /**
  106. * 返回成功消息
  107. *
  108. * @param msg 返回内容
  109. * @param data 数据对象
  110. * @return 成功消息
  111. */
  112. public static AjaxResult success(String msg, Object data)
  113. {
  114. return new AjaxResult(Type.SUCCESS, msg, data);
  115. }
  116. /**
  117. * 返回警告消息
  118. *
  119. * @param msg 返回内容
  120. * @return 警告消息
  121. */
  122. public static AjaxResult warn(String msg)
  123. {
  124. return AjaxResult.warn(msg, null);
  125. }
  126. /**
  127. * 返回警告消息
  128. *
  129. * @param msg 返回内容
  130. * @param data 数据对象
  131. * @return 警告消息
  132. */
  133. public static AjaxResult warn(String msg, Object data)
  134. {
  135. return new AjaxResult(Type.WARN, msg, data);
  136. }
  137. /**
  138. * 返回错误消息
  139. *
  140. * @return
  141. */
  142. public static AjaxResult error()
  143. {
  144. return AjaxResult.error("操作失败");
  145. }
  146. /**
  147. * 返回错误消息
  148. *
  149. * @param msg 返回内容
  150. * @return 警告消息
  151. */
  152. public static AjaxResult error(String msg)
  153. {
  154. return AjaxResult.error(msg, null);
  155. }
  156. /**
  157. * 返回错误消息
  158. *
  159. * @param msg 返回内容
  160. * @param data 数据对象
  161. * @return 警告消息
  162. */
  163. public static AjaxResult error(String msg, Object data)
  164. {
  165. return new AjaxResult(Type.ERROR, msg, data);
  166. }
  167. }

SpringBoot加载顺序

02-SpringBoot进阶 - 图8
_@_Order(num) //加载顺序 num数字越小 优先级越高
加载顺序打印

  1. Java HotSpot(TM) 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
  2. The service to start.
  3. . ____ _ __ _ _
  4. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
  5. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
  6. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
  7. ' |____| .__|_| |_|_| |_\__, | / / / /
  8. =========|_|==============|___/=/_/_/_/
  9. :: Spring Boot :: (v2.4.3)
  10. 2022-02-08 11:00:34.993 INFO 13164 --- [ main] com.neo.CommandLineRunnerApplication :
  11. ...
  12. 2022-02-08 11:00:36.407 INFO 13164 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
  13. 2022-02-08 11:00:36.419 INFO 13164 --- [ main] com.neo.CommandLineRunnerApplication : Started CommandLineRunnerApplication in 1.929 seconds (JVM running for 3.343)
  14. The OrderRunner1 start to initialize ...
  15. The OrderRunner2 start to initialize ...
  16. The Runner start to initialize ...
  17. The service has started.

SpringBoot自动装配原理

https://www.cnblogs.com/hellokuangshen/p/12468522.html
https://blog.csdn.net/qq_42025798/article/details/122058013
【狂神说Java】SpringBoot最新教程IDEA版通俗易懂
https://blog.csdn.net/qq_42025798/article/details/121974573
【狂神说Java笔记】Java后端开发工程师学习笔记
https://blog.csdn.net/qq_42025798/article/details/119192030
03-SpringBoot源码分析

VBlog上传到GitHub项目

https://github.com/lenve/VBlog
02-SpringBoot进阶 - 图9
vue项目不上传的文件和目录
02-SpringBoot进阶 - 图10
springboot项目不上传的文件和文件夹
02-SpringBoot进阶 - 图11

SpringBoot事务管理

SpringBoot 使用事务非常简单,底层依然采用的是Spring本身提供的事务管理
• 在入口类中使用注解 @EnableTransactionManagement 开启事务支持
• 在访问数据库的Service方法上添加注解 _@_Transactional 即可
案例思路
通过SpringBoot +MyBatis实现对数据库学生表的更新操作,在service层的方法中构建异常,查看事务是否生效;

实现步骤
1)在StudentController中添加更新学生的方法

  1. @Controller
  2. public class SpringBootController {
  3. @Autowired
  4. private StudentService studentService;
  5. @RequestMapping(value = "/springBoot/update")
  6. public @ResponseBody Object update() {
  7. Student student = new Student();
  8. student.setId(1);
  9. student.setName("Mark");
  10. student.setAge(100);
  11. int updateCount = studentService.update(student);
  12. return updateCount;
  13. }
  14. }

2)在StudentService接口中添加更新学生方法

  1. public interface StudentService {
  2. int update(Student student);
  3. }

3)在StudentServiceImpl接口实现类中对更新学生方法进行实现,并构建一个异常,同时在该方法上加@Transactional注解

  1. @Override
  2. @Transactional //添加此注解说明该方法添加的事务管理
  3. public int update(Student student) {
  4. int updateCount = studentMapper.updateByPrimaryKeySelective(student);
  5. System.out.println("更新结果:" + updateCount);
  6. //在此构造一个除数为0的异常,测试事务是否起作用
  7. int a = 10/0;
  8. return updateCount;
  9. }

4)在Application类上加@EnableTransactionManagement开启事务支持
@EnableTransactionManagement可选,但是@Service必须添加事务才生效

  1. @SpringBootApplication
  2. @EnableTransactionManagement //SpringBoot开启事务的支持
  3. public class Application {
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class, args);
  6. }
  7. }

5)启动Application,通过浏览器访问进行测试
浏览器
02-SpringBoot进阶 - 图12
IDEA控制台
02-SpringBoot进阶 - 图13
数据库表
02-SpringBoot进阶 - 图14
通过以上结果,说明事务起作用了。
6)注释掉StudentServiceImpl上的@Transactional测试
数据库的数据被更新
02-SpringBoot进阶 - 图15

Spring全家桶注解汇总

40 个 常用的 Sprin链接gBoot 注解,你知道几个?[

](https://mp.weixin.qq.com/s/ijPn2-q7hxFw-G8KYrmghg)
SpringBoot常用注解大全,一目了然!

02-SpringBoot进阶 - 图16

SpringMVC相关注解

参考:https://www.cnblogs.com/cjeandailynotes/p/10469377.html
SpringBoot中的SpringMVC和之前的SpringMVC框架使用是完全一样的,主要有以下注解:
image.png

_@_Controller

SpringMVC的注解,处理http请求,一般放在xxxController.java , 类的上面

_@_RestController

Spring4后新增注解,是@Controller注解功能的增强,是@Controller + @ResponseBody的组合注解;
如果一个Controller类添加了@RestController,那么该Controller类下的所有方法都相当于添加了@ResponseBody注解;
用于返回字符串或json数据。

_@_RequestMapping

RequestMapping是一个用来处理请求地址映射的注解(将请求映射到对应的控制器方法中),可用于类或方法上。
用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
标注在方法上,表示那个具体的方法来接受处理某次请求。
Spring MVC和Spring WebFlux都通过RquestMappingHandlerMapping和RequestMappingHndlerAdapter两个类来提供对@RequestMapping注解的支持。
@RequestMapping注解对请求处理类中的请求处理方法进行标注;@RequestMapping注解拥有以下的六个配置属性:

  • value:映射的请求URL或者其别名
  • method:兼容HTTP请求方法名(默认是get请求)
  • params:根据HTTP参数的存在、缺省或值对请求进行过滤
  • header:根据HTTP Header的存在、缺省或值对请求进行过滤
  • consume:设定在HTTP请求正文中允许使用的媒体类型
  • product:在HTTP响应体中允许使用的媒体类型

提示:在使用@RequestMapping之前,请求处理类还需要使用@Controller或@RestController进行标记

下面是使用@RequestMapping的示例:

  1. @Controller
  2. @RequestMapping(value="/book")
  3. public class BookController {
  4. @RequestMapping(value="/title")
  5. public String getTitle(){
  6. return "title";
  7. }
  8. @RequestMapping(value="/content")
  9. public String getContent(){
  10. return "content";
  11. }
  12. }

由于BookController类加了value=”/book”的@RequestMapping的注解,所以相关路径都要加上”/book”,即请求的url分别为:
(1)http://localhost:8080/book/title
(2)http://localhost:8080/book/content
“@RequestMapping”的value值前后是否有“/”对请求的路径没有影响,即value=”book” 、”/book”、”/book/“其效果是一样的。

SpringMVC提供了常用的请求方法注解,即指定请求方法的_@_RequestMapping

_@_GetMapping RequestMapping和Get请求方法的组合只支持Get请求;Get请求主要用于查询操作。 _@_PostMapping RequestMapping和Post请求方法的组合只支持Post请求;Post请求主要用户新增数据。 _@_PutMapping RequestMapping和Put请求方法的组合只支持Put请求;Put通常用于修改数据。 _@_DeleteMapping RequestMapping 和 Delete请求方法的组合只支持Delete请求;通常用于删除数据。

@RequestMapping()的所有属性:

  1. //这三个参数是一样的,都是匹配路由
  2. String name() default "";
  3. @AliasFor("path")
  4. String[] value() default {};
  5. @AliasFor("value")
  6. String[] path() default {};
  7. //请求方法
  8. RequestMethod[] method() default {};
  9. //@RequestMapping(value = "/hello",method = RequestMethod.POST)
  10. //请求参数
  11. String[] params() default {};
  12. //请求头
  13. String[] headers() default {};
  14. //@RequestMapping(value = "/something", headers = "content-type=text/*")
  15. //will match requests with a Content-Type of "text/html", "text/plain", etc
  16. String[] consumes() default {};
  17. String[] produces() default {};

value:指定请求的实际url
(1)普通的具体值
如 value=”/book”。
(2)含某变量的一类值

  1. @RequestMapping(value="/get/{bookId}")
  2. public String getBookById(@PathVariable String bookId,Model model){
  3. model.addAttribute("bookId", bookId);
  4. return "book";
  5. }

路径中的bookId可以当变量,@PathVariable注解即提取路径中的变量值。
(3)ant风格

  1. //可匹配“/get/id1”或“/get/ida”,但不匹配“/get/id”或“/get/idaa”;
  2. @RequestMapping(value="/get/id?")
  3. //可匹配“/get/idabc”或“/get/id”,但不匹配“/get/idabc/abc”;
  4. @RequestMapping(value="/get/id*")
  5. //可匹配“/get/id/abc”,但不匹配“/get/idabc”;
  6. @RequestMapping(value="/get/id/*")
  7. //可匹配“/get/id/abc/abc/123”或“/get/id/123”,也就是Ant风格和URI模板变量风格可混用。
  8. @RequestMapping(value="/get/id/**/{id}")

(4)含正则表达式的一类值

  1. //:可以匹配“/get/123-1”,但不能匹配“/get/abc-1”,这样可以设计更加严格的规则
  2. @RequestMapping(value="/get/{idPre:\\d+}-{idNum:\\d+}")
  3. //可以通过@PathVariable 注解提取路径中的变量(idPre,idNum)

(5)或关系

  1. //即 /getAll或/fetchAll都会映射到该方法上。
  2. @RequestMapping(value={"/getAll","/fetchAll"} )

method:指定请求的method类型, GET、POST、PUT、DELETE等;
@RequestMapping(value=”/get/{bookid}”,method={RequestMethod.GET,RequestMethod.POST})
params:指定request中必须包含某些参数值时才让该方法处理。

  1. //请求参数包含“action=del”,如:http://localhost:8080/book?action=del
  2. @RequestMapping(params="action=del")
  3. @Controller
  4. @RequestMapping("/owners/{ownerId}")
  5. public class RelativePathUriTemplateController {
  6. //仅处理请求中包含了名为“myParam”,值为“myValue”的请求。
  7. @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
  8. public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  9. // implementation omitted
  10. }
  11. }

headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
@RequestMapping(value=”/header/id”, headers = “Accept=application/json”):表示请求的URL必须为“/header/id 且请求头中必须有“Accept =application/json”参数即可匹配。

  1. @Controller
  2. @RequestMapping("/owners/{ownerId}")
  3. public class RelativePathUriTemplateController {
  4. @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
  5. public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  6. // implementation omitted
  7. }
  8. }

仅处理request的header中包含了指定“Refer”请求头和对应值为“[http://www.ifeng.com/](http://www.ifeng.com/)”的请求。
consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html。

  1. @Controller
  2. @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
  3. public void addPet(@RequestBody Pet pet, Model model) {
  4. // implementation omitted
  5. }

方法仅处理request Content-Type为“application/json”类型的请求。
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。

  1. @Controller
  2. @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
  3. @ResponseBody
  4. public Pet getPet(@PathVariable String petId, Model model) {
  5. // implementation omitted
  6. }

方法仅处理request请求中Accept头中包含了”application/json”的请求,同时暗示了返回的内容类型为application/json;

@RequestParam

@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。
下面是使用该注解的代码示例:

  1. public String requestparam1(@RequestParam String username)

请求中包含username参数(如/requestparam1?username=zhang
@RequestParam有以下三个参数:
value:参数名字,即入参的请求参数名字,如username表示URL请求的参数名,如果方法中参数名和URL请求参数名相同,value可以省略
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将抛出异常;
defaultValue:默认值,表示如果请求中没有同名参数时的默认值,设置该参数时,自动将required设为false。

  1. public String requestparam4(@RequestParam(value="username",required=false) String username)

表示请求中可以没有名字为username的参数,如果没有默认为null,此处需要注意如下几点:
原子类型:必须有值,否则抛出异常,如果允许空值请使用包装类代替。
Boolean包装类型:默认Boolean.FALSE,其他引用类型默认为null。
如果请求中有多个同名的应该如何接收呢?如给用户授权时,可能授予多个权限,首先看下如下代码:

  1. public String requestparam7(@RequestParam(value="role") String roleList)

如果请求参数类似于url?role=admin&rule=user,则实际roleList参数入参的数据为“admin,user”,即多个数据之间使用 “,” 分割;
我们最好使用一个字符串数组或列表来接收多个请求参数:

  1. public String requestparam7(@RequestParam(value="role") String[] roleList)

或者

  1. public String requestparam8(@RequestParam(value="list") List<String> list)

@PathVariable

@PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。
特别地,@PathVariable注解允许我们使用value或name属性来给参数取一个别名。下面是使用此注解的一个示例:

  1. @RequestMapping(value="/users/{userId}/topics/{topicId}")
  2. public String test(
  3. @PathVariable(value="userId") int userId,
  4. @PathVariable(value="topicId") int topicId){
  5. ...
  6. }

比如请求的URL为 “/users/123/topics/456”,则自动将URL中模板变量{userId}和{topicId}绑定到通过@PathVariable注解的同名参数上,
即入参后userId=123、topicId=456。
模板变量名需要使用{ }进行包裹,如果方法的参数名与URI模板变量名一致,则在@PathVariable中就可以省略别名的定义。

提示:如果参数是一个非必须的,可选的项,则可以在@PathVariable中设置require = false

_@_ModelAttribute

ModelAttribute可以应用在方法参数上或方法上,他的作用主要是当注解在方法参数上时会将注解的参数对象添加到Model中;当注解在
请求处理方法Action上时会将该方法变成一个非请求处理的方法,但其它Action被调用时会首先调用该方法。

6.1 @ModelAttribute注释一个方法
被@ModelAttribute注释的方法表示这个方法的目的是增加一个或多个模型(model)属性。这个方法和被@RequestMapping注释的方法
一样也支持@RequestParam参数,但是它不能直接被请求映射。实际上,控制器中的@ModelAttribute方法是在同一控制器中的
@RequestMapping方法被调用之前调用的。
被@ModelAttribute注释的方法用于填充model属性,例如,为下拉菜单填充内容,或检索一个command对象(如,Account),用它
来表示一个HTML表单中的数据。
一个控制器可以有任意数量的@ModelAttribute方法。所有这些方法都在@RequestMapping方法被调用之前调用。
有两种类型的@ModelAttribute方法。一种是:只加入一个属性,用方法的返回类型隐含表示。另一种是:方法接受一个Model类型的参
数,这个model可以加入任意多个model属性。
(1)@ModelAttribute注释void返回值的方法

  1. @Controller
  2. @RequestMapping(value="/test")
  3. public class TestController {
  4. /**
  5. * 1.@ModelAttribute注释void返回值的方法
  6. * @param abc
  7. * @param model
  8. */
  9. @ModelAttribute
  10. public void populateModel(@RequestParam String abc, Model model) {
  11. model.addAttribute("attributeName", abc);
  12. }
  13. @RequestMapping(value = "/helloWorld")
  14. public String helloWorld() {
  15. return "test/helloWorld";
  16. }
  17. }

这个例子,在获得请求/helloWorld 后,populateModel方法在helloWorld方法之前先被调用,它把请求参数(/helloWorld?abc=text)
加入到一个名为attributeName的model属性中,在它执行后helloWorld被调用,返回视图名helloWorld和model已由_@_ModelAttribute
方法生产好了。这个例子中model属性名称和model属性对象由model.addAttribute()实现,不过前提是要在方法中加入一个Model类型
的参数。
(2)@ModelAttribute注释返回具体类的方法

  1. @ModelAttribute
  2. public User getUserInfo(String id){
  3. if(id!=null && !id.equals("")){
  4. return userService.getUserInfo(id);
  5. }
  6. return null;
  7. }

这种情况,model属性的名称没有指定,它由返回类型隐含表示,如这个方法返回User类型,那么这个model属性的名称是user。
这个例子中model属性名称有返回对象类型隐含表示,model属性对象就是方法的返回值。它无须要特定的参数。
(3)@ModelAttribute(value=””)注释返回具体类的方法

  1. @Controller
  2. @RequestMapping(value="/test")
  3. public class TestController {
  4. /**
  5. * 3.@ModelAttribute(value="")注释返回具体类的方法
  6. * @param abc
  7. * @return
  8. */
  9. @ModelAttribute("str")
  10. public String getParam(@RequestParam String param) {
  11. return param;
  12. }
  13. @RequestMapping(value = "/helloWorld")
  14. public String helloWorld() {
  15. return "test/helloWorld";
  16. }
  17. }

这个例子中使用@ModelAttribute注释的value属性,来指定model属性的名称。model属性对象就是方法的返回值。它无须要特定的参数。
完整的代码:

  1. package demo.controller;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.ui.Model;
  5. import org.springframework.web.bind.annotation.ModelAttribute;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestParam;
  8. import demo.model.User;
  9. import demo.service.IUserService;
  10. @Controller
  11. @RequestMapping(value="/test")
  12. public class TestController {
  13. @Autowired
  14. private IUserService userService;
  15. /**
  16. * 1.@ModelAttribute注释void返回值的方法
  17. * @param abc
  18. * @param model
  19. */
  20. @ModelAttribute
  21. public void populateModel(@RequestParam String abc, Model model) {
  22. model.addAttribute("attributeName", abc);
  23. }
  24. /**
  25. * 2.@ModelAttribute注释返回具体类的方法
  26. * @param id
  27. * @return
  28. */
  29. @ModelAttribute
  30. public User getUserInfo(String id){
  31. if(id!=null && !id.equals("")){
  32. return userService.getUserInfo(id);
  33. }
  34. return null;
  35. }
  36. /**
  37. * 3.@ModelAttribute(value="")注释返回具体类的方法
  38. * @param abc
  39. * @return
  40. */
  41. @ModelAttribute("str")
  42. public String getParam(@RequestParam String param) {
  43. return param;
  44. }
  45. @RequestMapping(value = "/helloWorld")
  46. public String helloWorld() {
  47. return "test/helloWorld";
  48. }
  49. }

Jsp前台取值:

  1. <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
  2. <%
  3. String path = request.getContextPath();
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  5. %>
  6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  7. <html>
  8. <head>
  9. <title>helloWorld</title>
  10. </head>
  11. <body>
  12. 1.The attributeValue is: ${attributeName}
  13. <br/><br/>
  14. 2.用户信息:<br/>
  15. 姓名:${user.user_name}<br/>
  16. 年龄:${user.user_age}<br/>
  17. 邮箱:${user.user_email}<br/><br/>
  18. 3.The param is: ${str}
  19. </body>
  20. </html>

页面效果图:
02-SpringBoot进阶 - 图18
URL格式:http://localhost/SSMDemo/test/helloWorld?abc=text&id=1&param=aaa 注:当url或者post中不包含参数abc和参数param时,会报错。
(4)@ModelAttribute和@RequestMapping同时注释一个方法

  1. @Controller
  2. @RequestMapping(value="/test")
  3. public class TestController {
  4. @RequestMapping(value = "/helloWorld")
  5. @ModelAttribute("attributeName")
  6. public String helloWorld() {
  7. return "hi";
  8. }
  9. }

这时这个方法的返回值并不是表示一个视图名称,而是model属性的值,视图名称由RequestToViewNameTranslator根据请求”/helloWorld”转换为helloWorld。Model属性名称由@ModelAttribute(value=””)指定,相当于在request中封装了key=attributeName,value=hi。
Jsp页面:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  2. <html>
  3. <head>
  4. <title>helloWorld</title>
  5. </head>
  6. <body>
  7. The attributeValue is: ${attributeName}
  8. </body>
  9. </html>

4.2 @ModelAttribute注释一个方法的参数
@ModelAttribute注释方法的一个参数表示应从模型model中取得。若在model中未找到,那么这个参数将先被实例化后加入到model中。若在model中找到,则请求参数名称和model属性字段若相匹配就会自动填充。这个机制对于表单提交数据绑定到对象属性上很有效。
当@ModelAttribute注解用于方法参数时,它有了双重功能,即“存/取”。首先,它从模型中取出数据并赋予对应的参数,如果模型中尚不存在,则实例化一个,并存放于模型中;其次,一旦模型中已存在此数据对象,接下来一个很重要的步骤便是将请求参数绑定到此对象上(请求参数名映射对象属性名),这是Spring MVC提供的一个非常便利的机制—数据绑定。

  1. @RequestMapping(value = "/login.htm", method = RequestMethod.GET)
  2. public String doLogin(@ModelAttribute("baseMember") BaseMember member) {
  3. member.setLoginName("loginName");
  4. return "home";
  5. }

上述代码中,如果模型中尚不存在键名为“baseMember”的数据,则首先会调用BaseMember类的默认构造器创建一个对象,如果不存在默认构造器会抛出异常。因此,给实体类提供一个默认构造器是一个好的编程习惯。当请求路径的请求参数或提交的表单与BaseMember的属性名匹配时,将自动将其值绑定到baseMember对象中,非常的便利!这可能是我们使用@ModelAttribute最主要的原因之一。比如:请求路径为http://localhost:8080/spring-web/login.htm?loginName=myLoginName,baseMember对象中的loginName属性的值将被设置为myLoginName。
4.3 @ModelAttribute注解的使用场景
当@ModelAttribute注解用于方法时,与其处于同一个处理类的所有请求方法执行前都会执行一次此方法,这可能并不是我们想要的,因
此,我们使用更多的是将其应用在请求方法的参数上,而它的一部分功能与@RequestParam注解是一致的,只不过@RequestParam用
于绑定单个参数值,而@ModelAttribute注解可以绑定所有名称匹配的,此外它自动将绑定后的数据添加到模型中,无形中也给我们提供
了便利,这也可能是它命名为ModelAttribute的原因。

@SessionAttributes

在默认情况下,ModelMap中的属性作用域是request级别,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session 中,这样 ModelMap 的属性才可以被跨请求访问。
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 _@_SessionAttributes 注解来实现的。

  1. package demo.controller;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.ui.ModelMap;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.SessionAttributes;
  6. import demo.model.User;
  7. @Controller
  8. @RequestMapping(value="/demo1")
  9. //(1)将ModelMap中属性名为currUser的属性放到Session属性列表中,以便这个属性可以跨请求访问
  10. @SessionAttributes("currUser")
  11. public class Demo1Controller {
  12. @RequestMapping(value="/getUser")
  13. public String getUser(ModelMap model){
  14. User user=new User();
  15. user.setUser_name("zhangsan");
  16. user.setUser_age(25);
  17. user.setUser_email("zhangsan@sina.com");
  18. //(2)向ModelMap中添加一个属性
  19. model.addAttribute("currUser",user);
  20. return "/demo/user";
  21. }
  22. @RequestMapping(value="/getUser1")
  23. public String getUser1(ModelMap model){
  24. User user=(User)model.get("currUser");
  25. System.out.println(user.getUser_name());
  26. System.out.println(user.getUser_age());
  27. System.out.println(user.getUser_email());
  28. return "demo/user1";
  29. }
  30. }

我们在(2)处添加了一个 ModelMap 属性,其属性名为 currUser,而(1)处通过 _@_SessionAttributes 注解将 ModelMap 中名为 currUser 的属性放置到 Session 中,所以我们不但可以在 getUser() 请求所对应的 JSP 视图页面中通过 request.getAttribute(“currUser”) 和 session.getAttribute(“currUser”) 获取 user 对象,还可以在下一个请求(getUser1())所对应的 JSP 视图页面中通过 session.getAttribute(“currUser”) 或 session.getAttribute(“currUser”)访问到这个属性。
这里我们仅将一个 ModelMap 的属性放入 Session 中,其实 _@_SessionAttributes 允许指定多个属性。你可以通过字符串数组的方式指定多个属性,如 @SessionAttributes({“attr1”,”attr2”})。此外,_@_SessionAttributes 还可以通过属性类型指定要 session 化的 ModelMap 属性,如 _@_SessionAttributes(types = User.class),当然也可以指定多个类,如 _@_SessionAttributes(types = {User.class,Dept.class}),还可以联合使用属性名和属性类型指定:_@_SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})。
user.jsp页面:

  1. <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
  2. <%@ page import="demo.model.User" %>
  3. <%
  4. String path = request.getContextPath();
  5. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  6. %>
  7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  8. <html>
  9. <head>
  10. <base href="<%=basePath%>">
  11. <title>My JSP 'index.jsp' starting page</title>
  12. </head>
  13. <body><br>
  14. <%User user=(User)session.getAttribute("currUser");%>
  15. 用户名:<%=user.getUser_name() %><br/>
  16. 年龄:<%=user.getUser_age() %><br/>
  17. 邮箱:<%=user.getUser_email() %><br/><br/>
  18. <a href="<%=path %>/demo1/getUser1">跳转</a>
  19. </body>
  20. </html>

通过@ModelAttribute绑定
_@_SessionAttributes 是用来在 controller 内部共享 model 属性的。 我们可以在需要访问 Session 属性的 controller 上加上 @SessionAttributes,然后在 action 需要的 User 参数上加上 @ModelAttribute,并保证两者的属性名称一致。SpringMVC 就会自动将 _@_SessionAttributes 定义的属性注入到 ModelMap 对象,在 setup action 的参数列表时,去 ModelMap 中取到这样的对象,再添加到参数列表。只要我们不去调用 SessionStatus 的 setComplete() 方法,这个对象就会一直保留在 Session 中,从而实现 Session 信息的共享。

  1. @Controller
  2. @SessionAttributes("currentUser")
  3. public class GreetingController{
  4. @RequestMapping
  5. public void hello(@ModelAttribute("currentUser") User user){
  6. //user.sayHello()
  7. }
  8. }

@SessionAttributes清除
@SessionAttributes需要清除时,使用SessionStatus.setComplete();来清除。注意,它只清除@SessionAttributes的session,不会清除HttpSession的数据。故如用户身份验证对象的session一般不用它来实现,还是用session.setAttribute等传统的方式实现。

@Responsebody与_@_RequestBody

@Responsebody表示该方法的返回结果直接写入HTTP response body中。一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@Responsebody后返回结果不会被解析为跳转路径,而是封装成一个JSON对象返回给前端浏览器。

@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。
下面是一个使用@RequestBody的示例:
image.png
下面是一个前后端分离的ajax请求响应过程:

  1. $("#btn2").click(function(){
  2. var url='<%=request.getContextPath()%>/User/addUserInfo';
  3. var data={"user_name":$("#userName").val(),"user_sex":$("#userSex").val(),"user_age":$("#userAge").val(),
  4. "user_email":$("#userEmail").val(),"user_telephone":$("#userTelephone").val(),"user_education":$("#userEducation").val(),
  5. "user_title":$("#userTitle").val()};
  6. $.ajax({
  7. type:'POST',
  8. contentType : 'application/json',
  9. url:url,
  10. dataType:"json",
  11. data:JSON.stringify(data),
  12. async:false,
  13. success:function(data){
  14. alert("新增成功!");
  15. },
  16. error: function(XMLHttpRequest, textStatus, errorThrown){
  17. alert(XMLHttpRequest.status);
  18. alert(XMLHttpRequest.readyState);
  19. alert(textStatus);
  20. }
  21. })
  22. })

java代码

  1. @RequestMapping(value="/addUserInfo",method=RequestMethod.POST)
  2. @ResponseBody
  3. //将请求中的data写入UserModel对象中
  4. public String addUserInfo(@RequestBody UserModel user){
  5. //不会被解析为跳转路径,而是直接写入HTTP response body中
  6. return "{}";
  7. }

比较:_@_RequestBody _@_ResponseBody
_@_RequestBody

作用: i) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上; ii) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。 使用时机: A) GET、POST方式提时, 根据request header Content-Type的值来判断: application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理); multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据); 其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理); B) PUT方式提交时, 根据request header Content-Type的值来判断: application/x-www-form-urlencoded, 必须; multipart/form-data, 不能处理; 其他格式, 必须; 说明:request的body部分的数据编码格式由header部分的Content-Type指定;

_@_ResponseBody 将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。

作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。 使用时机: 返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;

@ControllerAdvice

@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。
@ControllerAdvice需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。
首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。
此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。
下面是一个使用@ControllerAdvice的示例代码:
image.png

@CrossOrigin

@CrossOrigin注解将为请求处理类或请求处理方法提供跨域调用支持。如果我们将此注解标注类,那么类中的所有方法都将获得支持跨域的能力。使用此注解的好处是可以微调跨域行为。使用此注解的示例如下:
image.png

@InitBinder

@InitBinder注解用于标注初始化WebDataBinider的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。
下面是使用此注解的示例:
image.png


Spring Bean 注解

@ComponentScan

@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。此注解的用法如下:

@Component

@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring 被此注解的类 注入到Spring IOC容器中并进行管理。

@Service

@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。下面是使用@Service注解的示例:
image.png

@Repository

@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。此注解的用法如下:
image.png

@DependsOn

@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:
640.jpg

@Bean

@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethod和destroyMethod的用法。示例如下:
image.png

@Scope

@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。
@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。
当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的改类的实例。在定义一个Bea时,可以设置Bean的scope属性为prototype:scope="prototype",也可以使用@Scope注解设置,如下:
@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)

下面将给出两种不同的方式来使用@Scope注解,示例代码如下:
image.png
image.png
当@Scope的作用范围设置成Singleton时,被此注解所标注的类只会被Spring IoC容器初始化一次。在默认情况下,Spring IoC容器所初始化的类实例都为singleton。同样的原理,此情形也有两种配置方式,示例代码如下:
image.png

@Autowired

@Autowired注解用于标记Spring将要解析和注入的依赖项。此注解可以作用在构造函数、字段和setter方法上。

首先要知道另一个东西,default-autowire,它是在xml文件中进行配置的,可以设置为byName、byType、constructor和autodetect;比如byName,不用显式的在bean中写出依赖的对象,它会自动的匹配其它bean中id名与本bean的set**相同的,并自动装载。

@Autowired是用在JavaBean中的注解,通过byType形式,用来给指定的字段或方法注入所需的外部资源。两者的功能是一样的,就是能减少或者消除属性或构造器参数的设置,只是配置地方不一样而已。

默认情况下,@Autowired 注解意味着依赖是必须的,它类似于 @Required 注解,然而,你可以使用 @Autowired 的 (required=false) 选项关闭默认行为。

autowire四种模式的区别:
image.png
@Autowired作用在setter方法上
你可以在 JavaBean中的 setter 方法中使用 @Autowired 注解。当 Spring遇到一个在 setter 方法中使用的 @Autowired 注解,它会在方法中执行 byType 自动装配。
image.png
@Autowired作用于字段上
你可以在属性中使用 @Autowired 注解来除去 setter 方法。当时使用 为自动连接属性传递的时候,Spring 会将这些传递过来的值或者引用自动分配给那些属性。所以利用在属性中 @Autowired 的用法,你的 TextEditor.java 文件将变成如下所示:
image.png
@Autowired作用于构造函数
下面是@Autowired注解标注构造函数的使用示例:
image.png

@Primary

当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。下面将给出一个实例代码来说明这一特性:
111.png
输出结果:
this is send DingDing method message.

@PostConstruct与@PreDestroy

值得注意的是,这两个注解不属于Spring,它们是源于JSR-250中的两个注解,位于common-annotations.jar中。@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法。@PreDestroy注解用于标注Bean被销毁前需要执行的方法。下面是具体的示例代码:
image.png

@Qualifier

当系统中存在同一类型的多个Bean时,@Autowired在进行依赖注入的时候就不知道该选择哪一个实现类进行注入。此时,我们可以使用@Qualifier注解来微调,帮助@Autowired选择正确的依赖项。下面是一个关于此注解的代码示例:
222.png


Spring Boot注解

@SpringBootApplication

@SpringBootApplication注解是一个组合注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。
此注解标注的类就是在Spring Boot应用程序的启动类中,run方法就是springboot程序入口。示例代码如下:
image.png

@EnableAutoConfiguration

@EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。


@ConditionalOnClass与@ConditionalOnMissingClass
这两个注解属于类条件注解,它们根据是否存在某个类作为判断依据来决定是否要执行某些配置。下面是一个简单的示例代码:

  1. @Configuration
  2. @ConditionalOnClass(DataSource.class)
  3. class MySQLAutoConfiguration {
  4. //...
  5. }

@ConditionalOnBean与@ConditionalOnMissingBean
这两个注解属于对象条件注解,根据是否存在某个对象作为依据来决定是否要执行某些配置方法。示例代码如下:

  1. @Bean
  2. @ConditionalOnBean(name="dataSource")
  3. LocalContainerEntityManagerFactoryBean entityManagerFactory(){
  4. //...
  5. }
  6. @Bean
  7. @ConditionalOnMissingBean
  8. public MyBean myBean(){
  9. //...
  10. }

@ConditionalOnProperty

@ConditionalOnProperty注解会根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。示例代码如下:

  1. @Bean
  2. @ConditionalOnProperty(name="alipay",havingValue="on")
  3. Alipay alipay(){
  4. return new Alipay();
  5. }

@ConditionalOnResource

此注解用于检测当某个配置文件存在使,则触发被其标注的方法,下面是使用此注解的代码示例:

  1. @ConditionalOnResource(resources = "classpath:website.properties")
  2. Properties addWebsiteProperties(){
  3. //...
  4. }

@ConditionalOnWebApplication与@ConditionalOnNotWebApplication

这两个注解用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。下面是一个简单的示例:

  1. @ConditionalOnWebApplication
  2. HealthCheckController healthCheckController(){
  3. //...
  4. }

@ConditionalExpression

此注解可以让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。

  1. @Bean
  2. @ConditionalException("${localstore} && ${local == 'true'}")
  3. LocalFileStore store(){
  4. //...
  5. }

@Conditional

@Conditional注解可以控制更为复杂的配置条件。在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。下面是使用该注解的简单示例:

  1. @Conditioanl(CustomConditioanl.class)
  2. CustomProperties addCustomProperties(){
  3. //...
  4. }

重点掌握注解的原理

条件注解@Conditional

条件注解并非一个新事物,这是一个存在于 Spring 中的东西,我们在 Spring 中常用的 profile 实际上就是条件注解的一个特殊化。
条件注解可以说条件注解是整个 Spring Boot 的基石,它存在于源码的方方面面,
想要把 Spring Boot 的原理搞清,条件注解必须要会用。

Spring4 中提供了更加通用的条件注解,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的, 有的小伙伴可能没用过条件注解,但是开发环境、生产环境切换的 Profile 多多少少都有用过吧?实际上这就是条件注解的一个特例。

基本原理

来看一个Demo吧,步骤如下:

  1. //1、定义一个 Food 接口:
  2. public interface Food {
  3. String showName();
  4. }
  5. //2、Food 接口有一个 showName 方法和两个实现类:
  6. public class Rice implements Food {
  7. public String showName() {
  8. return "米饭";
  9. }
  10. }
  11. public class Noodles implements Food {
  12. public String showName() {
  13. return "面条";
  14. }
  15. }
  16. /*3、
  17. 分别是 Rice 和 Noodles 两个类,两个类实现了 showName 方法,然后分别返回不同值。
  18. 接下来再分别创建 Rice 和 Noodles 的条件类,如下:
  19. */
  20. public class NoodlesCondition implements Condition {
  21. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  22. return context.getEnvironment().getProperty("people").equals("北方人");
  23. }
  24. }
  25. public class RiceCondition implements Condition {
  26. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  27. return context.getEnvironment().getProperty("people").equals("南方人");
  28. }
  29. }
  30. /*4、
  31. 在 matches 方法中做条件属性判断,当系统属性中的 people 属性值为 ‘北方人’ 的时候,NoodlesCondition 的条件得到满足,
  32. 当系统中 people 属性值为 ‘南方人’ 的时候,RiceCondition 的条件得到满足,
  33. 换句话说,哪个条件得到满足,一会就会创建哪个 Bean 。接下来我们来配置 Rice 和 Noodles :
  34. */
  35. @Configuration
  36. public class JavaConfig {
  37. @Bean("food")
  38. @Conditional(RiceCondition.class)
  39. Food rice() {
  40. return new Rice();
  41. }
  42. @Bean("food")
  43. @Conditional(NoodlesCondition.class)
  44. Food noodles() {
  45. return new Noodles();
  46. }
  47. }

这个配置类,大家重点注意两个地方:

  • 两个 Bean 的名字都为 food,这不是巧合,而是有意取的。两个 Bean 的返回值都为其父类对Food。
  • 每个 Bean 上都多了 @Conditional 注解,当 @Conditional 注解中配置的条件类的 matches 方法返回值为 true 时,对应的 Bean 就会生效。

配置完成后,我们就可以在 main 方法中进行测试了:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  4. ctx.getEnvironment().getSystemProperties().put("people", "南方人");
  5. ctx.register(JavaConfig.class);
  6. ctx.refresh();
  7. Food food = (Food) ctx.getBean("food");
  8. System.out.println(food.showName());
  9. }
  10. }

首先我们创建一个 AnnotationConfigApplicationContext 实例用来加载 Java 配置类,然后我们添加一个 property 到 environment 中,添加完成后,再去注册我们的配置类,然后刷新容器。容器刷新完成后,我们就可以从容器中去获取 food 的实例了,这个实例会根据 people 属性的不同,而创建出来不同的 Food 实例。
这个就是 Spring 中的条件注解。

进阶-@Profile

条件注解还有一个进化版,那就是 Profile。我们一般利用 Profile 来实现在开发环境和生产环境之间进行快速切换。其实 Profile 就是利用条件注解来实现的。
还是刚才的例子,我们用 Profile 来稍微改造一下:
首先 Food、Rice 以及 Noodles 的定义不用变,条件注解这次我们不需要了,我们直接在 Bean 定义时添加 @Profile 注解,如下:

  1. @Configuration
  2. public class JavaConfig {
  3. @Bean("food")
  4. @Profile("南方人")
  5. Food rice() {
  6. return new Rice();
  7. }
  8. @Bean("food")
  9. @Profile("北方人")
  10. Food noodles() {
  11. return new Noodles();
  12. }
  13. }

这次不需要条件注解了,取而代之的是 @Profile 。然后在 Main 方法中,按照如下方式加载 Bean:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  4. ctx.getEnvironment().setActiveProfiles("南方人");
  5. ctx.register(JavaConfig.class);
  6. ctx.refresh();
  7. Food food = (Food) ctx.getBean("food");
  8. System.out.println(food.showName());
  9. }
  10. }

效果和上面的案例一样。
这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?
我们来看一下 @Profile 的定义:

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Conditional(ProfileCondition.class)
  5. public @interface Profile {
  6. String[] value();
  7. }

可以看到,它也是通过条件注解来实现的。条件类是 ProfileCondition ,我们来看看:

  1. class ProfileCondition implements Condition {
  2. @Override
  3. public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  4. MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
  5. if (attrs != null) {
  6. for (Object value : attrs.get("value")) {
  7. if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
  8. return true;
  9. }
  10. }
  11. return false;
  12. }
  13. return true;
  14. }
  15. }

看到这里就明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。
@Profile 虽然方便,但是不够灵活,因为具体的判断逻辑不是我们自己实现的。而 @Conditional 则比较灵活。

总结:
两个例子向大家展示了条件注解在 Spring 中的使用,它的一个核心思想就是当满足某种条件的时候,某个 Bean 才会生效,而正是这一特性,支撑起了 Spring Boot 的自动化配置。

@Import

SpringBoot集成Redis缓存

http://www.bjpowernode.com/tutorial_springboot/831.html

  1. #配置pom.xml
  2. <dependency>
  3. <groupId>com.alibaba</groupId>
  4. <artifactId>fastjson</artifactId>
  5. <version>1.2.76</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-data-redis</artifactId>
  10. </dependency>
  11. #核心配置文件
  12. #配置redis连接信息(单机模式)
  13. spring.redis.host=localhost
  14. spring.redis.port=6379

service文件

  1. @Autowired
  2. private StudentMapper studentMapper;
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. @Override
  6. public RespBean getAllStudent() {
  7. List<TStudent> studentList = null;
  8. Object arr = redisTemplate.opsForValue().get("studentList");
  9. System.out.println(arr);
  10. if(ObjectUtils.isEmpty(arr) || arr == null || arr == "null"){
  11. System.out.println("===============>查询数据库");
  12. studentList = studentMapper.selectList(null);
  13. redisTemplate.opsForValue().set("studentList", JSON.toJSONString(studentList),15, TimeUnit.SECONDS);
  14. return RespBean.ok("ok",studentList);
  15. }
  16. System.out.println("===============>从redis缓存中取值");
  17. return RespBean.ok("ok",JSON.parse((String)arr));
  18. }

SpringBoot集成Dubbo

http://www.bjpowernode.com/tutorial_springboot/832.html

获取Spring容器并自动执行

1、SpringApplication.run()方法返回的Spring容器对象

02-SpringBoot进阶 - 图38
代码:

  1. @SpringBootApplication
  2. public class NotwebappApplication {
  3. public static void main(String[] args) {
  4. //方式一:SpringApplication.run()方法返回的ConfigurableApplicationContext是Spring容器的实现类
  5. ConfigurableApplicationContext context = SpringApplication.run(NotwebappApplication.class, args);
  6. StudentService studentService = (StudentService) context.getBean("studentServiceImpl");
  7. String ss = studentService.sayHello();
  8. System.out.println(ss);
  9. }
  10. }

ConfigurableApplicationContext.java关系图
02-SpringBoot进阶 - 图39

2、Springboot的入口类实现CommandLineRunner接口

  1. @SpringBootApplication
  2. public class NotwebappApplication implements CommandLineRunner {
  3. //第二步:通过容器获取bean,并注入给userService
  4. @Autowired
  5. public StudentService studentService;
  6. public static void main(String[] args) {
  7. //第一步:SpringBoot的启动程序,会初始化spring容器
  8. SpringApplication.run(NotwebappApplication.class, args);
  9. }
  10. //覆盖接口中的run方法
  11. @Override
  12. public void run(String... args) throws Exception {
  13. //第三步:容器启动后调用run方法,在该方法中调用业务方法
  14. String s = studentService.sayHello();
  15. System.out.println(s);
  16. }
  17. }

SpringBoot使用拦截器

参考:http://www.bjpowernode.com/tutorial_springboot/838.html
1、实现一个拦截器(实现HandlerInterceptor)

  1. public class MyInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. System.out.println("执行MyInterceptor.preHandle==============>");
  5. HashMap<String, String> loginUser = (HashMap<String, String>) request.getSession().getAttribute("login_user");
  6. if(ObjectUtils.isEmpty(loginUser)){
  7. response.sendRedirect(request.getContextPath()+"/springboot/login");
  8. //被拦截
  9. return false;
  10. }else{
  11. //通过
  12. return true;
  13. }
  14. }
  15. @Override
  16. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  17. System.out.println("执行MyInterceptor.postHandle--------------->");
  18. }
  19. @Override
  20. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  21. System.out.println("执行MyInterceptor.afterCompletion++++++++++++++++++++++++>");
  22. }
  23. }

2、通过配置类注册拦截器
在项目中创建一个config包,创建一个配置类InterceptorConfig,并实现WebMvcConfigurer接口, 覆盖接口中的addInterceptors方法,并为该配置类添加@Configuration注解,标注此类为一个配置类,让Spring Boot 扫描到,这里的操作就相当于SpringMVC的注册拦截器 ,@Configuration就相当于一个applicationContext-mvc.xml。

  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. //定义需要拦截的路径
  4. String [] addPathPatterns = {
  5. "/springboot/**"
  6. };
  7. //定义不需要拦截的路径
  8. String [] excludePathPatterns = {
  9. "/test/**",
  10. "/springboot/login",
  11. "/springboot/doLogin",
  12. "/springboot/register",
  13. "/springboot/doRegister",
  14. };
  15. @Override
  16. public void addInterceptors(InterceptorRegistry registry) {
  17. registry.addInterceptor(new MyInterceptor())
  18. .addPathPatterns(addPathPatterns)
  19. .excludePathPatterns(excludePathPatterns);
  20. }
  21. }

3、测试
1)直接输入 http://localhost:8080/springboot/hello 会跳转到登录页面http://localhost:8080/springboot/login
2)访问 http://localhost:8080/springboot/login
输入用户名密码登录
02-SpringBoot进阶 - 图40
3)登录成功后再访问 http://localhost:8080/springboot/hello
控制台打印:

  1. 执行MyInterceptor.preHandle==============>
  2. 执行MyInterceptor.postHandle--------------->
  3. 执行MyInterceptor.afterCompletion++++++++++++++++++++++++>

SpringBoot请求转发和重定向

参考:https://www.icode9.com/content-4-825109.html
1、转发
方式一:使用 “forword” 关键字(不是指java关键字),注意:类的注解不能使用_@_RestController 要用_@_Controller

  1. @RequestMapping(value="/test/test01/{name}" , method = RequestMethod.GET)
  2. public String test(@PathVariable String name) {
  3. return "forword:/ceng/hello.html";
  4. }

方式二:使用servlet 提供的API,注意:类的注解可以使用@RestController,也可以使用_@_Controller

  1. @RequestMapping(value="/test/test01/{name}" , method = RequestMethod.GET)
  2. public void test(@PathVariable String name, HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. request.getRequestDispatcher("/ceng/hello.html").forward(request,response);
  4. }

2、重定向
方式一:使用 “redirect” 关键字(不是指java关键字),注意:类的注解不能使用@RestController,要用_@_Controller

  1. @RequestMapping(value="/test/test01/{name}" , method = RequestMethod.GET)
  2. public String test(@PathVariable String name) {
  3. return "redirect:/ceng/hello.html";
  4. }

方式二:使用servlet 提供的API,注意:类的注解可以使用@RestController,也可以使用_@_Controller

  1. @RequestMapping(value="/test/test01/{name}" , method = RequestMethod.GET)
  2. public void test(@PathVariable String name, HttpServletResponse response) throws IOException {
  3. response.sendRedirect("/ceng/hello.html");
  4. }

使用API进行重定向时,一般会在url之前加上:request.getContextPath()

SpringBoot整合Servlet的两种方式

http://www.bjpowernode.com/tutorial_springboot/839.html

SpringBoot项目打包

jar包和war包的介绍和区别
https://www.jianshu.com/p/3b5c45e8e5bd

打jar包

打war包

1、使用IEDA创建项目

02-SpringBoot进阶 - 图41
将打包方式改为War,会在创建项目时生成ServletInitializer.java
02-SpringBoot进阶 - 图42

2、spring-boot-starter-web去除内嵌的tomcat依赖

pom.xml文件中要去掉spring-boot-starter-web内嵌的tomcat或者将tomcat依赖scope改为provide

  1. <!-- 移除嵌入式tomcat插件 -->
  2. <dependency>
  3.   <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-web</artifactId>
  5.     <exclusions>
  6.       <exclusion>
  7.         <groupId>org.springframework.boot</groupId>
  8.         <artifactId>spring-boot-starter-tomcat</artifactId>
  9.       </exclusion>
  10.     </exclusions>
  11. </dependency>
  12. <!-- 或者-->
  13. <dependency>
  14.   <groupId>org.springframework.boot</groupId>
  15.   <artifactId>spring-boot-starter-tomcat</artifactId>
  16.   <scope>provided</scope>
  17. </dependency>

3、SpringBootServletInitializer

继承org.springframework.boot.web.servlet.support.SpringBootServletInitializer,实现configure方法
为什么继承该类,SpringBootServletInitializer源码注释:

  1. Note that a WebApplicationInitializer is only needed if you are building a war file and
  2. deploying it. If you prefer to run an embedded web server then you won't need this at all.

请注意,WebApplicationInitializer 仅在您构建 war 文件并部署它时才需要。如果您更喜欢运行嵌入式 Web 服务器,那么您根本不需要它

启动类代码:

  1. @SpringBootApplication
  2. public class DemoApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(DemoApplication.class, args);
  5. }
  6. }

方式一、启动类继承SpringBootServletInitializer实现configure:

  1. @SpringBootApplication
  2. public class Application extends SpringBootServletInitializer {
  3. public static void main(String[] args) {
  4. SpringApplication.run(Application.class, args);
  5. }
  6. @Override
  7. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
  8. return builder.sources(Application.class);
  9. }
  10. }

方式二、新增加一个类继承SpringBootServletInitializer实现configure:

  1. public class ServletInitializer extends SpringBootServletInitializer {
  2. @Override
  3. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
  4. //此处的Application.class为带有@SpringBootApplication注解的启动类
  5. return builder.sources(DemoApplication.class);
  6. }
  7. }

4、打包&部署

1、使用IDEA的Maven打包工具
02-SpringBoot进阶 - 图43
注意:

使用外部Tomcat部署访问的时候,application.properties(或者application.yml)中配置的

  1. server.port=8888
  2. server.servlet.context-path=/springboot-jsp

将失效,请使用tomcat的端口,tomcat,webapps下项目名进行访问。 为了防止应用上下文所导致的项目访问资源加载不到的问题,建议pom.xml文件中<build></build>标签下添加<finalName></finalName>标签: 02-SpringBoot进阶 - 图44 使用IDEA的maven打包工具会将项目打包生成为: /tragetspringboot-jsp.war

2、将war包拷贝到tomcat服务器的webapps目录下,运行/bin/startup.bat
02-SpringBoot进阶 - 图45
3、浏览器中输入:
http://localhost:[tomcat端口]/[war包名]/jsp/blog/list
http://localhost:8080/springboot-jsp/jsp/blog/list

SpringBoot集成jsp

maven依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-tomcat</artifactId>
  4. <scope>provided</scope>
  5. </dependency>
  6. <!--让内嵌tomcat具有解析jsp功能(必须)-->
  7. <dependency>
  8. <groupId>org.apache.tomcat.embed</groupId>
  9. <artifactId>tomcat-embed-jasper</artifactId>
  10. </dependency>
  11. <!--jstl标签库-->
  12. <dependency>
  13. <groupId>javax.servlet</groupId>
  14. <artifactId>jstl</artifactId>
  15. </dependency>

2、引⼊jsp编译打包插件

  1. <build>
  2. <finalName>springboot_day1</finalName>
  3. <!--引⼊springboot插件 可以正确打包 显示jsp-->
  4. <plugins>
  5. <plugin>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-maven-plugin</artifactId>
  8. </plugin>
  9. </plugins>
  10. </build>

3、配置视图解析器

  1. #在配置⽂件中引⼊视图解析器
  2. spring:
  3. mvc:
  4. view:
  5. prefix: / # /代表访问项⽬中webapp中⻚⾯
  6. suffix: .jsp

截个图看看
02-SpringBoot进阶 - 图46
4、新建webapp目录
在java resources 同级目录下新建webapp目录,将jsp文件放在该目录下
浏览器上输入url请求:
image.png
以上都配置了,浏览器输入Controller访问路径还是404找不到jsp页面怎么回事呢?

答:是IDEA自己的问题,可以有如下两种解决方式

  1. 使⽤插件启动访问JSP⻚⾯

image.png

  1. 使⽤idea中指定⼯作⽬录启动 访问JSP(推荐)

image.png
测试时发现,每次修改jsp文件都要重新启动springboot项目,非常麻烦,如何跟devtools结合能热启动呢?
image.png
(开启jsp页面开发模式,修改jsp页面无需重启springboot应用)

SpringBoot集成Shiro

SpringBoot+Shiro+jsp项目实战

SpringBoot集成SpringSecurity

SpringSecurity安全框架

SpringBoot数据库连接加密处理

虽然项目的源代码配置是在application.properties文件中,但是,如果使用的是明文账密,还是处于不安全状态,一旦泄露,将会造成不可计量的损失,故而使用一个加密来加密必要的信息,使得数据库更安全。

这里使用 jasypt-spring-boot-starter 来处理加密字符。官网详情点击查看

1、导入依赖

  1. <!-- https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter -->
  2. <dependency>
  3. <groupId>com.github.ulisesbocchio</groupId>
  4. <artifactId>jasypt-spring-boot-starter</artifactId>
  5. <version>2.1.0</version>
  6. </dependency>

2、生产加密数据

  1. import org.jasypt.util.text.BasicTextEncryptor;
  2. public class EncryptionTest{
  3. public static void main(String[] args) {
  4. BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
  5. //自定义加密所需的salt(盐值)
  6. textEncryptor.setPassword("salt123456");
  7. //要加密的数据(数据库的用户名或密码,当然你要是想让URL加密,也是可以的)
  8. String username = textEncryptor.encrypt("root");
  9. String password = textEncryptor.encrypt("123456");
  10. System.out.println("username:" + username);
  11. System.out.println("password:" + password);
  12. }
  13. }

每次启动生成的数据都不一样,这次启动控制台打印:

  1. username:YxYODHKPIbPtwOBMdLbC7w==
  2. password:RV+Z9VVoDTDZsccOpDsrdw==
  3. Process finished with exit code 0

3、修改application.yml配置

  1. # 数据库四大组件
  2. spring:
  3. datasource:
  4. username: ENC(YxYODHKPIbPtwOBMdLbC7w==)
  5. password: ENC(RV+Z9VVoDTDZsccOpDsrdw==)
  6. url: jdbc:mysql://localhost:3306/dbtest?characterEncoding=UTF-8&serverTimezone=UTC
  7. driver-class-name: com.mysql.cj.jdbc.Driver
  8. # 默认加密方式PBEWithMD5AndDES,可以更改为PBEWithMD5AndTripleDES,这里我选择的是默认的
  9. jasypt:
  10. encryptor:
  11. password: salt123456 #你自定义的盐值
  12. # algorithm: PBEWithMD5AndTripleDES

然后直接启动项目就OK了,测试,可以获取到相应的数据。
image.png
注意:如果怕盐值也泄露出去,你可以使用命令启动的时候传入进去盐值。
例如:

  1. java -jar -Djasypt.encryptor.password=盐值 xxx.jar

完美解决。

SpringBoot文件上传

上传到本地

https://github.com/BFD2018/xiong-springboot-demos/tree/master/springboot-filesupload

上传到fastdfs

https://github.com/BFD2018/xiong-springboot-demos/tree/master/springboot-fdfs

上传到aliyun-oss

https://github.com/BFD2018/xiong-springboot-demos/tree/master/springboot-aliyun

SpringBoot发送邮件

源码:https://github.com/BFD2018/xiong-springboot-demos/tree/master/springboot-mail
pom.xml配置

  1. <!--发送邮件-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-mail</artifactId>
  5. <version>2.6.3</version>
  6. </dependency>
  7. <!--swagger2 测试页面模板 访问 http://localhost:8080/doc.html-->
  8. <dependency>
  9. <groupId>com.github.xiaoymin</groupId>
  10. <artifactId>knife4j-spring-boot-starter</artifactId>
  11. <version>3.0.3</version>
  12. </dependency>

swagger2测试模板效果如下:
image.png
knife4j基本使用参考:
https://blog.csdn.net/Octopus21/article/details/106769722/