SpringBoot
这篇文章,介绍了通过SpringBoot开发的具体流程,和SpringBoot中一些常见的知识点
1、第一个HelloWorld程序
SpringBoot项目创建
Spring官网创建
https://start.spring.io/
不推荐
IDEA的Spring Initializr
File->New->Project->Spring Initializr
中途可以选择需要的组件
SpringBoot目录介绍
初始化后项目的目录
一般情况下项目的目录结构
com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- controller
| +- CustomerController.java
|
+- config
| +- swagerConfig.java
|
Application.java
是项目的启动类- domain目录主要用于实体(Entity)与数据访问层(Repository)
- service 层主要是业务类代码
- controller 负责页面访问控制
- config 目录主要放一些配置类
新建一个Controller
新建controller包,并创建一个HelloController
增加@RestController
注解
- 这个
@RestController
注解,就是@Controller
+@ResponseBody
// @Controller
// @ResponseBody
// 使用@RestController注解,可以替代COntroller 和ResponseBody注解
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello springboot";
}
}
修改配置文件application.propertise
,将端口改为2333
server.port=2333
运行测试
小结
-
了解了SpringBoot项目的创建
-
了解了注解
@RestController
,既可以让类被Spring扫描到,又可以让返回请求不跳转到页面,而是在浏览器上显示返回内容
2、RestFul风格
RESTful Web 服务与传统的 MVC 开发一个关键区别是返回给客户端的内容的创建方式:传统的 MVC 模式开发会直接返回给客户端一个视图,但是 RESTful Web 服务一般会将返回的数据以 JSON 的形式返回,这也就是现在所推崇的前后端分离开发。
程序实例
book实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
private int id;
private String name;
}
BookController:
@RestController
@RequestMapping("/RestFul")
public class BookController {
private List<Book> books = new ArrayList<Book>();
/**
*
* @param book 前端传入的是json类型数据
* 而使用RequestBody,就能将Json类型数据反序列化为合适的Java对象
* @return
*/
@PostMapping("/add")
public ResponseEntity<List<Book>> addBook(@RequestBody Book book) {
books.add(book);
return ResponseEntity.ok(books);
}
@DeleteMapping("/del/{id}")
public ResponseEntity deleteBookById(@PathVariable("id") int id) {
books.remove(0);
return ResponseEntity.ok(books);
}
@GetMapping("/get")
public ResponseEntity getBookByName(@RequestParam("name")String name) {
List<Book> list = books.stream()
.filter(book -> book.getName().equals(name))
.collect(Collectors.toList());
return ResponseEntity.ok(list);
}
//上面的也可以改成RestFul形式
@GetMapping("/get/{name}")
public ResponseEntity getBookByName(@PathVariable("name")String name) {
List<Book> list = books.stream()
.filter(book -> book.getName().equals(name))
.collect(Collectors.toList());
return ResponseEntity.ok(list);
}
@GetMapping("/list")
public ResponseEntity getBookList() {
return ResponseEntity.ok(books);
}
}
@RestController
**将返回的对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中。**绝大部分情况下都是直接以 JSON 形式返回给客户端,很少的情况下才会以 XML 形式返回。转换成 XML 形式还需要额为的工作,上面代码中演示的直接就是将对象数据直接以 JSON 形式写入 HTTP 响应(Response)中。关于@Controller
和@RestController
的对比,我会在下一篇文章中单独介绍到(@Controller
+@ResponseBody
=@RestController
)。@RequestMapping
:上面的示例中没有指定 GET 与 PUT、POST 等,因为**@RequestMapping
默认映射所有HTTP Action**,你可以使用@RequestMapping(method=ActionType)
来缩小这个映射。@PostMapping
实际上就等价于@RequestMapping(method = RequestMethod.POST)
,同样的@DeleteMapping
,@GetMapping
也都一样,常用的 HTTP Action 都有一个这种形式的注解所对应。@PathVariable
:取url地址中的参数。@RequestParam
url的查询参数值。@RequestBody
:可以将 *HttpRequest* body 中的 JSON 类型数据反序列化为合适的 Java 类型。
ResponseEntity
: 表示整个HTTP Response:状态码,标头和正文内容。我们可以使用它来自定义HTTP Response 的内容(作用)。
测试:
使用postman进行测试:
小结
学习了:
@GetMapping,@DeleteMapping,@PostMapping
等注解
@PathVariable
用来获取URL中的参数地址
@RequestParam
url查询中的参数(但这种方式,URL的请求就不是restful风格的了)
@RequestBody
可以将 *HttpRequest* body 中的 JSON 类型数据反序列化为合适的 Java 类型
ResponseEntity
: 表示整个HTTP Response:状态码,标头和正文内容。我们可以使用它来自定义HTTP Response 的内容。
3、SpringBoot读取配置文件
使用@Value
my-phone: 18120194475
@Value("my-phone")
private String phoneNum;
这种方式,Spring不推荐
Spring建议使用一下方式
使用@ConfigurationProperties
配置文件:
server:
port: 8081
my-phone: 18120194475
library:
name: FARO_Z图书馆
books:
- id: 1
name: 悲惨世界
- id: 2
name: 合金弹头
编写一个实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix ="library")
@Component
@PropertySource("application.yml")
public class Library {
private String name;
private List<Book> books;
}
@Component
表示将该类识别为一个Bean
@ConfigurationProperties
用于绑定属性,其中prefix表示所绑定的属性的前缀
@PropertySource(value = "application.yml")
表示配置文件路径。
编写一个controller进行测试:
@RestController
public class LibraryController {
@Autowired
private Library lib;
@GetMapping("/lib")
public String getLib() {
return lib.toString();
}
}
@Autowired
如果要用配置好的实体类的话,一定要写,这样才可以绑定
测试结果:
小结
@ConfigurationProperties(prefix="")
:用于绑定属性,其中prefix表示所绑定的属性的前缀
@PropertySource(value"")
:表示噢诶之文件的路径
@Autowired
:对于想要使用配置的属性,一定要使用@Autowired绑定
再给出配置文件的优先级:
4、异常处理
4.1 、SpringBoot异常处理几种方式
https://snailclimb.gitee.io/springboot-guide/#/./docs/advanced/springboot-handle-exception
4.2、SpringBoot异常处理在实际项目中的应用
https://snailclimb.gitee.io/springboot-guide/#/./docs/advanced/springboot-handle-exception-plus
5、JPA
这里我先暂时跳过,先以只用Mybatis为主
6、过滤器和拦截器
6.1、实现过滤器
在servlet中,已经对过滤器有了了解
Filter 过滤器主要是用来过滤用户请求的,它允许我们对用户请求进行前置处理和后置处理,比如实现 URL 级别的权限控制、过滤非法请求等等。
Filter 过滤器是面向切面编程——AOP 的具体实现
自定义filter的话,只要实现javax.Servlet.Filter
接口,然后重写里面的三个方法即可:
public interface Filter {
//初始化过滤器后执行的操作
default void init(FilterConfig filterConfig) throws ServletException {
}
// 对请求进行过滤
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
// 销毁过滤器后执行的操作,主要用户对某些资源的回收
default void destroy() {
}
}
过滤器是如何实现拦截的
Filter
接口中有一个叫做 doFilter
的方法,这个方法实现了对用户请求的过滤。具体流程大体是这样的:
- 用户发送请求到 web 服务器,请求会先到过滤器;
- 过滤器会对请求进行一些处理比如过滤请求的参数、修改返回给客户端的 response 的内容、判断是否让用户访问该接口等等。
- 用户请求响应完毕。
- 进行一些自己想要的其他操作。
编写过滤器
这里我们自定义一个过滤器:
@Component
public class MyFilter implements Filter {
//这个是SLF4J 是类似log4j的日志实现
private final Logger logger = LoggerFactory.getLogger(MyFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("初始化过滤器:"+filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("过滤器开始对请求进行处理");
HttpServletRequest req = (HttpServletRequest) servletRequest;
System.out.println("请求接口为:"+req.getRequestURI());
long start = System.currentTimeMillis();
//放行
filterChain.doFilter(servletRequest,servletResponse);
long end = System.currentTimeMillis();
System.out.println("用户请求结束,请求处理耗时:"+Long.valueOf((end-start))+"ms");
}
@Override
public void destroy() {
logger.info("销毁过滤器");
}
}
1、手动注册配置实现
配置文件中注册自定义的过滤器:
@Configuration
public class MyFilterConfig {
@Autowired
MyFilter myFilter;
public FilterRegistrationBean<MyFilter> thirdFilter() {
FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(myFilter);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
return filterRegistrationBean;
}
}
2、注解实现
在自定义的Filter上,加上@WebFilter
注解
别忘记加上@Component
注解,不然Spring无法扫描到,就无法实例化,就没有用:
@WebFilter(filterName = "MyFilter",urlPatterns = "/*")
@Component
public class MyFilter implements Filter {
...
}
其实,也可以不添加@Component
注解,而是在启动类上,添加@ServletComponentScan
注解,这样,Spring就会自动去找和Servlet
相关的类,然后对它们进行实例化
不添加@Component注解:
@WebFilter(filterName = "MyFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
//不添加@Component注解
...
}
而是为启动类添加@ServletComponentScan
注解:
@SpringBootApplication
@ServletComponentScan
public class StudyApplication {
public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
}
}
一般,还是建议加@Component
定义多个拦截器,决定执行顺序
1、配置类方式
在配置文件中调用setOrder
,来设置启动优先级
2、注解方式
只要在过滤器上加上一个@Order(优先级)
注解,就可以决定filter启用顺序
Filter1启动优先级设置为1:
@WebFilter(filterName = "MyFilter2",urlPatterns = "/*")
@Order(1)
@Component
public class MyFilter2 implements Filter {
}
FIlter2启动优先级设置为2:
@WebFilter(filterName = "MyFilter",urlPatterns = "/*")
@Order(2)
@Component
public class MyFilter implements Filter {
}
测试:
6.2、实现拦截器
拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。
过滤器和拦截器的区别
- 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(筛子)
- 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(关卡)
自定义拦截器
要自定义Interceptor的话,必须:
实现org.springframework.web.servlet.HandlerInterceptor接口
或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,
且要重写下面三个方法:
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView)
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex)
注意: ***preHandle***方法返回 true或 false。如果返回 true,则意味着请求将继续到达 Controller 被处理。
LoginInterceptor
用于过滤所有请求:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("\n-------- LogInterception.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Start Time: " + System.currentTimeMillis());
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- LogInterception.postHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
// You can add attributes in the modelAndView
// and use that in the view page
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("\n-------- LogInterception.afterCompletion --- ");
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("End Time: " + endTime);
System.out.println("Time Taken: " + (endTime - startTime));
}
}
OldLoginInterceptor
,旧连接拦截器,一旦访问这个旧的连接,就会转发到 /admin/login:
public class OldLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");
//getContextPath()获取的是 http://localHost。。。 即uri前面的东西
//这里,当访问旧的废弃链接的时候,直接跳转到 /admin/login 的位置上去
response.sendRedirect(request.getContextPath()+"/admin/login");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
拦截后,应该是下面这个过程:
AdminInterceptor
:
public class AdminInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("\n-------- AdminInterceptor.preHandle --- ");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置类配置拦截器:
拦截器的配置类需要实现WebMvcConfigurer
接口
然后,重写里面的addInterceptors
方法,来配置拦截器
配置类里,可以配置每个拦截器需要拦截的路径和忽略的路径:
注意:配置类别忘记加@Configuration
注解
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//这里不添加 addPathPatterns(),就是所有路径都进行过滤
registry.addInterceptor(new LoginInterceptor());
registry.addInterceptor(new OldLoginInterceptor())
.addPathPatterns("/admin/oldLogin");
//excludePathPatterns是设置的忽略的路径
registry.addInterceptor(new AdminInterceptor())
.addPathPatterns("/admin/*")
.excludePathPatterns("/admin/oldLogin");
}
}
定义Controller:
@Controller
public class LoginController {
@RequestMapping(value = {
"/","/test"})
public String test(Model model) {
System.out.println("\n-------- MainController.test --- ");
System.out.println(" ** You are in Controller ** ");
return "test";
}
@Deprecated
@RequestMapping("/admin/oldLogin")
public String oldLogin(Model model) {
//下面永远不会运行到
//因为早就被拦截器拦截了
return "oldLogin";
}
@RequestMapping("/admin/login")
public String login(Model model) {
System.out.println("\n-------- MainController.login --- ");
System.out.println(" ** You are in Controller ** ");
return "login";
}
}
thymeleaf模板引擎:
如果之间创建项目的时候,没有添加thymeleaf依赖
那一定要进行下面的步骤:
- 导入thymeleaf依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 开发环境,关闭thymeleaf缓存
#开发环境设置thymeleaf缓存不可用,加快响应速度
spring:
thymeleaf:
cache: false
test.html:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor example</title>
</head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Home</a>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>
<h3>Spring Boot Mvc Interceptor</h3>
<span style="color:blue;">Testing LogInterceptor</span>
<br/><br/>
See Log in Console..
</body>
</html>
login.html:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor example</title>
</head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Home</a>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>
<h3>This is Login Page</h3>
<span style="color:blue">Testing OldLoginInterceptor & AdminInterceptor</span>
<br/><br/>
See more info in the Console.
</body>
</html>
测试:
输入oldLogin:
回车,就会跳转到login,说明oldLogin被拦截了:
小结
- 了解了过滤器,拦截器的区别:
一个是筛子,一个是关卡
- 学习了SpringBoot中过滤器的配置:
- 创建自定义过滤器类,实现Filter接口,复习而其中三个方法
- 使用注解或者手动配置配置文件
- 注解:@WebFilter(filterName = “MyFilterWithAnnotation”, urlPatterns = “/api/*”)
- 配置文件的方式,翻阅上面的文档
- 学习了SpringBoot中拦截器的配置:
- 创建自定义拦截器,并实现
HanderInterceptor
接口- 创建拦截器配置类,实现WebMvcConfigurer接口,为每个拦截器进行配置(配置拦截路径或者忽略路径)
7、整合Mybatis
这里先给出SpringBoot项目的完整项目结构:
下面是整合步骤:
- 新建数据库表
person
:
DROP TABLE IF EXISTS `user`;
CREATE TABLE user (
`id` int(15) AUTO_INCREMENT,
`name` VARCHAR(255) DEFAULT NULL,
`pwd` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
)
- 导入Mybatis和mysql的依赖:
也可以在新建项目的时候,导入相关的模块
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 注意Maven静态资源到处问题
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<!--此处的配置是识别到mapper.xml文件,也可以在application.properties中配置-->
<!--即静态资源导出问题-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
- 修改配置文件:
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot-mybatis?useUnicode=true&characterEncoding=utf-8
username: admin
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
- 先测试一下,看看有没有配置成功:
@SpringBootTest
class SpringbootStudy3ApplicationTests {
@Autowired
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
System.out.println(dataSource.getConnection());
}
}
- 编写Mapper接口:
注意:根据官方文档,mapper接口,一定要加上@Mapper
注解和@Repository
注解
@Repository
注解的功能和@Component
一样,只是@Repository
是用在Dao层的
@Mapper
@Repository //和@Component功能一样,只是用在Dao层
public interface UserMapper {
List<User> queryUserList();
User queryUserById(@Param("id") int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
- 编写xml实现
这里的xml实现,是放在 resources/mybatis/mapper 下的
想要和接口绑定,还需要在配置文件中配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.faroz.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from user
</select>
<select id="queryUserById" parameterType="_int" resultType="User">
select * from user where id=#{id}
</select>
<insert id="addUser" parameterType="User">
insert into user values(#{id},#{name},#{pwd})
</insert>
<update id="updateUser">
update user
set name=#{name},pwd=#{pwd}
where id=#{id}
</update>
<delete id="deleteUser" parameterType="_int">
delete from user where id=#{id}
</delete>
</mapper>
- 配置Mybatis
上面的yml配置其实没有配置完,只配置了一个数据源,其实,还要再配置一下Mybatis,
比如上面的xml和接口的配置问题
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot-mybatis?useUnicode=true&characterEncoding=utf-8
username: admin
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
# 整合Mybatis
mybatis:
type-aliases-package: top.faroz.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
- 编写Controller进行测试
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper mapper;
@GetMapping("/list")
public ResponseEntity queryUserList() {
List<User> list = mapper.queryUserList();
return ResponseEntity.ok(list);
}
@GetMapping("/list/{id}")
public ResponseEntity queryUserById(@PathVariable("id")int id) {
User user = mapper.queryUserById(id);
return ResponseEntity.ok(user);
}
@PostMapping("/add")
public String addUser(@RequestBody User user) {
mapper.addUser(user);
return "添加成功!";
}
@PostMapping("/update")
public String updateUser(@RequestBody User user) {
mapper.updateUser(user);
return "修改成功!";
}
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") int id) {
mapper.deleteUser(id);
return "删除成功!";
}
}