SpringBoot2入门
https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.introducing-spring-boot
4.0.0
com.amberli
boot-01-helloworld
1.0-SNAPSHOT
jar
spring-boot-starter-parent
org.springframework.boot
2.3.4.RELEASE
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.springframework.boot
spring-boot-maven-plugin
/*
主程序类
@springBootApplication:这是一个springboot应用
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
//@ResponseBody
//@Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "hello, Spring Boot 2";
}
}
直接运行main方法
浏览器输入:http://localhost:8080/hello
application.properties
默认配置:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
server.port=8888
org.springframework.boot
spring-boot-maven-plugin
把项目打成jar包,直接在目标服务器运行即可 java -jar boot-01-helloworld-1.0-SNAPSHOT.jar
注意点:
取消掉cmd的快速编辑模式,否则需要在cmd中按Enter回车键 才能启动应用
了解自动配置原理
1.依赖管理
父项目做依赖管理
spring-boot-starter-parent
org.springframework.boot
2.4.5
开发导入starter场景启动器
见到很多spring-boot-starter-*, *就是某种场景
SpringBoot所有支持的场景 https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
所有官方首发都遵循类似的命名模式;spring-boot-starter-*,其中*是特定类型的应用程序。
见到的 *-spring-boot-starter,第三方为我们提供的简化开发的场景启动器
无需关注版本号,自动版本仲裁
可以修改版本号
先查看spring-boot-dependencies里面的当前依赖的版本
在当前项目里面重写配置
5.1.43
2. 自动配置
自动配好Tomcat
org.springframework.boot
spring-boot-starter-tomcat
2.3.4.RELEASE
自动配好SpringMVC
引入SpringMVC全套组件
自动配好SpringMVC常用组件(功能)
自动配好Web常见功能,如字符编码问题
SpringBoot帮我们配好了所有web开发的常见场景
默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径,@SpringBootApplication(scanBasePackages = "com.amberli")
或者@ComponentScan指定扫描路径
@SpringBootApplication等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
各种配置拥有的默认值
配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
按需加载所有自动配置项
非常多的starter
引入了哪些场景,这个场景的自动配置才会开启
SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面
org.springframework.boot
spring-boot-maven-plugin
在IDEA中Maven插件上点击运行clean、package,即可把工程项目打包成jar包
打包好的jar包被生成在工程项目的target文件夹内
用cmd运行 java -jar boot-01-helloworld-1.0-SNAPSHOT.jar,就可以运行工程项目
ctrl + alt + U 以UML的雷同展现类有哪些继承类,派生类以及实现哪些接口
ctrl + shift + alt + u 以图的方式显示项目中依赖之间的关系,同上区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现
alt + ins 创建新类,新包,构造方法,getter、setter等
ctrl + n 查找所有类
ctrl + H 以树形的方式展现类层次结构图
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name:names
) {
System.out.println(name);
}
}
底层注解-@Configuration详解
/**
* 1.配置类里面是有@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2.配置类本身也是组件
* 3.proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods=true)(保证每个@Bean方法被调用多少次返回的组件都是单实例的)(默认)
* Lite(proxyBeanMethods=false)(每个@Bean方法被调用多次返回的组件都是新创建的)
* 组件依赖
*/
//@Configuration(proxyBeanMethods=false) //告诉SpringBoot这是一个配置类 == 配置文件
@Configuration(proxyBeanMethods=true) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {
/**
* 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
// (proxyBeanMethods=true 说明user组件依赖pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
/*
主程序类
@springBootApplication:这是一个springboot应用
*/
//@SpringBootApplication(scanBasePackages = "com.amberli")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name:names
) {
System.out.println(name);
}
//从容器中获取组件
Pet tom01 = run.getBean("tom", Pet.class);
Pet tom02 = run.getBean("tom", Pet.class);
System.out.println("组件:" + (tom01 == tom02));
// com.amberli.boot.config.MyConfig$$EnhancerBySpringCGLIB$$106bc9e2@435cc7f9
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean); //com.amberli.boot.config.MyConfig$$EnhancerBySpringCGLIB$$106bc9e2@435cc7f9
// 如果@Configuration(proxyBeanMethods=true) 代理对象调用方法,SpringBoot总会检查这个组件是否在容器中
// 保持组件单实例
User user = bean.user01();
User user1 = bean.user01();
System.out.println(user==user1);
User user01 = run.getBean("user01", User.class);
Pet tom = run.getBean("tom", Pet.class);
System.out.println("用户的宠物:" + (user1.getPet()==tom));
}
}
配置 类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式(默认)
配置 类组件之间无依赖关系,用Lite模式加速容器启动过程,减少判断
//@ConditionalOnBean(name="tom") //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件
//@Configuration(proxyBeanMethods=false) //告诉SpringBoot这是一个配置类 == 配置文件
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods=true) //告诉SpringBoot这是一个配置类 == 配置文件
@ConditionalOnBean(name="tom") //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件
//@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
// (proxyBeanMethods=true 说明user组件依赖pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
// @Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name:names
) {
System.out.println(name);
}
boolean tom = run.containsBean("tom");
System.out.println("容器中Tom组件:" + tom); // false
boolean user01 = run.containsBean("user01");
System.out.println("容器中user01组件:"+ user01); // false
boolean tomcatPet = run.containsBean("tomcatPet");
System.out.println("容器中tomcatPet组件:" + tomcatPet); //false
}
}
@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods=true) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name="tom") //容器中必须先有 tom 组件,才会有下面的user01、tomcatPet组件
@ConditionalOnMissingBean(name="tom") //反向操作,容器中没有 tom 组件时, 才会有下面的user01、tomcatPet组件
public class MyConfig {
@Bean //给容器中添加组件,以方法名作为组件id, 返回类型就是组件类型,返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
// (proxyBeanMethods=true 说明user组件依赖pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
// @Bean("tom")
@Bean
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name:names
) {
System.out.println(name);
}
boolean tom = run.containsBean("tom");
System.out.println("容器中Tom组件:" + tom); // false
boolean user01 = run.containsBean("user01");
System.out.println("容器中user01组件:"+ user01); // true
boolean tomcatPet = run.containsBean("tomcatPet");
System.out.println("容器中tomcatPet组件:" + tomcatPet); //true
}
}
bean.xml:
使用方法:
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}
测试类:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.amberli")
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean amber01 = run.containsBean("amber01");
boolean amber02 = run.containsBean("amber02");
System.out.println("容器中amber01组件:" + amber01); // true
System.out.println("容器中amber02组件:" + amber02); // true
}
}
SpringBoot配置文件绑定方法一
@ConfigurationProperties + @Component
假设配置文件application.properties有:
mycar.brand=aodi
mycar.price=150000
只有在容器中的额组件,才会拥有SpringBoot提供的强大功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
SpringBoot配置文件绑定方法二
@EnableConfigurationProperties + @ConfigurationProperties
在配置类中绑定
@EnableConfigurationProperties(Car.class)
1.开启Car配置绑定功能
2.把Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class MyConfig {
...
}
@ConfigurationProperties(prefix = "mycar")
public class Car {
...
}
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...}
@SpringBootConfiguration @Configuration 代表当前是一个配置类
@ComponentScan 指定扫描哪些Spring注解
@EnableAutoConfiguration
@AutoConfigurationPackage //自动配置包
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
...
}
@Import({Registrar.class}) //给容器中导入一个组件
public @interface AutoConfigurationPackage {
...
}
// 1.利用Registrar给容器中导入一系列组件
// 2.将指定的一个包下的所有组件导入进MainApplication所在包下
1.利用getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){};给容器中批量导入一些组件
2.调用List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取到所有需要导入到容器中的配置类
3.利用工厂加载Map> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4.从META-INF/spring.factories位置来加载一个文件
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里也有META-INF/spring.factories
虽然127个场景的所有自动配置启动的时候默认全部加载,但是xxxAutoConfiguration按照条件装配规则(@Conditional),最终会按需配置。
* SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先
* SpringBoot先加载所有的自动配置类xxxAutoConfiguration
* 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxProperties里面读取,xxxProperties和配置文件进行了绑定)
* 生效的配置类就会给容器中装配很多组件
* 只要容器中有这些组件,相当于这些功能就有了
* 定制化配置
* 用户直接自己@Bean替换底层的组件
* 用户去看这个组件是获取的配置文件什么值就去修改
xxxAutoConfiguration-->组件-->xxxProperties里面取值-->application.properties
SpringBoot最佳实践
引入场景依赖
官方文档
查看自动配置了哪些
是否需要修改
自己定义加入或者替换组件
自定义器xxxCustomizer;
Lombok简化开发
Lombok用标签方式代替构造函数、getter、setter、toString()、EqualsAndHashCode()和日志开发等
SpringBoot已经管理Lombok,引入依赖
org.projectlombok
lombok
IDEA中File-->Settings-->Plugins,搜索插件Lombok并安装
@NoArgsConstructor //无参构造器
@AllArgsConstructor //有参构造器
@Data //setter、getter
@ToString //toString()方法
@Slf4j //简化日志开发,控制台输出日志信息
@EqualsAndHashCode
添加依赖
org.springframework.boot
spring-boot-devtools
true
Spring Initailizr项目初始化向导
配置文件 yaml的用法
key: value; kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
'#'表示注释
字符串无需加引号,如果要加,单引号''、双引号""表示字符串内容会被 转义、不转义
k: v
# 行内写法
k: {k1:v1,k2:v2,k3:v3}
# 或
k:
k1: v1
k2: v2
k3: v3
# 行内写法
k: [v1,v2,v3]
# 或
k:
- v1
- v2
- v3
@ConfigurationProperties(prefix = "person")
@Component //容器中的组件
@ToString
@Data
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List animal;
private Map score;
private Set salarys;
private Map> allPets;
}
@ToString
@Data
public class Pet {
private String name;
private Double weight;
}
person:
userName: zhangsan
boss: true
birth: 2020/12/12
age: 18
# interests: [篮球,足球,编程]
interests:
- 篮球
- 足球
- 18
animal: [阿猫,阿狗]
# score:
# english: 80
# math: 90
# python: 95
score: {english:80, math:90, python:95}
salarys:
- 9999.9
- 9999.99
pet:
name: 阿狗
weight: 99.99
allPets:
sick:
- {name:阿狗, weight: 99.99}
- name: 阿猫
weight: 88.88
- name: 阿虫
weight: 77.77
health:
- name: 阿黑
weight: 66.66
- {name:阿白, weight:55.55}
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-maven-plugin
org.springframework.boot
spring-boot-configurationprocessor
静态资源目录
只要静态资源放在类路径下:called /static(or /public or /resources or /META-INF/resources
访问:当前项目根路径/+静态资源名 即可访问
原理:静态映射/**
请求进来,先去找Controller看能否处理,不能处理的所有请求都交给静态资源处理器
静态资源也找不到则相应404页面
静态资源访问前缀
默认无前缀
spring:
mvc:
static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
也可以改变默认的静态资源路径,/static, /public, /resources, /META-INF/resources失效
web:
resources:
static-locations: [classpath:/haha/,]
可用jar方式添加css, js等资源文件
https://www.webjars.org/
org.webjars
jquery
3.6.0
访问地址:http://localhost:8080/webjars/jquery/3.6.0/jquery.js 后面的地址要安装依赖里面的包路径
静态资源路径下 index.html
可以配置静态资源路径
但是不可以配置静态资源的访问前缀,否则导致index.html不能被默认访问
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
web:
resources:
static-locations: [classpath:/haha/]
controller能处理/index
指网页标签上的小图标
favicon.ico放在静态资源目录下即可
# mvc:
# static-path-pattern: /res/** 这个会导致Favicon 功能失效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
}
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
...
}
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider,
ObjectProvider resourceHandlerRegistrationCustomizerProvider,
ObjectProvider dispatcherServletPath,
ObjectProvider> servletRegistrations) {
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
this.mvcProperties.checkConfiguration();
}
ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
WebMvcProperties mvcProperties获取和spring.mvc绑定的所有的值的对象
ListtableBeanFactory beanFactory Spring的beanFactory
HttpMessageConverters找到所有的HttpMessageConverters
ResourceHandlerRegistrationCustomizer找到 资源处理器的自定义器
DispatchcherServletPath
ServletRegistrationBean给应用注册Servlet、Filter...
...
public class WebMvcAutoConfiguration {
...
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
...
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
...
}
...
}
spring:
web:
resources:
add-mappings: false # 禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
...
}
...
public class WebMvcAutoConfiguration {
...
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
...
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
这个构造方法内的代码也解释了web场景-welcome与favicon功能中配置static-path-pattern了welcome页面和小图标失效的问题
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage);
setRootViewName("forward:index.html");
}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
//调用Controller /index
logger.info("Adding welcome page template: index");
setRootViewName("index");
}
}
@xxxMapping
以前:
/getUser 获取用户
/deleteUser 删除用户
/editUser 修改用户
/saveUser 保存用户
现在:
/user
GET-获取用户
DELETE-删除用户
PUT-修改用户
POST-保存用户
核心Filter; HiddenHttpMethodFilter
用法:表单method=post,隐藏域_method=put
Title
hello,springboot2!欢迎您的光临!
测试REST风格
测试基本注解:
@PathVariable
@RequestHeader
@RequestParam-GET
@RequestParam-GET
@MatrixVariable(矩阵变量)
@MatrixVariable(矩阵变量)
@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}
spring:
mvc:
hiddenmethod:
filter:
enabled: true # 开启表单的Rest功能
@Slf4j
@RestController
public class HelloController {
// @RequestMapping(value = "/user", method = RequestMethod.GET)
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
// @RequestMapping(value = "/user", method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
}
===================================
ctrl+n 查WebMvcAutoConfiguration类
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
* 表单提交会带上_method=PUT
* 请求过来被HiddenHttpMethodFilter拦截
* 请求是否正常,并且是POST
* 获取到_method的值
* 兼容以下请求:PUT,DELETE,PATCH
* 原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值
* 过滤器链接放行的时候用wrapper.以后的方法调用getMethod是调用requestWrapper的
* Rest使用客户端工具,如Postman直接发送Put、Delete等请求方式,无需Filter
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 请求为post请求时请求正常
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
//传入参数大小写都行
String method = paramValue.toUpperCase(Locale.ENGLISH);
//PUT,DELETE,PATCH
if (ALLOWED_METHODS.contains(method)) {
//包装模式
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@ConditionalOnMissingBean({FormContentFilter.class})表示容器中没有HiddenHttpMethodFilter.class类组件时
OrderedHiddenHttpMethodFilter组件才生效,可以仿照springboot中HiddenHttpMethodFilter的写法在配置类中加入该组件。
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
在配置类中加入自定义组件,springboot默认规则失效。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//HandlerMapping:处理器映射。/xxx-->xxx
//Dertermine handler adapter for the current request.
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//Process last-modified header, if supported by the handler
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
其中保存了所有@RequestMapping和handler的映射规则
所有的请求都在HandlerMapping中。
SpringBoot自动配置欢迎页的WelcomePageHandlerMapping.访问/能访问到index.html;
SpringBoot自动配置了默认的RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息
如果有就找到这个请求对应的handler
如果没有就是下一个HandlerMapping
我们需要一些自定义的映射出来,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping
@RestController
public class ParameterTestController {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map headers,
@RequestParam("age") Integer age,
@RequestParam("inters") List inters,
@RequestParam Map params,
@CookieValue("_xsrf") String _xsrf,
@CookieValue("_xsrf") Cookie cookie){
Map map = new HashMap<>();
// map.put("id", id);
// map.put("name", name);
// map.put("pv",pv);
//
// map.put("userAgent", userAgent);
// map.put("headers", headers);
map.put("age", age);
map.put("inters", inters);
map.put("params", params);
map.put("_xsrf", _xsrf);
System.out.println(cookie.getName() + "===>" + cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map map = new HashMap<>();
map.put("content", content);
return map;
}
// 1.语法: /cars/sell;low=34;brand=byd,audi,yd
// 2.SpringBoot 默认是禁用了矩阵变量的功能
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List brand,
@PathVariable("path") String path){
Map map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
map.put("path", path);
return map;
}
}
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg", "成功了...");
request.setAttribute("code", 200);
return "forward:/success"; // 转发到 /success请求
}
// @RequestAttribute在这个方法
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
HashMap map = new HashMap<>();
map.put("reqMethod_msg", msg1);
map.put("annotation_msg", msg);
return map;
}
}
* 方式一:实现WebMvcConfigurer接口
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
public void configurePathMatch(PathMatchConfigurer configurer){
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除:后面的内容,矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
方式二:创建返回WebMvcConfigurer Bean:
@Configuration(proxyBeanMethods = false)
public class WebConfig{
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除:后面的内容,矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
@RestController
public class ParameterTestController {
// 1.语法: /cars/sell;low=34;brand=byd,audi,yd
// 2.SpringBoot 默认是禁用了矩阵变量的功能
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List brand,
@PathVariable("path") String path){
Map map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
map.put("path", path);
return map;
}
// /boss/a;age=20/2;age=10
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age", pathVar = "empId") Integer empAge){
HashMap map = new HashMap<>();
map.put("bossAge", bossAge);
map.put("empAge", empAge);
return map;
}
}
测试封装POJO:
@RestController
public class ParameterTestController {
/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("saveuser")
public Person saveUser(Person person){
return person; // {"userName":"zhangsan","age":18,"birth":"2020-12-09T16:00:00.000+00:00","pet":{"name":"阿猫","age":"5"}}
}
}
import lombok.Data;
import java.util.Date;
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
import lombok.Data;
@Data
public class Pet {
private String name;
private String age;
}
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
@Override//本方法在ModelAttributeMethodProcessor类,
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
@Override
@Nullable//本方法在ModelAttributeMethodProcessor类,
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
...
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
}
WebDataBinder利用它里面的Converters将请求数据转成指定的数据类型,再次封装到javabEAN中
在过程当中,用到GenericConversionService:在设置每个值的时,找它里面所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
数据响应:
响应页面
响应数据:JSON、XNL、xls、图片音频、自定义协议数据
org.springframework.boot
spring-boot-starter-web
web场景会自动引入json场景
org.springframework.boot
spring-boot-starter-json
2.5.1
compile
@Controller
public class ResponseTestController {
@ResponseBody //利用返回值处理器里面的消息转换器进行处理
@GetMapping("/test/person")
public Person getPerson(){
Person person = new Person();
person.setUserName("张三");
person.setAge(18);
person.setBirth(new Date());
return person; // {"userName":"张三","age":18,"birth":"2021-06-16T10:56:34.349+00:00","pet":null}
}
}
1. @ResponseBody响应数据出去 调用RequestResponseBodyMethodProcessor处理
2. Processor处理方法返回值,通过MessageConverter处理
3. 所有MessageConverter合起来可以支持各种媒体类型数据的操作(读、写)
4. 内容协商找到最终的 messageConverter
/**
* 自定义的converter
*/
public class AmberliMessageConverter implements HttpMessageConverter {
@Override
public boolean canRead(Class> aClass, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class> aClass, MediaType mediaType) {
return aClass.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
* application/x-amberli
* @return
*/
@Override
public List getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-amberli");
}
@Override
public Person read(Class extends Person> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
//写出去
OutputStream body = httpOutputMessage.getBody();
body.write(data.getBytes());
}
}
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
public void extendMessageConverters(List> converters){
converters.add(new AmberliMessageConverter());
}
}
}
@Controller
//@RestController
public class ResponseTestController {
@ResponseBody
@GetMapping("hell")
public FileSystemResource file(){
return null;
}
/**
* 1、浏览器发请求直接返回xml application/xml jacksonXmlConverter
* 2.如果是ajax请求 返回json application/json jacksonJsonConverter
* 3、如果自定义发请求,返回自定义协商数据 application/xxx xxxConverter
* @return
*/
@ResponseBody //利用返回值处理器里面的消息转换器进行处理
@GetMapping("/test/person")
public Person getPerson(){
Person person = new Person();
person.setUserName("张三");
person.setAge(18);
person.setBirth(new Date());
return person; //张三;18;Thu Jun 17 10:58:15 CST 2021
}
}
表达式名称 | 语法 | 用途 |
---|---|---|
变量取值 | ${...} | 获取请求域、session域、对象等值 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
链接 | @{...} | 生成链接 |
片段表达式 | ~{...} | jsp:include作用,引入公共页面片段 |
使用IDEA中的Spring Initializr
thymeleaf
web-starter
devtools
lombok
登陆
...
AdminX
...
@Slf4j
@Controller
public class IndexController {
//登录页
@GetMapping(value = {"/","/login"})
public String loginnPage(){
return "login";
}
// @PostMapping("/login")
// public String main(String username, String password){
//
// // 登录成功重定向到main.html页面
// return "redirect:/index.html";
// }
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
//自5.3版本起,isEmpty(Object)已建议弃用,使用hasLength(String)或hasText(String)替代。
// if(StringUtils.hasText(user.getUserName()) && StringUtils.hasLength(user.getPassword())){
if(StringUtils.hasText(user.getUserName()) && "123".equals(user.getPassword())){
//把登录成功的用户保存起来
session.setAttribute("loginUser", user);
return "redirect:/index.html";
}else {
model.addAttribute("msg","账号或密码错误");
//回到登录页
return "login";
}
}
// // index页面
// @GetMapping("/index.html")
// public String indexPage(){
// return "index";
// }
@GetMapping("index.html")
public String indexPage(HttpSession session, Model model){
log.info("当前方法是:{}","indexPage");
//判断是否登录, 拦截器, 过滤器
// Object loginUser = session.getAttribute("loginUser");
// System.out.println("====>"+loginUser);
// if(loginUser !=null){
// return "index";
// }else {
// //回到登录页
// model.addAttribute("msg","请重新登录");
// return "login";
// }
return "index";
}
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private String userName;
private String password;
}
所有公共信息
...
- web实验-遍历数据
* 控制层
@GetMapping("/dynamic_table.html")
public String dynamic_table(Model model){
// 表格内容的遍历
List users = Arrays.asList(new User("zhangsan","123"),
new User("lisi","1234"),
new User("wangwu","12345"),
new User("zhaoliu","123456"));
model.addAttribute("users", users);
return "table/dynamic_table";
}
* 页面层
序号
用户名
密码
[[${user.password}]]
- 视图解析(源码分析)-视图解析器与视图
视图解析原理流程:
1.目标方法处理的过程中(阅读DispatcherServlet源码),所有数据都会被放在 ModelAndViewContainer 里面,其中包括数据和视图地址。
2.方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer 。
3.任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)。
4. processDispatchResult()处理派发结果(页面改如何响应)
render(mv, request, response); 进行页面渲染逻辑
根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
所有的视图解析器尝试是否能根据当前返回值得到View对象
得到了 redirect:/index.html --> Thymeleaf new RedirectView()。
ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作。
RedirectView 如何渲染【重定向到一个页面】
获取目标url地址
response.sendRedirect(encodedURL);
视图解析:
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
- 返回值是普通字符串:new ThymeleafView()—>
- 拦截器-登录检查与静态资源放行
- 编写一个实现HandlerInterceptor接口的拦截器
/**
* 拦截器 做登录检查
* 1、配置好拦截器要拦截哪些请求
* 2. 把这些配置放在容器中
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}",requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
//放行
return true;
}
//拦截住,未登录,跳转到登录页
// session.setAttribute("msg","请先登录");
// response.sendRedirect("/");
request.setAttribute("msg","请先登录");
request.getRequestDispatcher("/").forward(request,response); //请求转发
return false;
}
/**
* 目标方法执行完成以后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}", modelAndView);
}
/**
* 页面渲染以后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
- 拦截器注册到容器中,指定拦截规则
/**
* 1.编写一个拦截器实现HandlerInterceptor接口
* 2.将编写好的拦截器注册到容器中(实现WebMvcConfigurer的addInterceptor
* 3.指定拦截资源规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
/**
* 将拦截器添加到容器中
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) //将拦截器添加到容器中
.addPathPatterns("/**") //需要拦截的部分,/**拦截所有的请求,包括静态资源
.excludePathPatterns("/", "/login","/css/**","/fonts/**","/images/**","/js/**"); //需放行的部分
}
}
- 拦截器(源码分析)-拦截器的执行时机和原理
1.根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)
2.先来的顺序执行,所有拦截器的preHandle()方法
如果当前拦截器preHandle()返回true,则执行下一个拦截器的preHandle()
如果当前拦截器返回false,直接倒序执行所有已经执行了的拦截器的afterCompletion()
3.如果任何一个拦截器返回false,直接跳出不执行目标方法
4.所有拦截器都返回true,才执行目标方法
5.倒序执行所有拦截器的postHandle()方法
6.前面的步骤有任何异常都会直接倒序触发afterCompletion()
7.页面成功渲染完成以后,也会倒序触发afterCompletion()
- 文件上传-单文件与多文件上传的使用
- 页面代码/static/forms/form_layouts.html
- 控制层代码
@Slf4j
@Controller
public class FormController {
@GetMapping("form_layouts")
public String form_layouts(){
return "forms/form_layouts";
}
/**
* MultipartFile 自动封装上传过来的文件
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg")MultipartFile headerImg, //单文件上传
@RequestPart("photos") MultipartFile[] photos //多文件上传
) throws IOException {
log.info("上传的信息:email={}, username={}, headerImg={}, photos={}",email, username, headerImg.getSize(), photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\aspring\\springboot_project\\boot-05-web-admin\\src\\main\\resources\\static\\upload\\"+originalFilename));
}
if(photos.length>0){
for (MultipartFile photo: photos
) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\aspring\\springboot_project\\boot-05-web-admin\\src\\main\\resources\\static\\upload\\"+originalFilename));
}
}
}
return "index";
}
}
- 文件上传相关配置项
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
- 文件大小相关的配置项
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB
- 数据访问-数据库场景的自动配置分析与整合测试
- 导入JDBC依赖
org.springframework.boot
spring-boot-starter-jdbc
- 导入数据库驱动包(以Mysql为例)
默认版本为: 8.0.22
mysql
mysql-connector-java
如需修改版本:
1.直接依赖引入具体版本(maven的就近依赖原则)
2.重新声明版本(maven的属性的就近优先原则)
1.8
5.1.49
- 相关数据源配置类
DataSourceAutoConfiguration:数据源的自动配置
修改数据源相关的配置:spring.datasource
数据库连接池的配置,是自己容器中没有DataSource才自动配置的
底层配置好的连接池是:HikarDataSource
DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置
JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以对数据库进行CRUD
可以修改前缀为spring.jdbc的配置项来修改JdbcTemplate
@Bean @Primary JdbcTemplate:Spring容器中有这个JdbcTemplate组件,使用@Autowored
JndiDataSourceAutoConfiguration:JNDI的自动配置
XADataSourceAutoConfiguration:分布式事务相关的
- yaml数据库配置项
spring:
datasource:
url: jdbc:mysql://localhost:3306/java_study
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
- 单元测试数据源
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
@SpringBootTest
class Boot05Web01ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
log.info("记录数为:{}", aLong);
}
}
- 数据访问-自定义方式整合druid数据源
- Druid是数据库连接池,它能提供强大的监控和扩展功能
- SpringBoot整合第三方技术的两种方式
- 自定义
- 找starter场景
- 自定义方式
- 添加依赖
com.alibaba
druid
1.1.17
- 配置Druid数据源
@Configuration
public class MyDataSourceConfig {
// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissionBean(DataSource.class)
@ConfigurationProperties("spring.datasource") //复用配置yaml文件的数据源配置
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
- 配置Druid的监控页功能
Druid内置提供了一个StatViewServlet用于展示Druid的统计信息,这个StatViewServlet的用途包括:
提供监控信息展示的html页面 http://localhost:8080/druid/sql.html
提供监控信息的JSON API
Druid内置提供一个StatFilter,用于统计监控信息
WebStatFilter用于采集web-jdbc关联监控的数据,如SQL监控、URI监控
Druid提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。
@Configuration
public class MyDataSourceConfig {
// 默认的自动配置是判断容器中没有才会配@ConditionalOnMissionBean(DataSource.class)
@ConfigurationProperties("spring.datasource") //复用配置yaml文件的数据源配置
@Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// 加入监控和防火墙功能
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
/**
* 配置druid的监控页功能
*/
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
// 监控页账号和密码:
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据
*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterRegistrationBean.addInitParameter("exclusions","*.js,*,jpg,*.png,*.css,*.icon,/druid/*");
return filterRegistrationBean;
}
}
- 数据访问-druid数据源starter整合方式
- 引入依赖
com.alibaba
druid-spring-boot-starter
1.1.17
- 分析自动配置
扩展配置项 spring.datasource.druid
自动配置类 DruidDataSourceAutoConfigure
DruidSpringAopConfiguration.class,监控SpringBean的,配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class,监控页的配置
spring.datasource.druid.stat-view-servlet默认开启
DruidWebStatFilterConfiguration.class, web监控配置
spring.datasource.druid.web-stat-filter默认开启
DruidFilterConfiguration.class所有Druid的filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
- yaml配置示例
spring:
datasource:
url: jdbc:mysql://localhost:3306/java_study
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
filters: stat,wall #底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: #配置监控页功能
enabled: true
login-username: admin
login-password: admin123
resetEnable: false
web-stat-filter: #监控web
enabled: true
url-pattern: /*
exclusions: '*.js,*,jpg,*.png,*.css,*.icon,/druid/*'
aop-patterns: com.amberli.boot.* #监控SpringBean
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
log-slow-sql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false