SpringBoot仍然基于Spring框架,所以和SpringMVC是“并列”关系,在Spring的基础上,还有SpringCloud等框架:
但SpringBoot旨在提供一种简洁、快速、“开箱即用”的项目构建方式:
所以SpringBoot一经推出,就大受欢迎。SpringBoot上面也可以构建MVC项目,但接下来我们的课程,以构建前后端分离的Restful(复习)后端为准。
另见:环境搭建
以注册到数据库为例……
首先需要Add Starters(引入依赖组件)。演示:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
spring.datasource.url=jdbc:mysql://localhost:3306/17bang spring.datasource.username=root spring.datasource.password= spring.jpa.hibernate.ddl-auto=update
PS:
@Indexed //便于(使用索引)更快的扫描到
public interface Repository<T, ID> {
演示:其继承结构,找到SimpleJpaRepository,查看其中的实现……注意:entityManager已自动存在于上下文中
为了便于其他所有Repository重用(拿到entityManager对象和少声明Interger参数),定义一个AbstractRepository:
public abstract class AbstractRepository<T extends BaseEntity>
extends SimpleJpaRepository<T, Integer>{
protected EntityManager em;
public AbstractRepository(Class<T> domainClass , EntityManager em ) {
super(domainClass, em);
this.em = em;
再由UserRepository继承自AbstractRepository:
@Repository
public class UserRepository extends AbstractRepository<User> {
public UserRepository(EntityManager em) {
super(User.class, em);
注意:只有在Application所在package中的所有@Repository、@Sevice等才会被SpringBoot默认扫描
演示:在UserController中直接调用entity和repository,在数据库中生成数据……
@Autowired
private UserRepository userRepository;
@PostMapping("/register")
public int Register(User user) {
userRepository.save(user);
return user.getId();
演示:观察save(entity)的源代码,里面调用persist()方法前后都没有transaction,但在方法上方标记了@Transactional……
@Transactional
public <S extends T> S save(S entity) {
于是事务的生成、提交和回滚都由容器/上下文(Spring Data框架)负责(这仍然是属于Spring而不仅仅是SpringBoot的,即:SpringMVC中也可以使用)。
可以注解在方法上,也可以注解在类上。注解在类上,等同于其所有方法被注解了;
注意方法必须是public的,否则只会事务无效,不会有提示。
@Transactional类中的方法还可以再注解不同属性的的@Transactional,演示(常用的模式):
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
再@想一想@:事务何时启动,何时提交/回滚呢?以及事务结束以后,connection又怎么处理呢?……
要解决这些问题:
演示:在application.properties中配置显示SpringBoot和Hibernate的log,
logging.level.root=debug logging.level.org.springframework.web=DEBUG logging.level.org.hibernate=DEBUG logging.file.name=c:\\log\\spring.log声明并调用两个事务方法:
@Transactional
public void A() {
System.out.println("a()....");
}
@Transactional(readOnly = true)
public void B() {
System.out.println("b()....");
}
public boolean hasName(String username) {
userRepository.A();
userRepository.B();
演示说明:
#高阶面试题:为什么同一个对象间的方法调用,@Transactional不起作用?#
对比:
@Transactional
public void A() {
B(); //此时不会生成transaction
}
@Transactional
public void B() {}
@Transactional产生作用依赖于proxy机制,
其他要利用AOP才能实现的注解也是一样的,比如@Cacheable……
生成自定义的interceptor同MVC所示,但如何配置哪些页面需要NeedLogOn呢?
@Configuration //该类为一个配置类,功能等同于一个bean xml配置文件 public class NeedLogOnConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new NeedLogOnInterceptor())
.addPathPatterns("/user/hasName");
WebMvcConfigurationSupport,顾名思义,是一个MVC的配置,可以通过添加@EnableWebMvc引入
@EnableWebMvc public class Application {
这样SpringBoot项目就可以变成一个SpringBoot的MVC项目。
PS:和@ControllerAdvice对应的是@RestControllerAdvice
当广泛的使用Ajax之后,很多前端喜欢直接向后台发送JSON格式的数据。
演示:无法自动绑定……
这时候,如果想要让Spring仍然能够自动Model绑定,需要在model前添加@RequestBody注解:
public int Register(@RequestBody User user即告诉Spring框架:从request的body中取值进行绑定
#理解#:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
然后其他同MVC一样,错误消息存储在BindingResult中
public int Register(@Validated User user, BindingResult error,
HttpServletResponse response ) {
if (error.hasErrors()) { try {
response.sendError(501, error.getAllErrors().toString());
PS:在前后端分离的项目中,验证用户输入并给予友好提示是前端的事情……
在微服务(复习)的开发环境中,我们很容易碰到这种场景:在Java Code中(而不是页面中通过a标签)调用另外一个url资源。
这时候使用Spring内置的RestTemplate就非常方便:
@Autowired //通过依赖注入引入 private RestTemplate template;注意RestTemplate没有默认内置,需要在Application.java中配置:
@Bean //让Spring生成这样一个Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@试一试@:如果没有这个配置……
然后调用其方法,指定要访问的uri,获取结果:
//url必须是绝对路径,协议域名都可以封装 String host = "http://localhost:9099"; String url = host +"/user/hasName/"+ user.getName(); ResponseEntity<Boolean> responseEntity = template.getForEntity(url, Boolean.class);
Boolean duplicated = responseEntity.getBody(); System.out.println(responseEntity.getStatusCode());
因为没有UI层,所以开发调试除了使用postman模拟HTTP请求,还可以引入单元测试启动项目,检查其结果是否符合预期。
PS:MVC也可以单元测试,但用得少,@想一想@:为什么?
SpringBoot默认就是引入了unit test相关依赖的:
<artifactId>spring-boot-starter-test</artifactId>可以有两种单元测试方式:
引入测试类,并为其添加两个注解:
@RunWith(SpringRunner.class) //运行在Spring环境中
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class UserControllerTest {
@SpringBootTest(和@WebMvcTest相对应,且不能混用)中webEnvironment也是必须的,指定生成一个随机端口的tomcat服务器环境,不写的话会使用一个Mock环境,无法自动依赖注入关键的:
@Autowired private TestRestTemplate template;TestRestTemplate和RestTemplate非常类似,但更适宜于测试环境:
新建一个测试方法:
@Test
public void RegisterTest() throws Exception {
注意为@Test选择正确的package:
package org.junit.jupiter.api;
测试的难点是发送请求时正确的设置其内容,即
ResponseEntity<Integer> response = template.postForEntity(host + "/user/register", request, Integer.class);中request的内容,它可以包括:
User fg = new User();
fg.setName("fg");
HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.COOKIE, "amount=6"); HttpEntity<User> request = new HttpEntity<User>(fg, headers);最后Assert:
Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "请求未能成功处理"); Assert.isTrue(response.getBody() > 0, "存入数据后id值不对");不要忘了被测试方法参数中 @CookieValue 的注解
public int Register(@RequestBody User user, @CookieValue Integer amount) {
可用于自动生成后端API的文档,解释:
通过maven引入packages:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>添加一个SwaggerConfig类:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
理论上就可以跑/swagger-ui.html页面了,然鹅并没有……
因为我们之前声明了一个:
public class NeedLogOnConfig extends WebMvcConfigurationSupport {
导入了MVC的默认配置,会对/swagger-ui.html进行dispatch……
演示:(复习:dispatch和resource)
要想让/swagger-ui.html避开这种dispatch(复习),就需要在@Configuration类中通过@Override addResourceHandlers进行配置:
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
调用registry的addXXX()方法:
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
即:对/swagger-ui.html及其要使用的/webjars下请求,不要使用MVC的dispatch机制,而是直接到classpath的/META-INF/resources/……
SpringBoot中的classpath包括main下面的java和resources,以及maven仓库地址
PS:
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
;
}
其中apiInfo()方法返回的就是一个ApiInfo对象:
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("一起帮·源栈欢迎你")
.description("大神小班·拎包入住")
.build();
}
其中就可以设置Swagger页面的title、description等。
Docket可以不止一个:
private ApiInfo apiInfoForArticle() {
return new ApiInfoBuilder()
.title("一起帮·文章模块")
.description("")
.build();
}
@Bean
public Docket createRestApiForArticles() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("文章")
.apiInfo(apiInfoForArticle())
.select()
.apis(RequestHandlerSelectors.basePackage("bangren.controller.Article"))
.build()
;
}
相对于上述全局配置,日常开发中用得更多的是注解在:
@Api(value = "用户模块")
public class UserController {
@ApiOperation(value = "检查用户名是否已使用", consumes = "put", produces = "boolean")
@ApiImplicitParams(
@ApiImplicitParam( //必须置于@ApiImplicitParams之中,不能单独解析
name="username",
required = true,
example = "飞哥")
)
@ApiResponses({
@ApiResponse(code = 200, message = "true:重复;false:不重复")
})
public boolean hasName(@PathVariable String username) {
我们应尽量让swagger自动解析,比如参数的类似啥的,但是这种@CookieValue还无法自动解析:
@ApiImplicitParams({
@ApiImplicitParam(name = "amount", value = "注册失败次数", paramType = "cookie")
})
public int Register( @RequestBody User user, @CookieValue Integer amount) {
另外,user不是基本类型,需要注解在@ApiModel(value = "新注册用户信息")
public class User extends BaseEntity {
每个属性再单独注解
@ApiModelProperty(value = "用户名", required = true) private String name;
更多内容,可查看官方文档
选择一起帮某一功能模块(比如用户/文章/广告……),将其做成一个Restful风格的微服务项目,通过postman和单元测试开发调试……
多快好省!前端后端,线上线下,名师精讲
更多了解 加: