本文记录我学习springboot的整个流程, 会持续不断更新, 具体的代码开源到了gitee, 点击跳转
@Controller 作⽤用:⽤用于标记这个类是⼀一个控制器器,返回⻚页⾯面的时候使⽤用;如果要返回 JSON,则需要在接⼝口上使⽤用 @ResponseBody才可以
@RestController 作⽤用:⽤用于标记这个类是⼀一个控制器器,返回 JSON数据的时候使⽤用,如果使⽤用这个注解,则接⼝口返回数据会被序列列化为 JSON
所以:@RestController = @Controller+@ResponseBody
@RequestMapping 作⽤用:路路由映射,⽤用于类上做 1级路路径;⽤用于某个⽅方法上做⼦子路路径
@SpringBootApplication 作⽤用 : ⽤用于标记是 SringBoot应⽤用,⾥里里⾯面包含多个⼦子注解 ,即
@SpringBootApplication =
@Configuration+@EnableAutoConfiguration+@ComponentScan
(下⾯面的⽬目前只需简单理理解即可,想深⼊入的同学,后续可以看专⻔门的 Spring原理理课程深⼊入 )
@Configuration: 主要标注在某个类上,⽤用于 spring扫描注⼊入 ,⼀一般结合 @Bean使⽤用
@EnableAutoConfiguration: 启⽤用 Spring的⾃自动加载配置 ,⾃自动载⼊入应⽤用程序所需的所有 Bean
@ComponentScan:告诉spring扫描包的范围,默认是Applocation类所在的全部⼦子包,可以指定
其他包
@ComponentScan({"net.xdclass.package1","net.xdclass.package2"})
SpringBoot2.X开发规范解读SpringBoot2.x⽬目录⽂文件结构讲解和静态资源访问src/main/java:存放代码src/main/resourcesstatic: 存放静态⽂文件,⽐比如 css、js、image, (访问⽅方式 http://localhost:8080/js/main.js)templates:存放静态⻚页⾯面 jsp,html,tplconfig:存放配置⽂文件 ,application.propertiesresources:静态资源⽂文件 Spring Boot 默认会挨个从
META/resources >resources >static >public
⾥面找是否存在相应的资源,如果有则直接返回,不在默认加载的目录,则找不不到
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
@SpringBootApplication@ComponentScan,并配置需要扫描的包名,如(basePackages = )@ComponentScan(basePackages ={"net.xdclass.controller","net.xdclass.service"})官方推荐
# pom⽂文件新增 maven插件
>
>
>
>org.springframework.boot >
>spring-boot-maven-plugin >
>
>
>
# 如果没有加,则执⾏行行jar包 ,报错如下
# java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar
# no main manifest attribute, in spring-boot-demo-0.0.1-SNAPSHOT.jar
构建:mvn install
构建跳过测试类 mvn install -Dmaven.test.skip=true
target⽬目录下有对应的 jar包就是打包后项⽬目
进到对应的target⽬目录启动 java -jar xxxxx.jar 即可
想后台运⾏行行,就⽤用守护进程 nohup java -jar xxx.jar &
E:\12_ssm_Java_springboot\springboot\demo> mvn install
ls .\target\demo-0.0.1-SNAPSHOT.jar
E:\12_ssm_Java_springboot\springboot\demo> java -jar .\target\demo-0.0.1-SNAPSHOT.jar
unzip *.jar
example.jar
|
+-META-INF
| +-MANIFEST.MF # jave 虚拟机入口函数等重要路径
+-org
| +-springframework
| +-boot
| +-loader
| +-<spring boot loader classes>
+-BOOT-INF
+-classes # 自己写的各种文件
| +-mycompany
| +-project
| +-YourClasses.class
+-lib # 依赖的包
+-dependency1.jar
+-dependency2.jar
创建项目, 配置启动类, 建立对应包
controller
service
dao
domain
utils




每个API都需要有一个统一返回, 在定义自己的返回信息
/**
* @Author: zjq
* @Date: 2022/9/4 15:25
* @Description: 用于每次请求返回给前端的统一接口协议 如下:
* {
* code: 0 //信息码
* data:{"1":"hello", "2":"world" ..} // 实际数据
* msg: "这里是提示信息, 报错信息"
* }
**/
@Data
public class JsonData {
private int code;
private Object data;
private String msg;
public JsonData() {
}
public JsonData(int code, Object data) {
this.code = code;
this.data = data;
}
public JsonData(int code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
// code = 0 表示成功, -1表示失败, 失败需要返回信息
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data);
}
public static JsonData buildError(String msg) {
return new JsonData(-1, "", msg);
}
public static JsonData buildError(String msg, int code) {
return new JsonData(code, "", msg);
}
}
以后每个接口的返回都是这样做返回的
@GetMapping(value = "list")
public Object list() {
List private static Map sessionMap = new HashMap<>(); // 模拟内存数据库
@Override
public String login(String username, String pwd) {
User user = userMapper.login(username, pwd);
if (user == null) {
return null;
} else {
String token = UUID.randomUUID().toString();
System.out.println(token);
sessionMap.put(token, user);
// 设置token, 记录user缓存
return token;
}
}

requestBody对象json数组提交接口开发post请求, RequestBody方式, json对象映射, 数组对象提交接口开发
@Data
public class Video {
private int id;
private String title;
private String summary;
private int price;
private String coverImg;
private Date createTime;
private List chapterList;
@PostMapping(value = "save_video_chapter")
public JsonData saveVideoChapter(@RequestBody Video video) {
System.out.println(video.toString());
return JsonData.buildSuccess("");
}

SpringBoot配置Jackson处理理字段常⽤用框架 阿⾥里里 fastjson,谷歌 gson等
javaBean序列化为Jsonjackson > fastJson > Gson > json-lib 同个结构Jcakson处理相关自动@JsonIgnore 比如请求用户信息时的密码@JsonFormat(pattern="yyyy-MM-dd-hh:mm:ss", locale="zh", timezone="GMT+8")@JsonInclude(Include.NON_NULL)@JsonProperty @JsonProperty("创建的时间") // 创建的别名本节: 视频创建时间返回自定义格式, 过滤用户敏感信息
序列化和反序列化操作
//序列列化操作
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(list);
System.out.println(jsonStr);
//反序列列化操作
List <dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
configuration>
plugin>
plugins>
build>
build→Compiler→打开自动编译compiler.automake.allow.when…
xml
properties
json
yaml
在springboot框架中, resource文件夹可以存放配置的文件有两种
application.properties的用法, 扁平的k/v格式
server.port=8081
server.tomcat.accept-count=200
server.compression.min-response-size=1M
application.yml的用法, 树状格式
server:
port: 8082
tomcat:
accept-count: 200
compression:
min-response-size: 1M
同一个目录下, 以yaml为主, properties进行补充
classpath 根目录下, resource文件夹编译之后就到该目录, 即 resources根目录 就是classpath目录resources/config/项目根目录项目根目录/config直接子目录/configjava -jar *.jar --spring.config.location=D:\config/application: 8082
config:
application: 8081
src:
resources:
application: 8084
config:
application: 80803
java -jar *.jar --server.port=8080
# 优先顺序是 8080>8081>8082>8083>8084
第一种: 默认配置文件 spring.profiles.active=prod # 就会加载对应的prod配置文件
/* 默认配置文件 spring.profiles.active=prod # 就会加载对应的prod配置文件
resources/application.properties
resources/application-dev.properties # 开发环境 (乌三)
resources/application-prod.properties # 生产环境(现网)
*/
第二种: 通过注解方式选择
@RestController
@RequestMapping(value = "api/v1/test")
@PropertySource({"classpath:config/pay.properties"})
public class TestController {
@Value("${wx.pay.appid}")
private String wxPayAppId;
@Value("${wx.pay.secret}")
private String wxPaySecret;
}

- 设置加载

@Value("${rand_int}")
private String randInt;
@Value("${rand_long}")
private String randLong;
@Value("${rand_uuid}")
private String randUuid;
@Value("${rand_less_than_ten}")
private String randLessThanTen;
@Value("${rand_in_range}")
private String randInRange;
/*
配置文件
# mock 设置随机值
rand_value="${random.value}"
rand_int="${random.int}"
rand_long="${random.long}"
rand_uuid="${random.uuid}"
rand_less_than_ten="${random.int(10)}"
rand_in_range="${random.int[1024,65536]}"
*/
需求分析 →设计→开发→测试→上线
测试里面的种类
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
@RunWith(SpringRunner.class) //底层⽤用 junit SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoApplicationTests.class})//启动整个springboot⼯工程
public class SpringBootTests { }
@Before // 资源初始化
@Test // 测试逻辑
@After // 资源回收
判断程序结果是否符合预期
TestCase.assertXXX

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {
private static final Logger LOOGER = LoggerFactory.getLogger(UserTest.class);
@Autowired
private UserController userController;
@Test
public void loginTest() {
User user = new User();
user.setUsername("jack");
user.setPwd("134");
JsonData jsonData = userController.login(user);
LOOGER.info(jsonData.toString());
TestCase.assertEquals(jsonData.getCode(), 0);
}
}
@RunWith(SpringRunner.class) // 底层用Junit, SpringJunit4ClassRunner
@SpringBootTest(classes = {DemoApplication.class}) //
public class VideoTest {
private static final Logger LOGGER = LoggerFactory.getLogger(VideoTest.class);
@Autowired
private VideoService videoService;
@Test
public void testVideoList() {
List<Video> videoList = videoService.videoList();
TestCase.assertTrue(videoList.size()>0);
}
}
@Autowired
private MockMvc mockMvc;
@Test
public void testVideoListApi() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/pub/video/list"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
int status = mvcResult.getResponse().getStatus();
LOGGER.info("获取状态: {}", status);
String result = mvcResult.getResponse().getContentAsString();
LOGGER.info(result);
}
@GetMapping("list")
public JsonData getList(){
int a = 1/0; // 这里有异常会报错
return JsonData.buildSuccess("hello list");
}
@RestControllerAdvice // 标记这是一个异常处理类
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class) // 标记所有异常都在这里回收
JsonData handlerException(Exception e, HttpServletRequest request) {
return JsonData.buildError("服务器出问题了", -2);
}
}
过滤器相当于在请求之前, 将请求内容进行一遍过滤, 比如请求的token为管理员还是用户
人→管理员(filter)→景区开放
SpringBoot2.X里面的过滤器包含
ApplicationContextHeaderFilter
OrderedCharacterEncodingFilter
OrderedFormContentFilter
OrderedRequestContextFilter
过滤器的优先级
Ordered.HIGHEST_PRECEDENCE
Ordered.LOWEST_PRECEDENCE
自定义Filter, 避免和默认的Filter优先级一样, 不然会冲突
注册Filter配置的两种方式
bean FilterRegistrationBeanServlet3.0 WebFilter@ServletComponentScan,进⾏行扫描Filter类,implements Filter,并实现对应的接口@WebFilter 标记一个类为 filter,被spring进⾏行行扫描urlPatterns:拦截规则,支持正则匹配chain.doFilter的方法的调用,来实现是否通过放行resp.sendRedirect("/index.html") 或者 返回json字符串串
权限控制, 用户登录状态控制
Servlet3.0的注解原⽣生 Servlet实战Servlet3.0的注解自定义原生@WebServlet(name = "userServlet", urlPatterns = "/api/v1/test/customs")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter write = resp.getWriter();
write.write("hello world, this is my custom servlet");
write.flush();
write.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
ServletContextListener // 应用启动监听器
HttpSessionLisener // 回话监听器
ServletRequestListener //请求监听器
@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("======contextDestroyed========");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("======contextInitialized========");
}
}
和过滤器用途基本类似
// 第一步: 继承拦截器
@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer{}
// 第二步 自定义 拦截器
preHandle: 调用Controller某个方法 之前
postHandle: 调用Controller某个方法 之后, 视图渲染之前, 如果控制器Controller出现了 异常, 则不会执行该方法
afterHandle: 调用Controller某个方法 不管有没有异常, 都会被调用, 用于清理资源

