百日筑基第十一天-看看SpringBoot
创建项目
Spring 官方提供了 Spring Initializr 的方式来创建 Spring Boot 项目。网址如下:
https://start.spring.io/
打开后的界面如下:
可以将 Spring Initializr 看作是 Spring Boot 项目的初始化向导,它可以帮助开发人员在一分钟之内创建一个 Spring Boot 骨架,非常的傻瓜式。
来解释一下 Spring Initializr 初始化界面中的关键选项。
1)Project:项目的构建方式,可以选择 Mavenopen in new window(安装方式可以戳链接) 和 Gradle(构建脚本基于 Groovy 或者 Kotlin 等语言来编写,而不是传统的 XML)。编程喵默认采用的 Maven。
2)Language:项目的开发语言,可以选择 Java、Kotlin(JetBrains开发的可以在 JVM 上运行的编程语言)、Groovy(可以作为 Java 平台的脚本语言来使用)。默认 Java 即可。
3)Spring Boot:项目使用的 Spring Boot 版本。默认版本即可,比较稳定。
4)Project Metada:项目的基础设置,包括包名、打包方式、JDK 版本等。
- Group:项目所属组织的标识符,比如说 top.codingmore;
- Artifact:项目的标识符,比如说 coding-more;
- Name:默认保持和 Artifact 一致即可;
- Description: 项目的描述信息,比如说《编程喵实战项目(Spring Boot+Vue 前后端分离项目)》;
- Package name:项目包名,根据Group和Artifact自动生成即可。
- Packaging: 项目打包方式,可以选择 Jar 和 War(SSM 时代,JavaWeb 项目通常会打成 War 包,放在 Tomcat 下),Spring Boot 时代默认 Jar 包即可,因为 Spring Boot 可以内置 Tomcat、Jetty、Undertow 等服务容器了。
- Java:项目选用的 JDK 版本。
5)Dependencies:项目所需要的依赖和 starter。如果不选择的话,默认只有核心模块 spring-boot-starter 和测试模块 spring-boot-starter-test。
生成zip文件解压:
重要的包
spring-boot-devtools 热部署
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId>
</dependency>
整合Lombok简化代码
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version><scope>provided</scope>
</dependency>
然后就可以这样用了:
@Getter
@Setter
class CmowerLombok {private int age;private String name;private BigDecimal money;
}
其中 scope=provided
,就说明 Lombok
只在编译阶段生效。也就是说,Lombok
会在编译期静悄悄地将带 Lombok
注解的源码文件正确编译为完整的 class
文件。
SpringBoot 2.1.x 版本后不需要再显式地添加 Lombok 依赖了。之后,还需要为 Intellij IDEA 安装 Lombok
插件,否则 Javabean
的 getter / setter
就无法自动编译,也就不能被调用。不过,新版的 Intellij IDEA 也已经内置好了,不需要再安装。
@Getter / @Setter
用起来很灵活,比如说像下面这样:
class CmowerLombok {@Getter @Setter private int age;@Getter private String name;@Setter private BigDecimal money;
}
@ToString
注解可以生成 toString
方法:
@ToString
class CmowerLombok {private int age;private String name;private BigDecimal money;
}
@Data
注解可以生成 getter / setter
、equals
、hashCode
,以及 toString
,是个总和的选项。
@Data
class CmowerLombok {private int age;private String name;private BigDecimal money;
}
@Slf4j
可以用来生成注解对象,你可以根据自己的日志实现方式来选用不同的注解,比如说:@Log
、@Log4j
、@Log4j2
、@Slf4j
等。
@Slf4j
public class Log4jDemo {public static void main(String[] args) {log.info("level:{}","info");log.warn("level:{}","warn");log.error("level:{}", "error");}
}
@Builder
注解可以用来通过建造者模式来创建对象,这样就可以通过链式调用的方式进行对象赋值,非常的方便。
@Builder
@ToString
public class BuilderDemo {private Long id;private String name;private Integer age;public static void main(String[] args) {BuilderDemo demo = BuilderDemo.builder().age(18).name("沉默王二").build();System.out.println(demo);}
}
Lombok 还提供了同步注解 @Synchronized、自动抛出异常注解 @SneakyThrows、不可变对象 @Value、自动生成 hashCode 和 equals 方法的注解 @EqualsAndHashCode等等,
Lombok 用起来虽然爽,但需要团队内部达成一致,就是要用大家都用,否则有些用了有些没用就会乱成一锅粥,很影响代码的整体风格。
整合 MySQL 和 Druid
Mysql
对应pom.xml文件中的代码:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId>
</dependency>
在 application.yml 文件中添加数据库链接驱动信息:
spring:datasource:username: codingmore-mysqlpassword: YyfR4TDxCwrjZ2Fsurl:jdbc: mysql://118.190.99.232:3306/codingmore-mysql?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
@SpringBootTest
@Slf4j
class CodingmoreMysqlApplicationTests {@Resourceprivate JdbcTemplate jdbcTemplate;@Testvoid contextLoads() {String sql ="select * from user";List<User> users = jdbcTemplate.query(sql, new RowMapper<User>() {@Overridepublic User mapRow(ResultSet rs, int rowNum) throws SQLException {User user = new User();user.setId(rs.getInt(1));user.setAge(rs.getInt("age"));user.setName(rs.getString("name"));user.setPassword(rs.getString("password"));return user;}});log.info("查询成功");log.info("用户{}",users);}
}
Spring Boot 的测试类主要放置在 src/test/java
目录下面,项目创建成功后,Spring Boot 会根据项目名称自动为我们生成测试类。
比如说本次项目名为 codingmore-mysql,那么测试类名为 CodingmoreMysqlApplicationTests。
@SpringBootTest
注解能够测试我们的项目主类,该项目为 CodingmoreMysqlApplication。
@Test
注解是 junit 单元测试的注解,表示该方法为测试方法。
JdbcTemplate
一个通过 JDBC 连接数据库的工具类,spring-boot-starter-jdbc 依赖中包含了该类。
@Resource
注解会帮我们在 Spring Boot 启动的时候注入一个 JdbcTemplate 的对象。
jdbcTemplate.query()
方法通过 SQL 语句和匿名内部类参数的形式,执行 SQL 并查询结果集。
RowMapper
就是查询到的每一行数据对象,我们可以通过重写 mapRow 方法将数据结果集封装到 User 对象上。
Druid
Druid 是阿里巴巴开源的一款数据库连接池,结合了C3P0、DBCP 等 DB 池的优点,同时还加入了日志监控。
Druid 包含了三个重要的组成部分:
- DruidDriver,能够提供基于 Filter-Chain 模式的插件体系;
- DruidDataSource,高效可管理的数据库连接池;
- SQLParser,支持所有 JDBC 兼容的数据库,包括 Oracle、MySQL 等。
第一步,在 pom.xml 文件中添加 Druid 的依赖,官方已经提供了 starter,我们直接使用。
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.23</version>
</dependency>
第二步,在 application.yml 文件中添加 Druid 配置。
spring:datasource: type: com.alibaba.druid.pool.DruidDataSourcedruid:#初始化连接池大小initial-size: 5#配置最小连接数min-idle: 5#配置最大连接数max-active: 200#配置连接等待超时时间max-wait: 60000#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒time-between-eviction-runs-millis: 60000#配置一个连接在池中最小生存的时间,单位是毫秒min-evictable-idle-time-millis: 300000#测试连接validation-query: SELECT 1 FROM DUAL#申请连接的时候检测,建议配置为true,不影响性能,并且保证安全test-while-idle: true#获取连接时执行检测,建议关闭,影响性能test-on-borrow: false#归还连接时执行检测,建议关闭,影响性能test-on-return: false#是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭pool-prepared-statements: false#开启poolPreparedStatements后生效max-pool-prepared-statement-per-connection-size: 20#配置扩展插件,常用的插件有=>stat:监控统计 log4j:日志 wall:防御sql注入filters: stat,wall,slf4j#打开mergeSql功能;慢SQL记录connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000#配置DruidStatFilterweb-stat-filter:enabled: trueurl-pattern: "/*"exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"#配置DruidStatViewServletstat-view-servlet:url-pattern: "/druid/*"#登录名login-username: root#登录密码login-password: root
第三步,启动项目。在日志信息里可以看到 Druid 的初始化信息。
第四步,通过 http://localhost:9002/druid/
地址就可以在浏览器访问 Druid 的监控页面了,用户名和密码是我们在配置文件里指定的 root 和 root,登录后是这样的。
Spring 对事务的支持
Spring 支持两种事务方式,分别是编程式事务和声明式事务,后者最常见,通常情况下只需要一个 @Transactional
就搞定了(代码侵入性降到了最低),就像这样:
@Transactional
public void savePosts(PostsParam postsParam) {// 保存文章save(posts);// 处理标签insertOrUpdateTag(postsParam, posts);
}
1)编程式事务
编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。
你比如说,使用 TransactionTemplate 来管理事务:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 业务代码} catch (Exception e){//回滚transactionStatus.setRollbackOnly();}}});
}
再比如说,使用 TransactionManager 来管理事务:
@Autowired
private PlatformTransactionManager transactionManager;public void testTransaction() {TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// .... 业务代码transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);}
}
就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。
2)声明式事务
声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中最关键最核心的技术之一,AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。
Spring Boot 对事务的支持
只需要在业务层添加事务注解(@Transactional
)就可以快速开启事务。
也就是说,我们只需要把焦点放在 @Transactional
注解上就可以了。
@Transactional 的作用范围
- 类上,表明类中所有 public 方法都启用事务
- 方法上,最常用的一种
- 接口上,不推荐使用
@Transactional 的常用配置参数
虽然 @Transactional 注解源码中定义了很多属性,但大多数时候,我都是采用默认配置,当然了,如果需要自定义的话,前面也都说明过了。
@Transactional 的使用注意事项总结
1)要在 public 方法上使用,在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。
2)避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效。
过滤器、拦截器、监听器
- 过滤器(Filter):当有一堆请求,只希望符合预期的请求进来。
- 拦截器(Interceptor):想要干涉预期的请求。
- 监听器(Listener):想要监听这些请求具体做了什么。
过滤器
- 过滤敏感词汇(防止sql注入)
- 设置字符编码
- URL级别的权限访问控制
- 压缩响应信息
添加一个过滤器 MyFilter :
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {long start = System.currentTimeMillis();chain.doFilter(request,response);System.out.println("Execute cost="+(System.currentTimeMillis()-start));}@Overridepublic void destroy() {Filter.super.destroy();}
}
@WebFilter 注解用于将一个类声明为过滤器,urlPatterns 属性用来指定过滤器的 URL 匹配模式,filterName 用来定义过滤器的名字。
MyFilter 过滤器的逻辑非常简单,重写了 Filter 的三个方法,在 doFilter 方法中加入了时间戳的记录。然后我们在项目入口类上加上 @ServletComponentScan 注解,这样过滤器就会自动注册。
拦截器
- 登录验证,判断用户是否登录
- 权限验证,判断用户是否有权限访问资源,如校验token
- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量
- 处理cookie、本地化、国际化、主题等
- 性能监控,监控请求处理时长等
@Slf4j
public class LoggerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("preHandle{}...",request.getRequestURI());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
一个拦截器必须实现 HandlerInterceptor 接口,preHandle 方法是 Controller 方法调用前执行,postHandle 是 Controller 方法正常返回后执行,afterCompletion 方法无论 Controller 方法是否抛异常都会执行。
只有 preHandle 返回 true 的话,其他两个方法才会执行。
如果 preHandle 返回 false 的话,表示不需要调用Controller方法继续处理了,通常在认证或者安全检查失败时直接返回错误响应。
再来一个 InterceptorConfig 对拦截器进行配置:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");}
}
@Configuration 注解用于定义配置类,干掉了以往 Spring 繁琐的 xml 配置文件。
编写一个用于被拦截的控制器 MyInterceptorController:
@RestController
@RequestMapping("/myinterceptor")
public class MyInterceptorController {@RequestMapping("/hello")public String hello() {return "沉默王二是傻X";}
}
@RestController 注解相当于 @Controller + @ResponseBody 注解,@ResponseBody 注解用于将 Controller 方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。