1、快速入门
springboot是一个整合其他东西的总框架!
新建Maven项目--配置项目名--建成项目后可以修改group-id、artifactid、version
修改pom.xml配置文件
导入父工程:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent>
添加系统依赖(做web开发添加的依赖):
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
编写代码:
编写主程序类
@SpringBootApplication public class MainAppliaction { public static void main(String[] args) { SpringApplication.run(MainAppliaction.class,args); } }
创建Controller类
@RestController public class HelloController { @RequestMapping("/hello") public String handle01(){ return "Hello,Spring Boot 2!"; } }
运行主程序类
简化配置
springboot是一个整合其他东西的总框架!所以为了简化配置,将未来所有配置文件整合到一个文件中,固定名字,application.properties,可以修改Tomcat、springMVC的一些设置。
不配置时有默认值。
简化部署:
创建一个可执行的jar包,引入boot提供的插件,可以直接把项目打包成jar包。
只需要引入一个插件即可:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
把项目打包成jar包,直接在目标服务器上运行。
1.2、自动配置
当我们引入web开发场景下的依赖时:
spring-boot-starter-web
会
- 自动配好Tomcat
- 引入Tomcat依赖。
- 配置Tomcat
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.3.4.RELEASE</version> <scope>compile</scope> </dependency>
- 自动配好SpringMVC
-
- 引入SpringMVC全套组件
- 自动配好SpringMVC常用组件(功能)
- 自动配好Web常见功能,如:字符编码问题
-
- SpringBoot帮我们配置好了所有web开发的常见场景
/* 主程序类,相当于所有启动的入口 @SpringBootApplication:这是一个springboot应用 */ @SpringBootApplication public class MainAppliaction { public static void main(String[] args) { //返回我们的IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainAppliaction.class, args); //获取IOC容器中的所有组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } } }
- 默认的包结构
-
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 无需以前的包扫描配置
- 想要改变扫描路径,@SpringBootApplication(scanBasePackages="com.atguigu")
- 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.the.boot")
- 各种配置拥有默认值
-
- 默认配置最终都是映射到某个类上,如:MultipartProperties(配置类)
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有自动配置项
-
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
- ......
2、容器功能
2.1、组件添加
1、@Configuration
- 基本使用
在相关类上添加这个注解,告诉SpringBoot这是一个配置类 == 配置文件(.xml)
在配置类中添加方法,方法名作为相关组件的id,返回类型即组件类型,方法返回对象就是IOC中的bean实例。@bean(value = "") 可以为bean实例设置名称。配置类也是一个组件。
在配置类中创建的对象默认都是单实例的,无论获取多少次都是同一个对象。
//配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的 //配置类本身也是bean实例对象 //外部类无论对配置类里的这个bean注册方法,调用多少次获取的都是之前注册容器中的单实例对象 //Configuration新增proxyBeanMethods = true默认为true, //proxyBeanMethods:代理bean的方法 //全模式Full(proxyBeanMethods = true) 轻量级模式Lite(proxyBeanMethods = false) 组件依赖 //false则不会检查容器中是否有相应的组件,true则会检查 //如果没有依赖false,否则改成true @Configuration(proxyBeanMethods = true) //告诉springboot这是一个配置类,相当于bean.xml public class MyConfig { @Bean public User user01(){ return new User("zhangsan",18, tomcatPet()); //User组件依赖了pet组件 } @Bean("tom") public Pet tomcatPet(){ return new Pet("tomcat"); } }
/* 主程序类,相当于所有启动的入口 @SpringBootApplication:这是一个springboot应用 */ @SpringBootApplication public class MainAppliaction { public static void main(String[] args) { //1、返回我们的IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainAppliaction.class, args); //2、查看容器里面的组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } //3、从容器中获取组件 Pet tom01 = run.getBean("tom", Pet.class); Pet tom02 = run.getBean("tom", Pet.class); System.out.println(tom01 == tom02); //我们获取到的Myconfig对象本身就是代理对象 MyConfig bean = run.getBean(MyConfig.class); System.out.println(bean); //如果@Configuration(proxyBeanMethods = true) 代理对象调用方法,Springboot总会检查这个组件是否在容器内 //保持组件单实例 User user1 = bean.user01(); User user2 = bean.user01(); System.out.println(user1 == user2); User user01 = run.getBean("user01",User.class); Pet tom = run.getBean("tom", Pet.class); System.out.println(user01.getPet() == tom); } }
- Full模式与Lite模式
-
- 示例
- 最佳实战
-
-
- 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
-
2、@Bean、@Component、@Controller、@Service、@Repository
均可用来创建组件
3、@Import导入组件
在容器的组件上添加@Import注解,可以在容器中导入非常多的组件
@Import({User.class, DBHelper.class。。。}) //调用类的无参构造器中,在IOC容器中创建这两个类的bean实例。
Import导入容器的bean实例,默认bean实例的名字是全类名。
4、@Conditional
条件装配:满足conditional满足的条件才,则进行bean实例注入
2.2、原生配置文件引入
1、@ImportResource
之前的老项目采用xml文件配置,而非配置类,为了解决这一问题,如果不想一点一点迁移。
在配置类上写@ImportResource("classpath:beans.xml"),相当于将xml文件解析翻译成配置类中的形式。
2、@EnableConfigurationProperties + @ConfigurationProperties
在配置文件中,写相应的属性:在要被注入属性的类上添加注解@ConfigurationProperties(prefix = "xxx") 只会使用xxx开头的项。
并且添加@Component
想让一个javabean和配置文件中的属性进行绑定,要在目标类上添加@Component和@ConfigurationProperties(prefix="xxx")
第二种方法:在配置类上面添加@EnableConfigurationProperties
@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}
在Car上添加@ConfigurationProperties(prefix="") Car上面不需要开启@Component
3、最佳实践
1、引入场景依赖(例如,web开发采用springboot-starter-web)
2、查看自动配置了哪些
application.properties 中添加debug=true,negative生效、positive生效
3、是否需要修改
参照文档修改配置项
自定义加入或者替换组件
4、开发小技巧
3.1 Lombok
简化java bean开发,pom.xml引入Lombok
1、引入依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
2、安装idea lombok插件
3、在bean上添加注解@DATA,帮助我们生成getter和setter方法
添加@ToString注解,可以帮助我们生成tostring方法
添加@NoArgConstructor,帮助我们生成无参构造器
添加@AllArgsConstructor,帮助我们生成带所有参数的构造器, 如果是带部分属性的构造器,则无法使用,需要自己手写。
添加@EqualsAndHashCode,重写hashcode方法
添加@slf4j可以引入日志,log.info("");
在编译的时候执行。
3.2 dev-tools
1、开发中经常使用,引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
可以实现热更新,在项目里面做什么改变,只要ctrl + f9即可更新
ctrl+f9会帮我们重新编译,dev-tool会帮我们热更新
3.3 spring initializr
使用idea的spring initializr
会自动完成一些配置
以后创建项目,都是用spring初始化向导
5、yaml配置文件
与application.properties功能一样
- key: value k与v之间有空格
- 使用空格缩进表示层级关系
- 缩进不允许使用tab,只允许使用空格,idea可以用tab,只要相同层级的元素左对齐即可
- #表示注释
- ''和""表示字符串
- 字符串无需加引号
- 字面量:单个的、不可再分的值。date、boolean、string、number、null
- k: v
- 对象:键值对的集合。map、hash、set、object
- 行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
k1: v1
k2: v2
k3: v3 - 数组:一组按次序排列的值。array、list、queue
- 行内写法: k: [v1,v2,v3]
#或者
k:
- v1
- v2
- v3
实例:
@Data
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
@Data
public class Pet {
private String name;
private Double weight;
}
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
6、Web开发
spring Initilaizr中勾选Developer Tools中的工具和Web开发中的Spring Web
6.1 静态资源
静态资源放在 /static /public /resources /META-INF/resources下 都可以通过/**路径访问
静态资源会自动映射到url路径中的 /**
假如有一个动态请求名 /** 和静态资源冲突了,请求进来先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器。静态资源能找到就返回,找不到返回404.
为了使拦截器配置方便,例如登录时,把姿态资源访问路径放行,所以最好设置静态资源访问前缀,默认是无前缀的。
在yaml文件下配置 static-path-pattern: "/res/**"
改变默认的静态资源路径:static-locations: [classpath: /haha]
6.2 Rest映射及源码解析
Rest风格:
以前:/getUser /deleteUser /putUser /postUser
现在 /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
表单提交没有“put”和“delete”请求方式!!!
HiddenHttpMethodFilter
SpringBoot在WebMvcAutoConfiguration完成对Spring MVC的配置。
在WebMvcAutoConfiguration中使用的是OrderedHiddenHttpMethodFilter,它是对HiddenMethodFilter的继承。
在doHttpFilter中只有表单是POST方式,才会去取methodParam(真正提交的请求参数)
_method作为真正的请求方式。在表单的提交项中,需要使用如下方式
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
但是HiddenHttpMethodFilter需要一定的条件:
表单REST风格的原理(表单提交要使用REST的时候)
- 表单提交会提交_method=PUT、DELETE参数
- 请求过来会被Filter拦截
- 判断表单提交的方式是否为POST并且获取当前请求有无错误,如果满足条件,然后获取请求参数_method的值,判断是否为空,如果不为空则将_method参数转化为大写,所以表单里的delete和put的大写和小写都可以
- 然后判断他们判断的请求包不包括该请求:
HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())
- 原生request(post),包装模式requestWrapper重写了getMethod()方法,然后返回的是传入的值,传入的值又是在请求中传入的_method方法
- 过滤器链放行的时候用wrapper,以后调用getMethod是调用requestWrapper的方法
- 如果没有进入,就把原生的放出去
上面针对是表单,Rest如果使用客户端工具(postman)发请求,从Http层就不是了,不会通过filter。如POSTMan直接发送put、delete等方式请求,无需filter
如果不做表单提交,不用在yaml配置
@RequestMapping(value="/user",method=RequestMethod.GET)
可以简写为@GetMapping("/user") 注解内部就是上面的
同理:@PutMapping("/user") 、 @DeleteMapping("user") 、@PostMapping("user")
6.3 请求映射原理
SpringMVC功能分析都从DispatcherServlet类中的doDispatch()方法开始分析。
6.4 普通参数与基本注解
注解
@PathVariable (获取路径变量)
@RequestHeader (获取请求头)
@RequestParam (获取请求参数)
url : /car/{id}/owner/{username}?age=18&inters=basketbann&inters=game
@CookieValue (获取cookie值)
获取请求的cookie值
@RequestBody(获取请求体)请求体只有post请求才有请求体,一般是表单提交
@RequestAttribute(获取request域属性)
@Controller public class RequestController { @GetMapping("/goto") public String goToPage(HttpServletRequest request){ request.setAttribute("msg","成功了"); request.setAttribute("code",200); return "forward:/success"; //转发到 /success请求 } @ResponseBody @GetMapping("/success") public Map succese(@RequestAttribute("msg",可选required=false) String msg, @RequestAttribute("code") Integer code, HttpServletRequest request){ Object msg1 = request.getAttribute("msg"); Map<String,Object> map = new HashMap<>(); map.put("reqMethod_msg",msg1); map.put("annotation",msg); map.put("code",code); return map; } }
@Controller与@RestController的区别?
@Servlet API
@复杂参数
@自定义对象参数
6.5 各种类型参数解析原理
①
只要给方法参数位置标注注解,SpringMvc就会自动地在调用方法时确定他们的值。
请求进入之后,过filter,进入dispatcherServlet,在所有的默认HandlerMapping中查找能处理该请求的handlerMapping(一般是RequestMappingHandlerMapping),然后返回对应的handler,所谓的handler就是我们说的能处理请求的controller的哪个方法。然后再为找到的handler找到合适的handler适配器(默认有4种),第一种支持方法上标注@RequestMapping方法的,第二种支持函数式编程的,其他的2种。
找到handler的适配器之后,执行目标方法。然后设置参数解析器(参数解析器共有26个,对每个参数,遍历每个解析器看哪个解析处理这种类型的参数((1看有没有标注相应的注解,2看是否支持参数的类型)),如果支持就调用相应解析器的resolveArgument),确定我们将要执行的目标方法的每个参数的值是多少。另外我们看到returnValueHandler返回值处理器,确定我们我们返回值的类型。然后真正执行目标方法。目标方法执行完成,会将所有的数据放在ModelAndViewContainer,包含要去的页面地址View,还有Model数据(我们操作Map和Model所保存的数据)。
// Determine handler adapter for the current request.
每个参数最终都要落脚到Resolver(参数解析器)
② servlet API
③复杂参数
参数如果是Map<>类型,Model类型,或者是HttpServletRequest、HttpServletResponse类型,实质上都会添加到request作用域。
④自定义对象参数 封装POJO(例如 Person person)
自定义类型参数使用ServletModelAttributeMethodProcessor解析器,该解析器判断是否是简单类型,不是就符合。然后进行解析,然后创建一个参数的实例(new Person())空的person对象,然后创建一个webbinder【web数据绑定器】,将请求参数的值绑定到指定的java bean,(HTTP超文本传输协议,利用转换服务,把string转化为各种类型),在webBinder里面利用类型转换器(converters)把string转化为指定的类型,再封装到java bean里。
数据绑定:页面提交的请求数据(GET POST)都可以和对象属性进行绑定
可以自定义converter,实现对象属性的类型转换,将字符串转换为对象属性类型
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
7、数据响应与内容协商
1、响应页面
2、响应数据:json、xml、xls、图片音视频、自定义协议数据
1、响应json
web开发场景自动引入了json场景,json场景引入了json相关依赖,jackson.jar
在类上面添加@Controller注解,然后在相应的方法上添加@responsebody注解,给前端返回json
@Controller public class ResponseTestController { @ResponseBody @GetMapping("/test/person") public Person getPerson(){ Person person = new Person("zhangsan1",23); return person; } }
原理:
执行完目标方法后,会得到一个返回值,如果有返回值并且返回的不是一个字符串,第一步根据返回值的值和类型找到合适的返回值处理器,找到能处理它的处理器(是否支持这种类型的返回值,或者是否有相关的注解),然后调用处理器的处理方法进行处理handleReturnValue()。
SpringMVC支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
标注了@ResponseBody会使用RequestResponseBodyMethodProcessor这个处理器进行处理,利用MessageConvetor进行转换,最后以json的方式写出去。
标注了@ResponseBody会使用RequestResponseBodyMethodProcessor这个处理器进行处理,然后在底层找到合适的MessageConvetor进行转换,然后写出去(不一定是json,最终由返回类型决定)。找messageconvetor时涉及到内容协商:
内容协商
根据客户端的请求头中的Accept字段,确定返回的类型
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
- 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
- 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
-
- contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
8、拦截器-登录检查与静态资源放行
访问任何请求,登录之后才可以访问。session太麻烦,每个页面都需要判断,所以需要使用拦截器。
HandlerInterceptor拦截器接口:
preHandle():预先处理,随便发一个请求,在Controller处理之前,我们进行preHandle处理,目标方法执行以后,在还没有进入页面,可以调用postHandle(),在页面渲染完成后,可以调用afterCompletion()。
/** * @date 2022/4/5 - 15:07 * * 登录检查 * 1、配置拦截器要拦截哪些请求 * 2、把这些配置放在容器中 * 3、 */ @Slf4j public class LonginInterceptor implements HandlerInterceptor { /** * 目标方法执行之前 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("拦截的请求是" + request.getRequestURI()); HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser"); if(loginUser!=null){ return true; } // session.setAttribute("msg","请先登录"); request.setAttribute("msg","请先登录"); // response.sendRedirect("/"); request.getRequestDispatcher("/").forward(request,response); return false; } /** * 目标方法执行完成以后 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } /** * 页面渲染以后 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
/** * 1、编写一个拦截器实现HandlerInterceptor接口 * 2、拦截器注册到容器(实现WebMvcConfigurer的addInterceptor方法 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】 */ @Configuration public class AdminWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LonginInterceptor()) .addPathPatterns("/**") //所有请求都会被拦截,包括我们的静态资源 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求 } }
preHandle---目标方法---postHandle---AfterCompletion
9、文件上传(单文件与多文件)