@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册配置
// 配置登录拦截器
// 先注册, 先被拦截
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/test/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
@Bean
public LoginInterceptor getLoginInterceptor() {
return new LoginInterceptor();
}
}
- 是否有加@Configuration
- 拦截路路径是否有问题 ** 和 *
- 拦截器器最后路路径⼀一定要 /** 如果是⽬目录的话则是 /*/
Filter和Interceptor二者都是AOP编程思想的体现,功能基本都可以实现Filter能做的事情它都能做Filter在只在Servlet前后起作用,而Interceptor够深入到方法前后、异常抛出前后等filter依赖于Servlet容器即web应用中,而Interceptor不依赖于Servlet容器所以可以运行在多种环境。Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。Filter和Interceptor的执行顺序action执行->拦截后->过滤后registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/test/**").excludePathPatterns("/**/*.html","/**/*.js");
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
@GetMapping("error")
public JsonData getList(){
int a = 1/0; // 这里有异常会报错
return JsonData.buildSuccess("hello list");
}
@ControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class)
Object handlerException(Exception e, HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html"); // 浏览器访问这个页面一样
modelAndView.addObject("msg", e.getMessage());
return modelAndView;
}
}
error.html的路径是 src/main/resources/templates/error.html
Java Server Pages动态网页技术,由应用服务器中的JsP引擎来编译和执行,再将生成的整个页面返回给客户端
可以写java代码
持表达式语言(e1、jst1)
内建函数
JsP->Servlet(占用JVM内存)permSize
javaweb官方推荐
springboot官方不推荐
FreeMarker Template Language:(FT工) 文件一般保存为xxx.ftl
严格依赖MvC模式,不依赖Servlet容器(不占用JvM内存)
内建函数
轻量级的模板引擎(复杂逻辑业务的不推荐,解析DOM或者XML会占用多的内存)
可以直接在浏览器中打开且正确显示模板页面
直接是html结尾,直接编辑xdlcass.net/user/userinfo.html
社会工程学伪装
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
spring:
freemarker:
allow-request-override: false
cache: false
charset: UTF-8
check-template-location: true
content-type: text/html # 文件类型
expose-request-attributes: true
expose-session-attributes: true
suffix: .ftl # # 文件后缀
template-loader-path: classpath:/templates/ # 文件路径
@Controller // 这里需要注意, 不是RestController
@RequestMapping(value = "api/v1/test")
public class FreemarkerController {
@Autowired
WXConfig wxConfig;
@GetMapping(value = "freemarker")
public String index(ModelMap modelMap) {
modelMap.addAttribute("setting", wxConfig);
return "user/fm/index";
// 不用加后缀, 因为配置文件里面已经指定了后缀
}
}
src/main/resources/templates/user/fm/index.ftl
<h1>setting.wxPayAppId : ${setting.wxPayAppId}h1>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
thymeleaf:
cache: false
mode: HTML5
prefix: classpath:/templates/
encoding: UTF-8
servlet:
content-type: text/html
suffix: .html
src/main/resources/templates/tl/index.html
Java自带的java.util.Timer类配置比较麻烦,时间延后问题Quartzi框架:配置更简单,xml或者注解适合分布式或者大型调度作业SpringBoot框架自带@EnableScheduling // 打开定时器扫@Component@Scheduled()@Component
public class VideoOrderTask {
// 每2s执行一次, 前提是该任务执行完成后, 执行时间点后xx秒再次执行
@Scheduled(fixedRate = 2000)
public void fixedRateTest() {
}
// https://tool.lu/crontab/
@Scheduled(cron = "*/9 * * * * *")
public void cronTest() {
}
// 上一次结束时间点往后再过3s后 执行一次
@Scheduled(fixedDelay = 3000)
public void fixedDelayTest() {
}
}

