这篇文章,介绍了通过SpringBoot开发的具体流程,和SpringBoot中一些常见的知识点
Spring官网创建
https://start.spring.io/
不推荐
IDEA的Spring Initializr
File->New->Project->Spring Initializr
中途可以选择需要的组件
初始化后项目的目录
一般情况下项目的目录结构
com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- controller
| +- CustomerController.java
|
+- config
| +- swagerConfig.java
|
Application.java
是项目的启动类新建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扫描到,又可以让返回请求不跳转到页面,而是在浏览器上显示返回内容
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 的内容。
my-phone: 18120194475
@Value("my-phone")
private String phoneNum;
这种方式,Spring不推荐
Spring建议使用一下方式
配置文件:
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绑定
再给出配置文件的优先级:
https://snailclimb.gitee.io/springboot-guide/#/./docs/advanced/springboot-handle-exception
https://snailclimb.gitee.io/springboot-guide/#/./docs/advanced/springboot-handle-exception-plus
这里我先暂时跳过,先以只用Mybatis为主
在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
的方法,这个方法实现了对用户请求的过滤。具体流程大体是这样的:
这里我们自定义一个过滤器:
@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("销毁过滤器");
}
}
配置文件中注册自定义的过滤器:
@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;
}
}
在自定义的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
在配置文件中调用setOrder
,来设置启动优先级
只要在过滤器上加上一个@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 {
}
测试:
拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。
要自定义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依赖
那一定要进行下面的步骤:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
#开发环境设置thymeleaf缓存不可用,加快响应速度
spring:
thymeleaf:
cache: false
test.html:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor exampletitle>
head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Homea>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)a>
div>
<h3>Spring Boot Mvc Interceptorh3>
<span style="color:blue;">Testing LogInterceptorspan>
<br/><br/>
See Log in Console..
body>
html>
login.html:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor exampletitle>
head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Homea>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)a>
div>
<h3>This is Login Pageh3>
<span style="color:blue">Testing OldLoginInterceptor & AdminInterceptorspan>
<br/><br/>
See more info in the Console.
body>
html>
测试:
输入oldLogin:
回车,就会跳转到login,说明oldLogin被拦截了:
一个是筛子,一个是关卡
- 创建自定义过滤器类,实现Filter接口,复习而其中三个方法
- 使用注解或者手动配置配置文件
- 注解:@WebFilter(filterName = “MyFilterWithAnnotation”, urlPatterns = “/api/*”)
- 配置文件的方式,翻阅上面的文档
- 创建自定义拦截器,并实现
HanderInterceptor
接口- 创建拦截器配置类,实现WebMvcConfigurer接口,为每个拦截器进行配置(配置拦截路径或者忽略路径)
这里先给出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)
)
也可以在新建项目的时候,导入相关的模块
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.xmlinclude>
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
注解和@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实现,是放在 resources/mybatis/mapper 下的
想要和接口绑定,还需要在配置文件中配置
<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>
上面的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
@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 "删除成功!";
}
}