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和单元测试开发调试……
多快好省!前端后端,线上线下,名师精讲
更多了解 加: