springboot整合web开发

Posted 阿尔托莉雅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot整合web开发相关的知识,希望对你有一定的参考价值。

springboot整合web开发

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.配置拦截器

拦截器通常可以完成如下工作:

  1. 日志记录
  2. 权限控制
  3. 数据预处理等

它是在请求到达controller之前做的处理,通常你可以判断一些信息,比如用户不登陆就不让他访问这些操作等,自定义拦截器有两个步骤:

  1. 实现HandlerInterceptor
  2. 自定义配置类实现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");
    }
}

这简化了我们的对视图的解析,但这方式的局限性就是不能给视图传递数据。

以上是关于springboot整合web开发的主要内容,如果未能解决你的问题,请参考以下文章

全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)(代码片段

Springboot整合web开发

SpringBoot整合WEB开发--启动任务系统

SpringBoot整合WEB开发--配置AOP

SpringBoot:2.SpringBoot整合Thymeleaf模板引擎渲染web视图

SpringBootWeb整合开发