1.处理静态资源
我们创建一个springboot项目,添加web依赖后,它的默认项目结构是下面这样的
我们会将我们的静态资源放到 resources
下面的 static
里面,然后就可以直接使用路径去访问,http://127.0.0.1:8080/xxx ,有很多人初学的时候会这样访问: http://127.0.0.1:8080/static/xxx,这样就会出现404,因为springboot找静态资源就是直接去static下面找的,如果你使用第二种访问方式,那么就需要在static下面在建立一个名字叫static的文件夹,然后将文件放进去,以放个图片为例:
使用static是访问不了,下面是我自定义的一个错误异常页面:
我们在static下面在建立static,然后将照片放进去,重启项目就可以访问了:
除此之外,springboot一共给我们提供了5个静态资源的访问的位置
他们5个之间有一个优先级,META-INF.resources
最高,webapp
最低,你把静态资源放在这五个的任意一个都可以,如果有重名的话,优先级越高就越容易被加载。
优先级具体是怎样的呢?我们可以打开WebMvcAutoConfiguration
可以看到里面的addResourceHandlers
方法如下:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations()); // 主要关注这个东西
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
点开getStaticLocations()方法后:
public String[] getStaticLocations() {
return this.staticLocations;
}
然后去找到定义:
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations;
private boolean addMappings;
private boolean customized;
private final WebProperties.Resources.Chain chain;
private final WebProperties.Resources.Cache cache;
public Resources() {
this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
this.addMappings = true;
this.customized = false;
this.chain = new WebProperties.Resources.Chain();
this.cache = new WebProperties.Resources.Cache();
}
}
可以看到staticLocations
是一个数组,而里面就提供了四个静态资源的位置,这个位置的排序方式就是他们的优先级的顺序,这里只有四个,还有一个是在WebMvcAutoConfiguration
下的 getResourceLocaltion()
方法加进去的。
2.静态资源的两种配置方式
有时候需要自定义一些文件夹,如果没有放在上面的5个文件夹下面,就访问不到,这里有两种配置方式,一种是使用 application.properties
这种配置文件配置,还有一种是使用 java 类去配置,比如现在我在resources下面新建一个文件夹 cy 然后里面放了一个AAA.jpeg 的图片,现在这个是无论如何都无法访问的。
我们可以在application.properties
里面配置一下静态资源的位置
spring.web.resources.static-locations=classpath:/cy/
spring.mvc.static-path-pattern=/**
现在使用 http://127.0.0.1:8080/AAA.jpeg 就可以访问了
使用java类去配置:
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/cy/");
}
}
和配置文件等效。
3.单文件和多文件上传
springboot中文件上传有一个接口MultipartResolver
,它有两个实现类,一个是CommonsMultipartResolver
另一个是 StandardServletMultipartResolver
,前者用于老版本的servlet,目前springboot使用的是后面这个。
下面尝试单文件上传:
1.新建一个springboot项目,只需要添加web相关的依赖
2.在resource下面的static下面新建一个index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
文件上传一定要用post,还有enctype的参数,这两个都必须改成上面那样。
3.编写一个controller接口并返回文件预览的地址
/**
* @Author: chenyong
* @Date: 2021/8/14 11:10
*/
@RestController
public class FileUploadController {
SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/"); // 这里在前后都加了/ 是为了拼接路径,上传的文件按照日期分类
/**
* 将文件存到临时文件里面,按找日期分类
* @param file
* @param request
* @return
*/
@PostMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest request) {
String realPath = request.getServletContext().getRealPath("/"); // 获取服务器的路径
String format = sdf.format(new Date());
String path = realPath + format; // 文件存放的路径
File folder = new File(path);
if(! folder.exists()){
folder.mkdirs(); // 如果文件夹不存在就创建
}
String oldName = file.getOriginalFilename();// 获取原始的文件名字
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); // 怕文件重名,采用随机的名字,并保留原始的扩展名
try {
file.transferTo(new File(folder,newName));
String result = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + format + newName;
// getScheme() 用来获取协议:http还是https
// getServerName() 获取服务器的名字
// 然后把端口和文件路径已经文件名组合就可以看到上传的文件了
return result;
} catch (IOException e){
e.printStackTrace();
}
return "文件上传失败";
}
}
选择文件上传后,会给出预览地址,在游览器上面输入就可以查看:
多文件上传
多文件上传有两种方式,第一种是一个选项可以选择多个文件上传,另外一个是有多个上传选项,每个选项只能选择一个文件,先看第一种,修改html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple> <!--加一个multiple表示可以选多个文件-->
<input type="submit" value="上传">
</form>
</body>
</html>
修改后端接口,因为是多文件,就需要采用数组去接收参数
@PostMapping("/upload")
public List<String> upload(MultipartFile[] files, HttpServletRequest request) {
String realPath = request.getServletContext().getRealPath("/"); // 获取服务器的路径
String format = sdf.format(new Date());
String path = realPath + format; // 文件存放的路径
File folder = new File(path);
if(! folder.exists()){
folder.mkdirs(); // 如果文件夹不存在就创建
}
List<String> list = new ArrayList<>();
for(MultipartFile file : files){
String oldName = file.getOriginalFilename();// 获取原始的文件名字
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf(".")); // 怕文件重名,采用随机的名字,并保留原始的扩展名
try {
file.transferTo(new File(folder,newName));
String result = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + format + newName;
list.add(result);
} catch (IOException e){
e.printStackTrace();
}
}
return list;
}
第二种就是单文件上传,然后多搞几个:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="file" name="file2">
<input type="submit" value="上传">
</form>
</body>
</html>
public List<String> upload(MultipartFile file1,MultipartFile file2 HttpServletRequest request) {
// 其他操作都是一样的
}
4.三种跨域方式处理
第一种方法,在接口上面添加 @CrossOrigin("允许的地址")
此时这个接口就被允许跨域访问。
@CrossOrigin("http:localhost:8081")
@GetMapping
public String test(){
return "Cross Origin";
}
也可以直接加在Controller的类上,那么这个类的所有方法都允许被跨域访问
@RestController
@CrossOrigin("http:localhost:8081")
public class CrossOriginController {
}
上面这种方式很繁琐,需要在每个类上面都添加一下,于是可以使用第二种方式,配置类:
/**
* @Author: chenyong
* @Date: 2021/8/14 14:18
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 对所有方法开启跨域
.allowedHeaders("*")
.allowedMethods("*") // 允许所有的方法,GET、PUT、POST等
.allowedOrigins("*") // 允许所有的站点跨域访问,也可以指定具体的站点
.maxAge(1800); // 缓存的持续的最大时间
}
}
第三种方式是创建一个 CorsFilter 的bean
@Bean
CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration cfg = new CorsConfiguration();
cfg.addAllowedHeader("*");
cfg.addAllowedOrigin("*"); // 允许所有的站点跨域访问,也可以指定具体的站点
cfg.addAllowedMethod("*");// 允许所有的方法,GET、PUT、POST等
cfg.setMaxAge(1800L);// 缓存的持续的最大时间
source.registerCorsConfiguration("/**",cfg); // 第一个参数是开启跨域的地址 /** 表示对所有的地址开启
return new CorsFilter(source);
}
5.配置拦截器
拦截器通常可以完成如下工作:
- 日志记录
- 权限控制
- 数据预处理等
它是在请求到达controller之前做的处理,通常你可以判断一些信息,比如用户不登陆就不让他访问这些操作等,自定义拦截器有两个步骤:
- 实现HandlerInterceptor
- 自定义配置类实现WebMvcConfigurer,将自定义的拦截器添加进去.
自定义拦截器
/**
* @Author: chenyong
* @Date: 2021/8/14 14:38
*/
@Component
public class MyInterceptor implements HandlerInterceptor {
// 请求到达之前执行,返回true就是放行,false就不会执行后面的拦截器和接口
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getRemoteAddr() + ": preHandle");
return true;
}
// 执行完成了controller里面的方法后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(request.getRemoteAddr() + ": postHandle");
}
// 只有当preHandle返回为true时才会执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(request.getRemoteAddr() + ": afterCompletion");
}
}
将拦截器添加到配置里面去:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 下面的配置表示对除了 /test 的接口外其他的都会经过这个拦截器
registry.addInterceptor(myInterceptor()).addPathPatterns("/**").excludePathPatterns("/test");
}
下面定义两个接口
@GetMapping("/test")
public String test(){
System.out.println("test");
return "test";
}
@GetMapping("/test1")
public String test1(){
System.out.println("test1");
return "test1";
}
访问 /test1 在访问 /test 会出现如下输出
127.0.0.1: preHandle
test1
127.0.0.1: postHandle
127.0.0.1: afterCompletion
test
6.系统启动任务
有时候可能会需要用到,当系统一启动的时候就会帮我自动执行一些任务,这时候就需要用到这个东西,它有两种实现方式,第一种是实现 CommandLineRunner
接口,另一种是实现 ApplicationRunner
接口,两者实现的功能都是一样,只是参数不太一样。
方法一:CommandLineRunner
@Component
@Order(100)
public class MyCommandLineRunner01 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner args = "+ Arrays.toString(args));
}
}
方法儿:ApplicationRunner
@Component
@Order(98)
public class MyApplicationRunner01 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 获取没有键的参数,获取的值和 CommondLinerunner 一致
List<String> nonOptionArgs = args.getNonOptionArgs();
System.out.println("ApplicationRunner nonOptionArgs = " + nonOptionArgs);
// 获取键值对
Set<String> optionNames = args.getOptionNames();
for(String optionName : optionNames){
System.out.println(optionName + "->" + args.getOptionValues(optionName));
}
// 获取命令行中的所有参数
String[] sourceArgs = args.getSourceArgs();
System.out.println("ApplicationRunner sourceArgs = " + Arrays.toString(sourceArgs));
}
}
在idea的环境中添加参数
允许项目后可以看到,设置优先级有效,ApplicationRunner 比 CommandLineRunner 先执行。
7.路径映射
在使用thymeleaf的时候,我们如果要访问对应的页面需要写一个视图Controller去返回视图。
@Controller
public class ViewController {
@GetMapping("/index")
public String index(){
return "index";
}
}
通过/index就可以访问到resources/templates/index.html
的这个视图,如果有大量的这样的解析就很麻烦,可以通过配置类来完成这个路径映射。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
registry.addViewController("/home").setViewName("home");
}
}
这简化了我们的对视图的解析,但这方式的局限性就是不能给视图传递数据。