Spring Boot 总结
Spring Boot 优点
快速创建与框架集成 内嵌Servlet容器 starters自动依赖与版本控制 自动配置 运行时应用监控
Spring Boot 自动配置
@SpringBootApplication -> @EnableAutoConfiguration -> @AutoConfigurationPackage -> @Import({Registrar.class}): 扫描启动类所在的包及其子包的注解。 @Import({AutoConfigurationImportSelector.class}): 从类路径下的 META-INF/spring.factories 导入自动配置类 SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); 所有配置信息所在jar包: spring-boot-autoconfigure-2.2.4.RELEASE.jar 自动配置: Spring Boot启动会加载大量的自动配置类(xxxAutoConfiguration)。 给容器中自动配置类添加组件的时候,会从Properties类(xxxProperties)中获取属性。 xxxAutoConfiguration:自动配置类 xxxProperties:封装配置文件中的相关属性 package org.springframework.boot.autoconfigure.thymeleaf; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ThymeleafProperties.class}) @ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class}) @AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class}) public class ThymeleafAutoConfiguration {} package org.springframework.boot.autoconfigure.thymeleaf; @ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; private boolean cache; private Integer templateResolverOrder; private String[] viewNames; private String[] excludedViewNames; private boolean enableSpringElCompiler; private boolean renderHiddenMarkersBeforeCheckboxes; private boolean enabled; private final ThymeleafProperties.Servlet servlet; private final ThymeleafProperties.Reactive reactive; public ThymeleafProperties() { this.encoding = DEFAULT_ENCODING; this.cache = true; this.renderHiddenMarkersBeforeCheckboxes = false; this.enabled = true; this.servlet = new ThymeleafProperties.Servlet(); this.reactive = new ThymeleafProperties.Reactive(); } } @EnableWebMvc:完全接管SpringMvc配置,取消SpringBoot自动配置。
Spring Boot 国际化配置
spring.messages.basename=message.login(message是classpath下的文件夹,login是国际化配置文件名)
Spring Boot 视图解析器配置
WebMvcConfigurerAdapter -> registry.addViewController("/").setViewName("index");
Spring Boot 指定日期格式
spring.mvc.date-format=yyyy-MM-dd HH:mm
Spring Boot 拦截器配置
public class LoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(request.getSession().getAttribute("user") == null){ response.sendRedirect("/admin/user"); return false; } return true; } } SpringBoot配置拦截器路径(SpringBoot已经配置了静态资源映射): public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/user/login"); }
Spring Boot 获取配置文件的值
@Component @PropertySource(value = {"classpath: person.properties"}) //加载指定配置文件 @ConfigurationProperties(prefix = "person") 告诉Spring Boot此类中所有属性和配置文件中的相关配置进行绑定 支持JSR303 @Configuration public class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druid(){ return new DruidDataSource(); } } @Value("{person.name}") 支持SpEL
Spring Boot 配置文件加载位置
-file:./config/ -file:./ -classpath:/config/ -classpath:/
Spring Boot thymeleaf
指定版本: <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version> 只需将HTML页面放在classpath:/templates/,thymeleaf就能自动渲染(默认配置在classpath:/templates/) 导入thymeleaf名称空间:xmlns:th="http://www.thymeleaf.org" 表达式语法: ${}: 底层是ognl表达式 可以调用对象的属性和方法(${person.name},${list.size()}) 使用内置的基本对象(${#locale.country}) 使用内置的工具对象(${#strings.toString(obj)}) *{}: 和${}功能一样 补充属性:配合th:object="${person}"使用(*{name}) #{}:获取国际化内容(#{home.welcome}) @{}:替换url(@{/login(username=${person.name}, password=${person.password})}) ~{}:片段引用表达式 文本操作: +:字符串拼接 | the name is ${person.name} |:字符串替换 默认值:value ?: defaultvalue 标签内表达式: [[${}]] -> th:text [(${})] -> th:utext 抽取片段: th:fragment="header(n)" 插入: 将公共片段整个插入到声明的标签中 -> th:insert="~{fragment/fragment::header(1)}"(相对于templates目录) 替换: 将声明的标签替换为公共片段 -> th:replace="~{fragment/fragment::header}"(相对于templates目录) 包含: 将公共片段的内容包含进声明的标签中 -> th:include="~{fragment/fragment::header}"(相对于templates目录) 也可以用id选择器(id="header"): th:insert="~{fragment/fragment::#header}"
thymeleaf 发送Put请求
配置HiddenHttpMethodFilter:SpringBoot已自动配置好 form表单(method="post")中创建一个input项: <input type="hidden" name="_method" value="put" th:if="${person!=null}"/> @PutMapping("/person")
thymeleaf 发送Delete请求
配置HiddenHttpMethodFilter:SpringBoot已自动配置好 <button calss="deteteBtn" th:attr="{/person/}+${person.id}">删除</button> <form id="deleteForm" method="post"> <input type="hidden" name="_method" value="delete"/> </form> <script> $(".deteteBtn").click(function(){ $("#deleteForm").attr("action",$(this).attr("del_url")).submit(); }) </script> @DeleteMapping("/person/{id}")
Spring Boot 定制错误页面
页面能获取的信息: timestamp status error exception message errors(JSR303检验错误) 1. 有模板引擎:在templates文件夹下的error文件夹下创建error.html 2. 没有模板引擎:静态资源文件夹下的error文件夹下创建error.html 定制错误的Json数据: @ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(NotFoundException.class) public String handleException(Exception e, HttpServletRequest request){ Map<String,Object> map = new HashMap<>(); request.setAttribute("javax.servlet.error.status_code",500); map.put("code","500"); map.put("message",e.getMessage()); return "forward:/error"; } } /error请求会被BasicErrorController处理 //给容器中加入我们自己定义的ErrorAttributes @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace); map.put("name","Sara"); return map; } }
Spring Boot 定制和修改Servlet容器相关配置
修改配置文件中以server开头的相关配置 server.port=8081 server.context‐path=/crud
Spring Boot Mybatis使用
@Mapper public interface PersonMapper { @Select("select * from person where id=#{id}") public Person getById(Integer id); @Delete("delete from person where id=#{id}") public int deleteById(Integer id); @Options(useGeneratedKeys = true, keyProperty = "id") //自动生成并封装主键 @Insert("insert into person(name) values(#{name})") public int insert(Person person); @Update("update person set name=#{name} where id=#{id}") public int update(Person person); } 使用MapperScan批量扫描所有的Mapper接口: @MapperScan(value = "com.mapper") @SpringBootApplication 使用mapper配置文件: mybatis: config‐location: classpath:mybatis/mybatis‐config.xml #指定全局配置文件的位置 mapper‐locations: classpath:mybatis/mapper/*.xml #指定sql映射文件的位置*/
Spring Boot 缓存
CacheManager: 缓存管理器,管理各种缓存(Cache)组件。 Cache: 缓存接口,定义缓存操作。 实现有:RedisCache、EhCacheCache、ConcurrentMapCache(默认缓存实现,数据保存在ConcurrentMap<Object, Object>中)等。 @EnableCaching: 开启基于注解的缓存。 @CacheConfig(value = "person"): 注解在类上,用于缓存公共配置。 @Cacheable(value = {"缓存名:必须配置"}, key = "可以不配置"): 有数据时方法不会被调用。主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。 @CachePut(value = {"缓存名:必须配置"}, key = "可以不配置"): 保证方法总会被调用,并且缓存结果。主要针对缓存更新。 @CacheEvict(value = {"缓存名:必须配置"}, key = "可以不配置", beforeInvocation = true): 清空缓存。 keyGenerator: 缓存数据时key生成策略。 serialize: 缓存数据时value序列化策略。 整合Redis(对象必须序列化): 引入redis的starter后,容器中保存的是RedisCacheManager,默认CacheManager未注入。 RedisCacheManager会创建RedisCache作为缓存组件。
Spring Boot 任务
异步任务: 启动类上注解:@EnableAsync 方法是上注解:@Async @Configuration @EnableAsync public class ThreadPoolTaskConfig { /** * 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务, * 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; * 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 */ /** 核心线程数(默认线程数) */ private static final int corePoolSize = 20; /** 最大线程数 */ private static final int maxPoolSize = 100; /** 允许线程空闲时间(单位:默认为秒) */ private static final int keepAliveTime = 10; /** 缓冲队列大小 */ private static final int queueCapacity = 200; /** 线程池名前缀 */ private static final String threadNamePrefix = "Async-Service-"; @Bean("taskExecutor") //bean的名称,默认为首字母小写的方法名 public ThreadPoolTaskExecutor taskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveTime); executor.setThreadNamePrefix(threadNamePrefix); //线程池对拒绝任务的处理策略 //CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 初始化 executor.initialize(); return executor; } } @Async("taskExecutor") 定时任务: 启动类上注解:@EnableScheduling 方法是上注解:@Scheduled(cron = "0 * * * * MON-SAT") //周一到周五每分钟0秒启动一次 邮件任务: spring.mail.password=授权码 spring.main.host=smtp.qq.com spring.mail.properties.mail.smtp.ssl.enable=true 使用JavaMailSenderImpl发送邮件
Spring Boot Security
编写配置类: @EnableWebSecurity 继承WebSecurityConfigurerAdapter 登陆/注销: HttpSecurity配置登陆、注销功能 Thymeleaf提供的SpringSecurity标签支持: 需要引入thymeleaf-extras-springsecurity4 sec:authentication="name"获得当前用户的用户名 sec:authorize="hasRole(‘ADMIN‘)"当前用户必须拥有ADMIN权限时才会显示标签内容 remember me: 表单添加remember-me的checkbox 配置启用remember-me功能 CSRF(Cross-site request forgery)跨站请求伪造: HttpSecurity启用csrf功能,会为表单添加_csrf的值,提交携带来预防CSRF
Spring Boot JWT(单点登录SSO)
cookie:由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该cookie发送给服务器。 session:服务器使用session把用户的信息临时保存在了服务器上,用户离开网站后session会被销毁。 token应该在HTTP的头部发送从而保证了Http请求无状态。 通过设置服务器属性Access-Control-Allow-Origin:*,让服务器能接受到来自所有域的请求。 实现思路: 用户登录校验,校验成功后就返回Token给客户端 客户端收到数据后保存在客户端 客户端每次访问API是携带Token到服务器端 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码 JWT请求流程 用户使用账号发出post请求 服务器使用私钥创建一个jwt 服务器返回这个jwt给浏览器 浏览器将该jwt串在请求头中像服务器发送请求 服务器验证该jwt 返回响应的资源给浏览器 JWT包含了三部分: Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型) Payload 负载(类似于飞机上承载的物品) Signature 签名/签证 Header JWT的头部承载两部分信息:token类型和采用的加密算法。 { "alg": "HS256", "typ": "JWT" } Payload 载荷就是存放有效信息的地方。 有效信息包含三个部分 标准中注册的声明 公共的声明 私有的声明 标准中注册的声明 (建议但不强制使用) : iss: jwt签发者 sub: 面向的用户(jwt所面向的用户) aud: 接收jwt的一方 exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间) nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 公共的声明: 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。 私有的声明: 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。 Signature jwt的第三部分是一个签证信息 这个部分需要base64加密后的header和base64加密后的payload使用‘.‘连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。 密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。 Spring Boot 和 JWT 的集成 依赖 <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> 需要自定义两个注解 需要登录才能进行操作(UserLoginToken) @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; } 用户Bean @Data public class User { String Id; String username; String password; } 需要写token的生成方法 public String getToken(User user) { String token=""; token= JWT.create().withAudience(user.getId()) .sign(Algorithm.HMAC256(user.getPassword())); return token; } 拦截器: public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired UserService userService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { //如果不是映射到方法直接通过 if(!(object instanceof HandlerMethod)){ return true; } HandlerMethod handlerMethod = (HandlerMethod)object; Method method = handlerMethod.getMethod(); //检查有没有需要用户权限的注解 if (method.isAnnotationPresent(UserLoginToken.class)) { UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class); if (userLoginToken.required()) { // 执行认证 String token = httpServletRequest.getHeader("token"); if (token == null) { throw new RuntimeException("无token,请重新登录"); } // 获取 token 中的 user id String userId; try { userId = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new RuntimeException("Token验证失败!"); } User user = userService.findUserById(userId); if (user == null) { throw new RuntimeException("用户不存在,请重新登录"); } // 验证 token JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { throw new RuntimeException("用户密码错误!"); } } } return true; } } @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthenticationInterceptor()) .addPathPatterns("/**"); //拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 } } Controller @PostMapping("/login") public Result login(@RequestBody User user) { //处理验证,发送Token到前端 ... } @UserLoginToken @GetMapping("/getMessage") public Result getMessage() { ... }
SimpleDateFormat(线程不安全)
单线程:private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 多线程: private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static Date parse(String dateStr) throws ParseException { return threadLocal.get().parse(dateStr); } public static String format(Date date) { return threadLocal.get().format(date); } private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime date = LocalDateTime.now() public static String formatDate(LocalDateTime date) { return formatter.format(date); } public static LocalDateTime parseDate(String date) { return LocalDateTime.parse(date, formatter); }
优化 Spring Boot
server: tomcat: min-spare-threads: 20 max-threads: 100 connection-timeout: 5000 java -Xms512m -Xmx768m -jar springboot-1.0.jar
Redis + Token机制实现接口幂等性校验
幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次 为需要保证幂等性的每一次请求创建一个唯一标识token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token: 1. 如果存在,正常处理业务逻辑,并从redis中删除此token,那么,如果是重复请求,由于token已被删除,则不能通过校验,返回请勿重复操作提示 2. 如果不存在,说明参数不合法或者是重复请求,返回提示即可 自定义注解@ApiIdempotent /** * 在需要保证 接口幂等性 的Controller的方法上使用此注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiIdempotent {} /** * 接口幂等性拦截器 */ public class ApiIdempotentInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class); if (methodAnnotation != null) { //幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示 check(request); } return true; } private void check(HttpServletRequest request) { tokenService.checkToken(request); } } @Service public class TokenServiceImpl implements TokenService { private static final String TOKEN_NAME = "token"; @Autowired private RdeisTemplate rdeisTemplate; @Override public Result createToken() { ...创造Token并放入Redis... return new Result(...); } @Override public void checkToken(HttpServletRequest request) { String token = request.getHeader(TOKEN_NAME); if (StringUtils.isBlank(token)) {//header中不存在token token = request.getParameter(TOKEN_NAME); if (StringUtils.isBlank(token)) {//parameter中也不存在token throw new Exception(...); } } if (...判断Redis中是否有Token...) { throw new Exception(...); } ...删除Token并验证是否删除成功(防止多线程问题)... } } /** 需要先获取Token */ @RestController public class TestController { @ApiIdempotent @PostMapping("/") public Result testIdempotence() { return new Result(); } }
相关推荐
与卿画眉共浮生 2020-11-13
hellowordmonkey 2020-11-02
丽丽 2020-10-30
feinifi 2020-10-14
yangjinpingc 2020-10-09
RickyIT 2020-09-27
lisongchuang 2020-09-27
meleto 2020-08-17
幸运小侯子 2020-08-14
csuzxm000 2020-08-02
咻pur慢 2020-08-02
haidaoxianzi 2020-07-16
Sweetdream 2020-06-28
neweastsun 2020-06-25
86427019 2020-06-25
mendeliangyang 2020-06-25
YangHuiLiang 2020-06-24
smalllove 2020-11-03