@EnableAsync // 打开异步任务@Component 和 @AsyncController直接使用即可// com/huawei/demo/task/AsyncTask.java
@Async
@Component
public class AsyncTask {
public void task1() {
Thread.sleep(4000);
System.out.println("task 1");
}
public void task2() {
Thread.sleep(4000);
System.out.println("task 2");
}
}
// src/main/java/com/huawei/demo/controller/TestController.java
@Autowired
AsyncTask asyncTask;
@GetMapping(value = "async")
public JsonData getAsync() {
long start = System.currentTimeMillis();
asyncTask.task1();
asyncTask.task2();
asyncTask.task3();
long end = System.currentTimeMillis();
return JsonData.buildSuccess(end - start);
}
// async
public Future<String> task4(){
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" task 4 ");
return new AsyncResult<String>("task4");
}
// Controller
@GetMapping("async_get_result")
public JsonData testAsync(){
long begin = System.currentTimeMillis();
Future<String> task4 = asyncTask.task4();
Future<String> task5 = asyncTask.task5();
for(;;){ // 一直等待异步任务完成
if(task4.isDone() && task5.isDone()){
try {
String task4Result = task4.get();
System.out.println(task4Result);
String task5Result = task5.get();
System.out.println(task5Result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
break;
}
}
}
long end = System.currentTimeMillis();
return JsonData.buildSuccess( end - begin);
}