0%

SpringGuides初步学习

前言

这篇博客是学习spring的时候,对于官网中的guides做出的一些自己的理解和拓展。

官方几乎对于所有的方面都做了相关的guides介绍,每一个guides只需要15-30分钟即可。

构建一个RESTful的web服务

先导知识

首先,你需要了解,什么是RESTful,这个概念是Roy Thomas Fielding这位老哥在2000年的时候,在自己的博士毕业论文中提出的概念,有兴趣可以去阅读一下原文

在这个小guide中,将会介绍,如果使用spring来返回一个json数据格式。

具体实现

只需要构造好一个对应的对象(bean),然后创建对应的controller就可以了。

拓展

为什么返回对象能够输出json呢?

问题来了,我们的controller只是返回了一个对象,为什么最后能够以json的形式返回呢?

这是因为在类上声明了@RestController(@ResponseBody),有了这个注解,它会利用jackson自动把对象给解析成json进行返回。

更加具体的,只需要你的pojo有对应的getter方法,那么这些有getter方法的属性,就会自动解析成json返回。

@Component、@Repository、@Service、@Controller的区别

@Component是一个比较通用的说法,即任何被spring管理的类,都可以加上这个注解。剩下的三个则是它的更加具体的表现,显然如果你的类有更加明确的作用,那么标注更加具体能够在语义上更好地表现。

定时任务

这个小guide没有使用web,只是展示了如何使用spring实现一个定时任务,比如每隔多久运行一次,spring也可以轻易实现。

具体实现

其实就是把需要实现定时功能的代码写到一个方法中,然后使用@Scheduled注解即可。

这里注意需要把对应的类加上componet注解加入到spring中,否则是不生效的。

同时还需要在主应用中使用@EnableScheduling来开启定时任务。

拓展

Scheduled这个注解本身如果需要测试的话,需要额外使用awaitility这个工具。

消费RESTful的web服务

前面学习了如何使用spring来构造一个RESTful的web服务,这一部分则是讲述如果收到一个json数据,如何去处理它。

官方为我们提供了一个随机的RESTful的网站,访问即可获取对应的json数据。

对应的json数据看起来长这样:

1
2
3
4
5
6
7
{
type: "success",
value: {
id: 10,
quote: "Really loving Spring Boot, makes stand alone Spring apps easy."
}
}

具体实现

第一步自然而然就是根据上面的json数据来构建一个java的POJO了。

这里有一个新的知识点,就是之前是我们把一个对象给序列化成json,而这里是别人的json数据我们反序列化成对应的对象,要是里面有我们不认识的数据怎么办?这里jackson是提供了一个注解(@JsonIgnoreProperties(ignoreUnknown = true))来忽略的。

当然你也可以每个对象的名字和上面的json的key不对应,只需要@JsonProperty指定就可以了。

然后就是重点:RestTemplate这个类了。这个类以json数据作为输入,然后用第三方库进行处理,并且这个类可以从远端获取对应的json数据串,并且解析成对应的对象。

拓展

如果我的json的key和POJO的属性名不对应,会发生什么情况?那就解析不了,对应的值是NULL。当然是用@JsonProperty指定对应的json的key就可以了。

RestTemplate使用Jackson的解析库来解析。CommandLineRunner会自动在springboot启动的时候进行运行。

@Bean和@Component有什么区别?

这两者区别就大了,而且是完全不能搞混的。

@Component通过使用类路径扫描来进行自动检测和自动配置,让spring管理一个类。

@Bean则是告诉spring管理这个方法返回的对象。

所以这两者都是能够让框架来管理对应的类。

CommandLineRunner有什么作用?

这是一个接口,实现了这个接口的bean可以在容器加载完成之后执行。一般我们可以让一个方法返回一个CommandLineRunner对象,这样就能启动了;或者也可以在主类中声明实现这个接口,并且重写对应的run方法。

使用Gradle来构建java项目

暂时没有这个需求。

使用Maven来构建java项目

第一步,创建文件夹:mkdir -p src/main/java/hello

第二步,写入对应的java类。

第三步,在和src同级的目录下,创建一个pom.xml文件,写入对应的信息。

第四步,开始使用maven进行编译、打包、安装到本地库等一系列操作。

之后又介绍了一下使用JUnit配合maven进行单元测试。

在Spring中使用JDBC访问关系数据(H2数据库)

首先,我们假定数据需要访问的有一个顾客对象,然后顾客有ID、姓和名这三个属性。

Spring为开发者提供了JdbcTemplate这个类来让开发人员focus on业务。

首先第一步,当然是为了对应的实体创建对应的POJO。

第二步,获取JdbcTemplate对应的实例,由于在pom文件中已经有了对应的starter(spring-jdbc),所以springboot会帮我们自动注入,我们只需要autowire即可。

第三步,教程中选用的是H2数据库,这是一个用java写的嵌入式数据库,且只在内存中生效,所以用来作为测试是非常方便的。然后就是使用JdbcTemplate用来插入和查询,并将查询结果封装到对应的POJO中。

拓展

利用jdbcTemplate简单操作数据库。

一些删除表格、创建表格等的DDL语句,可以直接用jdbcTemplate.execute来执行对应的语句。

插入可以用?作为占位符,然后传入一个Object[]作为参数,这个数组中的每一个参数会一一取出然后填入问号里。

在Spring中使用MySQL(hibernate)

在这一部分,使用Spring Data JPA这项技术来访问数据。所以依赖关系如图所示:

image-20210326162608346

具体实现

到对应的数据库中,创建对应的数据库和用户,并且授予对应的权限(非常标准的做法):

1
2
3
create database db_example; -- Creates the new database
create user 'springuser'@'%' identified by '123456'; -- Creates the user
grant all on db_example.* to 'springuser'@'%'; -- Gives all privileges to the new user on the newly created database

之前我们使用H2数据库的时候,我们并没有做任何配置,似乎是直接拿来就用了,这是因为在springboot中的默认配置文件中,默认指定了数据库是H2,这次使用了MySQL,所以需要修改:

1
2
3
4
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/db_example
spring.datasource.username=springuser
spring.datasource.password=123456

这里使用的是hibernate框架,第一个是hibernate的设置。

然后创建对应的实体来对应数据库的记录,这里只需要在类上标注上@Entity,然后再在主键上使用@Id就可以了。

创建一个仓库类,来管理所有的这些增删改查,spring中有一个不错的父类可以继承:CrudRepository,它实现了通过id增删改查,所以就省去了很多代码。

好了,最后就是controller层,就是简单封装一下而已。

这里遇到一个比较严重的问题:required a bean named 'entityManagerFactory' that could not be found.,显然是在spring中没有这个对应的bean,然后我网上搜了不少答案,最后大概率应该是有bad的jar file,然后只能把.m2下面的repository删掉了重新下….

相同的问题发现网友也有不少,参考这个链接

测试的话,可以用postman,也可以用命令行工具curl,利用-d(data)这个选项指定对应的数据。当然也可以查看对应的信息。

拓展

具体使用hibernate的crud的这个repo类的话,可以发现在数据库中它还额外生成了一张表,是下一个值的表。

利用spring来上传文件

这次除了标准的web应用之外,还需要用到thymeleaf这个渲染的第三方库。

然后是上传文件的话,需要用到MultipartConfigElement,但是springboot已经帮我们配置好了。也就是在springboot启动的时候,已经有对应的bean在这个容器中了。

上来首先写一个存储服务的接口,因为我们的存储服务肯定需要一些操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface StorageService {

void init();

void store(MultipartFile file);

Stream<Path> loadAll();

Path load(String filename);

Resource loadAsResource(String filename);

void deleteAll();

}

定义好了对应的接口之后,就需要对应的实现。这里教程里非常奇怪,直接就给出了对应的实现,并不要求你去实现它。应该也是固定的一些套路,看了一眼代码并不算很难。