SpringBoot基本知识扫盲

Lou.Chen
大约 32 分钟

一、springboot基本配置

#设置端口
server.port=8081
#设置上下文路径 (即请求包含的前缀)
server.servlet.context-path=/lc
#配置内嵌的tomcat编码
server.tomcat.uri-encoding=UTF-8
#还可以配置其他jetty相关的属性
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除内部的tomcat-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--排除tomcat服务器后,使用jetty服务器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
  </dependencies>

1、关于springboot启动类

@SpringBootApplication
public class TomcatApplication {
    public static void main(String[] args) {
        SpringApplication.run(TomcatApplication.class, args);
    }

}

@SpringBootApplication===>

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
①@ComponentScan
@Service,@Repository,@Component,@Controller用来定义一个bean.
@ComponentScan注解就是用来自动扫描被这些注解标识的类,最终生成ioc容器里的bean.可以通过设置@ComponentScan(basePackages,includeFilters,excludeFilters)属性来动态确定自动扫描范围,类型以及不扫描的类型.

默认情况下:它扫描所有类型,并且扫描范围是@ComponentScan注解所在配置类包及子包的类

②@SpringBootConfiguration
这个注解的作用与@Configuration作用相同,都是用来声明当前类是一个配置类.可以通过@Bean注解生成IOC容器管理的bean.
③@EnableAutoConfiguration
	@EnableAutoConfiguration是springboot实现自动化配置的核心注解,通过这个注解把spring应用所需的bean注入容器中.@EnableAutoConfiguration源码通过@Import注入了一个ImportSelector的实现类
	AutoConfigurationImportSelector,这个ImportSelector最终实现根据我们的配置,动态加载所需的bean.

2、maven中的 parent

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
 </parent>
<build>
       <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
</build>

这里查看源我们可以发现,它自动帮我们定义了一系列的引用的版本号。我们去引用其他依赖时,不需要定义额外的版本号(这里的依赖必须是org.springframework.boot中,否则还是要加版本号)

3、花里胡哨的banner

在resources下添加banner.txt

http://patorjk.com/software/taagopen in new window

http://www.network-science.de/ascii/open in new window

////////////////////////////////////////////////////////////////////
//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机      永无BUG                 //

禁用banner

@SpringBootApplication
public class TomcatApplication {
    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(TomcatApplication.class);
        SpringApplication build = builder.build();
        build.setBannerMode(Banner.Mode.OFF);
        build.run(args);
    }
}

4、单元测试添加

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <!--只会在测试时使用 不会被打包-->
            <scope>test</scope>
        </dependency>

test主文件

@RunWith(SpringRunner.class)
//启动类
@SpringBootTest(classes = JDBCTemplateApplication.class))
public class PropertyApplicationTests {
    @Test
    public void contextLoads(){
    }
}

5、自定义属性配置及加载properties

①@PropertySource("classpath:person.properties")

导入properties文件

②@ConfigurationProperties(prefix = "person")

定义前缀person

③@Value("${name:xxx}")

注意:在@Value注入属性的时候,该类必须注册到IOC中才能获取到该值

属性上加@value可不用定义该属性的get,set方法,否则要加get,set方法

定义实体对应的properties文件中的属性

定义默认值:xxx

配置实体,定义为组件:

@Component
@PropertySource("classpath:person.properties")
@ConfigurationProperties(prefix = "person")
public class Person {

    @Value("${name:xxx}")
    private String name;
    private Integer age;
    private String gender;
 	
    //getter...
  	//setter...
    //toString...
}

properties文件==>

person.name=张三
person.age=10
person.gender=

使用==>

注入即用:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PropertyApplicationTests {
    @Autowired
    private Person person;
    @Test
    public void contextLoads(){
        System.out.println(person);
    }
}

6、yaml/yml配置

注意:在yaml中,最好不要写大写的属性名称,大写前面加字母前加 - 后转小写

1、yaml是配置是有序的,properties是无序的

2、自定义的yaml目前暂不支持使用注解的方式注入到springboot项目中

3、注意每个属性的冒号: 后需要加一个空格

注入集合字符串,注入集合对象
server:
  port: 8081
  servlet:
    context-path: /lc

redis:
  port: 6379
#  注入集合字符串
  hosts:
    - 192.169.22.33
    - 192.169.22.34
    - 192.169.22.35
    - 192.169.22.36
    - 192.169.22.37
#  注入集合对象
  redisList:
  #一个 -组 代表一个对象
    - port: 6379
      host: 192.168.22.38
    - port: 6379
      host: 192.168.22.39
public class Redis {
    private Integer port;
    private String host;
    //getter...
  	//setter...
    //toString...
}
@Component
@ConfigurationProperties(prefix = "redis")
public class RedisCluster {
    private Integer port;
    private List<String> hosts;
    private List<Redis> redisList;
    //getter...
  	//setter...
    //toString...
}

7、多环境的yaml/properties配置

我们可以定义诸如:application-xxx.yaml

application-test.yaml

application-dev.yaml

application-prod.yaml

多种环境的配置,在application.yaml中激活即可:

spring.profiles.active=prod

会覆盖application.yaml中本身内容

8、访问static下的所有资源(html)

	<build>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
                <filtering>false</filtering>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>false</filtering>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>
		</resources>
	</build>

9、static目录和tmeplates目录的区别

  • static目录

static目录是用来保存静态文件的目录, 比如HTML, JS, CSS, 图片等, 是不需要服务器进行数据绑定的页面. static目录下的文件, SpringBoot帮我们做了静态路径自动解析. 比如: 我写了一个/static/hello.html 在浏览器上访问: localhost/hello.html 即可访问到.

  • tmeplates目录

template目录是用来保存动态模版文件的目录, 比如Freemarker, JSP, Thymeleaf等需要服务器动态渲染数据的文件. 由于页面渲染需要服务器中的数据, 所以该文件必须经过Controller控制器进行Model数据绑定后, 由服务器进行跳转. 所以直接访问是无意义的, 也访问不到.

比如: 我写了一个/templates/hello.ftl 在浏览器上访问: localhost/hello.ftl是访问不到的, 需要访问Controller的路径, 才能访问到页面 (由Controller进行跳转, 也就说明数据已经绑定成功.)

二、springboot整合视图层框架

1、整合Freemarker

依赖==>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
public class User {
    private String id;
    private String username;
    private String password;
    private String address;
    //getter
    //setter
    //toString
}
@Controller
public class UserController {
    @GetMapping("/user")
    public String user(Model model) {
        List<User> list = new ArrayList<>();
        for (int i = 1; i < 6; i++) {
            User user=new User();
            user.setId(i+"");
            user.setUsername("cl"+i);
            user.setPassword("123456");
            user.setAddress("pengx");
            list.add(user);
        }
        model.addAttribute("users", list);
        return "user";
    }
}

模板引擎==>

注意:此文件的后缀为 .ftlh

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户信息</title>
</head>
<body>
    <table border="1" cellspacing="0">
        <tr>
            <td>编号</td>
            <td>用户名</td>
            <td>密码</td>
            <td>地址</td>
        </tr>
        <#list users as u>
            <tr>
                <td>${u.id}</td>
                <td>${u.username}</td>
                <td>${u.password}</td>
                <td>${u.address}</td>
            </tr>
        </#list>
    </table>

</body>
</html>

yaml配置==>

spring:
  freemarker:
    #自定义模板文件的加载位置
    template-loader-path: classpath:/templates
    #是否开启缓存
    cache: false
    #上下文类型
    content-type: text/html

更多属性的更改参考 FreeMarkerProperties中的默认配置

public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";
    public static final String DEFAULT_PREFIX = "";
    public static final String DEFAULT_SUFFIX = ".ftlh";
    private Map<String, String> settings = new HashMap();
    private String[] templateLoaderPath = new String[]{"classpath:/templates/"};
    private boolean preferFileSystemAccess = true;
    //.....
    //.....
}

2、整合thymeleaf

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
@Controller
public class UserController {

    @GetMapping("/user")
    public String user(Model model) {
        List<User> list = new ArrayList<>();
        for (int i = 1; i < 6; i++) {
            User user=new User();
            user.setId(i+"");
            user.setUsername("cl"+i);
            user.setPassword("123456");
            user.setAddress("pengx");
            list.add(user);
        }
        model.addAttribute("users", list);
        return "User";
    }

}

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户信息</title>
</head>
<body>
    <table border="1" cellspacing="0">
        <tr>
            <td>编号</td>
            <td>用户名</td>
            <td>密码</td>
            <td>地址</td>
        </tr>
            <tr th:each="user :${users}">
                <td th:text="${user.id}"></td>
                <td th:text="${user.username}"></td>
                <td th:text="${user.password}"></td>
                <td th:text="${user.address}"></td>
            </tr>

    </table>
</body>
</html>

更多配置请看源码 ThymeleafProperties:

public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
    private boolean cache;
    private Integer templateResolverOrder;
    private String[] viewNames;
    private String[] excludedViewNames;
    private boolean enableSpringElCompiler;
    private boolean renderHiddenMarkersBeforeCheckboxes;
    private boolean enabled;
    private final ThymeleafProperties.Servlet servlet;
    private final ThymeleafProperties.Reactive reactive;
}

3、整合jsp

①pom.xml
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

②配置视图解析器 WebMvcConfigurer

在src/main/webapp/page/...... 增加webapp文件夹,并指定存放jsp的位置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/page/", ".jsp");
    }
}
③控制器
@Controller
public class UserController {
    @GetMapping("/user")
    public String user(Model model) {
        List<User> list = new ArrayList<>();
        for (int i = 1; i < 6; i++) {
            User user=new User();
            user.setId(i+"");
            user.setUsername("cl"+i);
            user.setPassword("123456");
            user.setAddress("pengx");
            list.add(user);
        }
        model.addAttribute("users", list);
        return "User";
    }
}
④视图
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <table border="1" cellspacing="0">
        <tr>
            <td>编号</td>
            <td>用户名</td>
            <td>密码</td>
            <td>地址</td>
        </tr>
        <c:forEach var="user" items="${users}">
            <tr>
                <td>${user.id}</td>
                <td>${user.username}</td>
                <td>${user.password}</td>
                <td>${user.address}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

三、springboot整合web开发

1、HttpMessageConverter

注:以下例子只是实体响应日期类型的格式转换

(1)功能

①将服务端返回的对象序列化成JOSN字符串

②将前端传来的JSON字符串反序列化成java对象

(2)Jackson和Gson自动化配置和手动配置

所有的JSON生成都离不开HttpMessageConverter

SpringMvc自动配置了JacksonGsonHttpMessageConverter, springboot对此做了自动化配置

①Jackson

spring-boot-starter-web中默认使用的是jsckson

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
方式一:

自动配置==>

如果我们没有配置MappingJackson2HttpMessageConverter,则springboot帮我们自动配置

org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration==>

		@Bean
        @ConditionalOnMissingBean(
            value = {MappingJackson2HttpMessageConverter.class},
            ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
        )
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
            return new MappingJackson2HttpMessageConverter(objectMapper);
        }

手动配置==>

如果我们配置了MappingJackson2HttpMessageConverter类,则自动配置失效

WebMvcConfig.java:

@Configuration
public class WebMvcConfig {
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper=new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converter.setObjectMapper(mapper);
        return converter;`
    }
}
方式二:

自动配置==>

org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration

		@Bean
        @Primary
        @ConditionalOnMissingBean
        ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
            return builder.createXmlMapper(false).build();
        }

手动配置==>

配置ObjectMapper使其自动配置失效

WebMvcConfig.java:

@Configuration
public class WebMvcConfig {
    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper mapper=new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        return mapper;
    }
}
②Gson

排除spring-boot-starter-web中原有的jackson。

新增gson依赖

方式一:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-json</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>

自动配置:==>

同理,如果我们没有配置HttpMessageConverter,则springboot帮我们自动配置

org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration==>

		@Bean
        @ConditionalOnMissingBean
        GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
            GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
            converter.setGson(gson);
            return converter;
        }

手动配置:==>

@Configuration
public class WebMvcConfig {
    @Bean
    GsonHttpMessageConverter gsonHttpMessageConverter() {
        GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
        converter.setGson(new GsonBuilder().setDateFormat("yyyy-MM-dd").create());
        return converter;
    }
}
方式二:

自动配置==>

org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration==>

	@Bean
    @ConditionalOnMissingBean
    public Gson gson(GsonBuilder gsonBuilder) {
        return gsonBuilder.create();
    }

手动配置==>

@Configuration
public class WebMvcConfig {
	@Bean
    Gson gson() {
        return new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
    }
}
(3)fastjson手动配置
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-json</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
@Configuration
public class WebMvcConfig {
    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
        FastJsonHttpMessageConverter converter=new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig=new FastJsonConfig();
        fastJsonConfig.setDateFormat("yyyy-MM-dd");
        converter.setFastJsonConfig(fastJsonConfig);
        return converter;
    }
}

2、springboot静态资源管理

(1)默认的自动配置

在源码中,我们可以看出,springboot帮我们自动做了一些配置

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurati==>

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }
                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

org.springframework.boot.autoconfigure.web.ResourceProperties==>

 private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

我们可以发现,springboot帮我们配置了一些静态资源的处理路径,按照顺序依次resources文件夹下查找静态资源文件

classpath:/META-INF/resources/"

classpath:/resources/

classpath:/static/

classpath:/public/

注意:在请求的时候我们就可以省略定义的文件夹名了

(2)自定义访问目录
①yaml配置
spring:
  resources:
  #静态资源的位置
    static-locations: classpath:/lc/
  mvc:
  #静态资源的请求规则
  #不配置规则(任意规则)
#    static-path-pattern: /**
  #请求静态资源时必须加 /hh/ 前缀再加文件夹或者文件路径
    static-path-pattern: /hh/**
②配置文件配置

addResourceHandler("/**") 静态资源请求规则

addResourceLocations("classpath:/lc/") 静态资源位置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/lc/");
    }
}

例如:--resources

​ --lc

​ --java

​ --hello.js

无访问规则:==>

访问路径为 http://localhost:8080/java/hello.jsopen in new window

有访问规则(/hh/**)==>

访问路径为 http://localhost:8080/hh/java/hello.jsopen in new window

3、springBoot实现文件上传

(1)form形式

在springboot中我们直接使用MultipartFile接口中的org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile实现类

自动配置==>

org.springframework.boot.autoconfigure.web.servlet.MultipartProperties==>

@ConfigurationProperties(
    prefix = "spring.servlet.multipart",
    ignoreUnknownFields = false
)
public class MultipartProperties {
    //是否开启上传
    private boolean enabled = true;
    //临时存储位置
    private String location;
    //上传大小限制
    private DataSize maxFileSize = DataSize.ofMegabytes(1L);
    //上传文件(多个)总大小
    private DataSize maxRequestSize = DataSize.ofMegabytes(10L);
    //文件上传的阈值(上传多少文件时,不能往内存中写,需要转换处理)
    private DataSize fileSizeThreshold = DataSize.ofBytes(0L);
    private boolean resolveLazily = false;
    //......
    //......
}
spring:
  servlet:
    multipart:
#      定义文件的上传大小限制 这里需要大写KB
      max-file-size: 1KB
①单文件
@RestController
public class FileUploadController {

    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/yyyy/MM/dd/");

    @PostMapping("/upload")
    public String hello(MultipartFile file, HttpServletRequest req) {
        String format = simpleDateFormat.format(new Date());
//        自定义:获取上传的文件的真正路径/img/yyyy/MM/dd/
        String realPath = req.getServletContext().getRealPath("/img") + format;
        File folder = new File(realPath);
//        若定义的文件夹不存在
        if (!folder.exists()) {
//            创建该文件夹
            folder.mkdirs();
        }
//        获得上传的文件名称
        String oldName = file.getOriginalFilename();
//        截图文件的后缀,并加上生成的uuid作为文件名
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
//            文件转换 上传到内存中
// 				重新定义其文件路径 及 文件名
            file.transferTo(new File(folder, newName));
//            返回路径:协议://服务器名称:端口/img//yyyy/MM/dd/+新的文件名
            String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName;
//            返回该url
            return url;
        } catch (IOException e) {
        }
        return "error";
    }
}

<!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>
②多文件

注意:这里的files为一个数组,为前端的

的name名称。

若有多个input标签,则只需要在后端请求参数上多加个MultipartFile file1,MultipartFile file2即可

    @PostMapping("/uploads")
    public String uploads(MultipartFile[] files, HttpServletRequest req) {
        String format = simpleDateFormat.format(new Date());
//        自定义:获取上传的文件的真正路径/img/yyyy/MM/dd/
        String realPath = req.getServletContext().getRealPath("/img") + format;
        File folder = new File(realPath);
//        若定义的文件夹不存在
        if (!folder.exists()) {
//            创建该文件夹
            folder.mkdirs();
        }
			//循环遍历存储
        for (MultipartFile file : files) {
//        获得上传的文件名称
            String oldName = file.getOriginalFilename();
//        截图文件的后缀,并加上生成的uuid作为文件名
            String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
            try {
//            文件转换 上传到内存中
                // 重新定义其文件路径 及 文件名
                file.transferTo(new File(folder, newName));
//            返回路径:协议://服务器名称:端口/img//yyyy/MM/dd/+新的文件名
                String url = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName;
//                输出该url
                System.out.println(url);
            } catch (IOException e) {
            }
        }
        return "success";
    }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/uploads" method="post" enctype="multipart/form-data">
        <input type="file" name="files" multiple/>
        <input type="submit" value="提交">
    </form>
</body>
</html>
(2)ajax形式
①单文件

后端和form单文件上传一致

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.4.1.js"></script>
</head>
<body>
    <div id="result"></div>
    <input type="file" id="file"/>
    <input type="button" value="提交" onclick="upload()">
    <script type="text/javascript">
        function upload() {
            //获取第一个文件 (这是个数组,可以多文件上传)
            var file = $("#file")[0].files[0];
            var formData = new FormData();
            formData.append("file", file);
            $.ajax({
                type: "post",
                url: "/upload",
                //使数据不做处理
                processData:false,
                //不要设置Content-Type请求头
                contentType:false,
                data:formData,
                success:function (msg) {
                    $("#result").text(msg)
                }
            })
        }
    </script>
</body>
</html>
②多文件

后端和form 多文件上传一致

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery-3.4.1.js"></script>
</head>
<body>
    <div id="result"></div>
    <input type="file" id="file1"/>
    <input type="file" id="file2"/>
    <input type="button" value="提交" onclick="upload()">
    <script type="text/javascript">
        function upload() {
            var formData = new FormData();
            //获取第一个文件 (这是个数组,可以多文件上传)
            var file1 = $("#file1")[0].files[0];
            var file2 = $("#file2")[0].files[0];
            formData.append("files", file1);
            formData.append("files", file2);
            $.ajax({
                type: "post",
                url: "/uploads",
                //使数据不做处理
                processData:false,
                //不要设置Content-Type请求头
                contentType:false,
                data:formData,
                success:function (msg) {
                    $("#result").text(msg)
                }
            })
        } 
    </script>
</body>
</html>

4、@ControllerAdvice的使用

(1)全局异常处理

@RestControllerAdvice ==>

@ControllerAdvice
@ResponseBody

@ExceptionHandler 指定拦截的异常类型

若有返回值,则直接返回定义的异常bean即可,加上**@RestControllerAdvice**

①无返回值
@ControllerAdvice
public class CustomExceptionHandler {
//        @ExceptionHandler拦截指定异常
//        这里我们可以返回任意值,或者不返回。
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public void uploadFileException(MaxUploadSizeExceededException e, HttpServletResponse res) throws IOException {
        res.setContentType("text/html;charset=utf-8");
        PrintWriter printWriter = res.getWriter();
        printWriter.write("文件大小超出限制!");
        printWriter.flush();
        printWriter.close();
    }
}
②返回视图

这里我们用的thymeleaf

@ControllerAdvice
public class CustomExceptionHandler {
    //    定义返回的视图
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ModelAndView uploadFileException(MaxUploadSizeExceededException e){
        //新建视图并设定名称
        ModelAndView modelAndView =new ModelAndView("exception");
        modelAndView.addObject("error", "文件大小超出限制!");
        return modelAndView;
    }
}
③常用形式 返回自定义bean
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
//    处理SQLException该异常的方法
    @ExceptionHandler(SQLException.class)
    public ResponseBean sqlException(SQLException e){
        //判断是否为其类和及其子类
        if(e instanceof MySQLIntegrityConstraintViolationException){
            return ResponseBean.error("该数据有关联的数据,操作失败!");
        }
        logger.error(e.getMessage());
        return ResponseBean.error("数据库操作异常,操作失败!");
    }

}
(2)预设全局数据
@ControllerAdvice
public class GlobalData {
    @ModelAttribute(value = "info")
    public Map<String, Object> mydata() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "louchen");
        map.put("address", "www.louchen.top");
        return map;
    }
}
@RestController
public class UserController {
    @GetMapping("/hello")
    public String hello(Model model) {
        //转为map集合
        Map<String,Object> map = model.asMap();
//        获取所有的key
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            System.out.println("key:"+map.get(key));
        }
        return "success";
    }
}

这里的map集合中===>

key为 @ModelAttribute(value = "info")中的value值

value为定义在map中的所有值

key:{address=www.louchen.top, name=louchen}
(3)请求参数预处理

当遇到参数需要处理的时候,例如多个对象的重复属性等其他情况

public class Cat {
    private String id;
    private String name;
    private Integer age;
    //getter
    //setter
    //toString
}
public class Doctor {
    private String id;
    private String name;
    private Integer age;
}
@ControllerAdvice
public class InitData {

    @InitBinder("doctor")
    public void initDoctor(WebDataBinder binder){
        //参数请求的时候的需要加的前缀
        binder.setFieldDefaultPrefix("doc.");
    }

    @InitBinder("cat")
    public void initCat(WebDataBinder binder){
        //参数请求的时候的需要加的前缀
        binder.setFieldDefaultPrefix("cat.");
    }

}
@RestController
public class InitDataController {
    @PostMapping("/add")
    //这里的@ModelAttribute中为@InitBinder中绑定的值
    public void addDocAndCat(@ModelAttribute("doctor")Doctor doctor, @ModelAttribute("cat")Cat cat){
        System.out.println(doctor.toString());
        System.out.println(cat.toString());
    }
}

请求参数:post: http://localhost:8080/add

doc.idopen in new window ==>

doc.nameopen in new window ==>

doc.age ==>

cat.idopen in new window ==>

cat.nameopen in new window ==>

cat.age ==>

5、自定义错误页面

(1)错误页面模板加载顺序

在springboot中,程序发生错误的时候会自动寻找自动配置的错误页面

--resources

--static

--error ===>404.html,4xx.html,500.html,5xx.html

--templates

--error ===>404.html,4xx.html,500.html,5xx.html

在此目录下:

按照全匹配的优先规则依次从动态页面templates/error下寻找,再从静态页面static/error下寻找

在按照模糊匹配的优先规则依次从动态页面templates/error下寻找,再从静态页面static/error下寻找

(2)自定义异常数据

自动配置==>

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration==>

	@Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    }

手动配置==>

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
        errorAttributes.put("myerror", "自定义的异常信息");
        return errorAttributes;
    }
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1" cellspacing="0">
    <tr>
        <td>path</td>
        <td th:text="${path}"></td>
    </tr>
    <tr>
        <td>timestamp</td>
        <td th:text="${timestamp}"></td>
    </tr>
    <tr>
        <td>message</td>
        <td th:text="${message}"></td>
    </tr>
    <tr>
        <td>error</td>
        <td th:text="${error}"></td>
    </tr>
    <tr>
        <td>path</td>
        <td th:text="${path}"></td>
    </tr>
    <tr>
        <td>status</td>
        <td th:text="${status}"></td>
    </tr>
    <tr>
        <td>myerror</td>
        <td th:text="${myerror}"></td>
    </tr>
</table>
</body>
</html>
path/111
timestampMon Mar 16 21:31:50 GMT+08:00 2020
messageNo message available
errorNot Found
path/111
status404
myerror自定义的异常信息
(3)自定义异常视图

自动配置==>

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration==>

@Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean({ErrorViewResolver.class})
        DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }

手动配置==>

**注意:**这里的视图名称在templates文件夹下的任意目录即可

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {

    public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
        super(applicationContext, resourceProperties);
    }
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = super.resolveErrorView(request, status, model);
        //视图的名称
        modelAndView.setViewName("lc.html");
        modelAndView.addObject(model);
//        该Map<String, Object> model不可修改。
//        如果我们新增其他的属性 我们可以遍历此map数据取出,然后定义新的map放进ModelAndView中
        return modelAndView;
    }
}

视图模板内容和上面的一致

6、springboot跨域问题

https://segmentfault.com/a/1190000019550329?utm_source=tag-newestopen in new window

(1)背景

​ Same Origin Policy,译为“同源策略”。它是对于客户端脚本(尤其是JavaScript)的重要安全度量标准,其目的在于防止某个文档或者脚本从多个不同“origin”(源)装载。 它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。 ​ 注:具有相同的Origin,也即是拥有相同的协议、主机地址以及端口。一旦这三项数据中有一项不同,那么该资源就将被认为是从不同的Origin得来的,进而不被允许访问。

(2)CORS简介

​ CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 CORS需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX/Fetch通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 ​ 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

简答请求(HEAD、GET、POST):不需要试探请求

非简单请求(DELETE,PUT)Request Method: OPTIONS 需要进行一次试探请求。在正式通信之前,增加一次OPTIONS查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

详解响应头:

Access-Control-Allow-Origin

该字段必填。它的值要么是请求时Origin字段的具体值,要么是一个*,表示接受任意域名的请求。

Access-Control-Allow-Methods 该字段必填。它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

Access-Control-Expose-Headers 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

Access-Control-Allow-Credentials 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。

顺便提一下,如果在开发中,发现每次发起请求都是两条,一次OPTIONS,一次正常请求,注意是每次,那么就需要配置Access-Control-Max-Age,避免每次都发出预检请求。

(3)实现跨域的方法
①最小粒度的cors控制,精确到单个请求级别。

@CrossOrigin("*") 允许所有请求

@CrossOrigin("http:localhost:8080") 只允许http:localhost:8080

@RestController
public class CORSController {
    @CrossOrigin("*")
    @GetMapping("/doget")
    public String doGet(){
        return "doGet";
    }

}

从@CrossOrigin注解中==>

该注解可以用于方法上和类上,用于类上,所有该类下的请求都支持该跨域请求。用于方法上,只有该请求支持跨域

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
	//....
}
②全局配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
//        哪些请求能够跨域(所有)
        registry.addMapping("/**")
//                哪些域能够访问(所有)
                .allowedOrigins("*")
//                哪些请求头(所有)
                .allowedHeaders("*")
//                允许的请求方法类型(所有)
                .allowedMethods("*")
//                会首先发送一个探测请求(若探测许可,即发送真正的请求,否则返回请求失败)
//                设定发送探测请求的时间间隔(30s,单位秒)
                .maxAge(30);
    }
}

@RestController
public class CORSController {
    @GetMapping("/doget")
    public String doGet(){
        return "doGet";
    }
    @PutMapping("/dopost")
    public String doPut(){
        return "doPut";
    }
}

另一个域的请求视图==>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS</title>
    <script src="jquery-3.4.1.js"></script>
</head>
<body>
    <div id="result"></div>
    <input type="button" value="GET" onclick="getData()">
    <input type="button" value="PUT" onclick="putData()">
    <script>
        function getData() {
            $.get('http://localhost:8081/doget',function (msg) {
                $("#result").text(msg);
            })
        }
        function putData() {
            $.ajax({
              type:'PUT',
              url:'http://localhost:8081/dopost',
              success:function (msg) {
                  $("#result").text(msg);
              }
            })
        }
    </script>
</body>
</html>d
③过滤器形式配置
@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin","*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        chain.doFilter(req, res);
    }
}

7、springboot加载xml配置文件

resources==>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.lc.xml.controller.HelloController" id="sayHello"></bean>

</beans>
@Configuration
//加载bean.xml配置文件,注入到spring容器中
@ImportResource(locations = "classpath:beans.xml")
public class WebMvcConfig {
}

public class HelloController {
    public String sayHello() {
        return "say hello";
    }
}

Test==>

@RunWith(SpringRunner.class)
@SpringBootTest
public class XmlTest {
    @Autowired
    private HelloController helloController;
    @Test
    public void Test1() {
        String s = helloController.sayHello();
        System.out.println(s);
    }
}

9、springboot中使用拦截器

定义拦截执行的方法(内容)==>

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行之前");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行后");
    }
}

注入容器中,并添加新的拦截器==>

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器,拦截所有请求
        registry.addInterceptor(myInterceptor()).addPathPatterns("/**");
    }
    @Bean
    MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }

}

使用更多配置==>

org.springframework.web.servlet.config.annotation.InterceptorRegistration==>

public class InterceptorRegistration {
    private final HandlerInterceptor interceptor;
    private final List<String> includePatterns = new ArrayList();
    private final List<String> excludePatterns = new ArrayList();
    @Nullable
    private PathMatcher pathMatcher;
    private int order = 0;
    //......
    //......
}

10、Springboot实现系统启动任务

注意:两种接口可混用,不冲突

(1)实现CommandLineRunner接口

@order(100) 的使用:

默认为最大值2^31-1,即int的范围。

默认最大值表示优先级最低,即执行的顺序

值越大优先级越低,反之值越小优先级越高

@Component
@Order(100)
public class MyCommondLineRunnerOne implements CommandLineRunner {

    /**
     * 获得启动程序的初始参数
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommondLineRunnerOne==>>"+Arrays.toString(args));
    }
}

@Component
@Order(99)
public class MyCommondLineRunnerTwo implements CommandLineRunner {
    /**
     * 获得启动程序的初始参数
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommondLineRunnerTwo==>>"+Arrays.toString(args));
    }

}
添加启动参数:

①在主程序中,写死即可

@SpringBootApplication
public class XmlApplication {
    public static void main(String[] args) {
        args=new String[]{
          "org.lc","louchen"
        };
        SpringApplication.run(XmlApplication.class,args);
    }
}

==>

MyCommondLineRunnerTwo==>>[org.lc, louchen]
MyCommondLineRunnerOne==>>[org.lc, louchen]

②在idea启动的时候配置参数

Edit Configurations =>Environment=>Program agruments

添加参数即可: aaa bbb

==>

MyCommondLineRunnerTwo==>>[aaa, bbb]
MyCommondLineRunnerOne==>>[aaa, bbb]

③以jar包的方式加入启动参数

注意参数之间的空格

java -jar xml-1.0-SNAPSHOT.jar ccc ddd

==>

MyCommondLineRunnerTwo==>>[ccc, ddd]
MyCommondLineRunnerOne==>>[ccc, ddd]
(2)实现ApplicationRunner接口
@Component
@Order(102)
public class MyApplicationRunnerOne implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //获取所有的值
        String[] sourceArgs = args.getSourceArgs();
        System.out.println(Arrays.toString(sourceArgs));
        //获取所有没有键的数据
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println(nonOptionArgs);
        //获取所有的键值对
        Set<String> optionNames = args.getOptionNames();
        //通过键获取值
        //args.getOptionValues(optionName)
        for (String optionName : optionNames) {
            System.out.println(optionName+":"+args.getOptionValues(optionName));
        }
        System.out.println("-------------------MyApplicationRunnerOne------------------------------");
    }
}

@Component
@Order(101)
public class MyApplicationRunnerTwo implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //获取所有的值
        String[] sourceArgs = args.getSourceArgs();
        System.out.println(Arrays.toString(sourceArgs));
        //获取所有没有键的数据
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println(nonOptionArgs);
        //获取所有的键值对
        Set<String> optionNames = args.getOptionNames();
        //通过键获取值
        //args.getOptionValues(optionName)
        for (String optionName : optionNames) {
            System.out.println(optionName+":"+args.getOptionValues(optionName));
        }
        System.out.println("-------------------MyApplicationRunnerTwo------------------------------");
    }
}

添加启动参数:

这里我们只采用jar的形式添加参数,其他的方式和上面类似

注意:键值对的参数要 在前面加两个 -

java -jar xml-1.0-SNAPSHOT.jar --name=louchen --address=hubei 张三 李四

==>

[--name=louchen, --address=hubei, 张三, 李四]
[张三, 李四]
address:[hubei]
name:[louchen]
-------------------MyApplicationRunnerTwo------------------------------
[--name=louchen, --address=hubei, 张三, 李四]
[张三, 李四]
address:[hubei]
name:[louchen]
-------------------MyApplicationRunnerOne------------------------------

11、springboot中的restful请求类型

https://www.jianshu.com/p/4981911d5e15open in new window

@RequestBody与@RequestParam作用与GET,POST,DELETE,PUT请求上不同

加上@RequestBody的参数注解必须以application/json形式传输参数

一个请求体中的所以数据只在被@RequestBody标记中的有效

加上@RequestParam的参数注解必须以url的形式传输参数

(1)GET请求(默认url传参)
①无@RequestBody

因为GET请求无请求体,所有传输的参数必须以url形式传输参数

请求形式localhost:8080/hello/?id=1&name=张三&uid=1001

@RestController
public class UserController {
    @GetMapping("/hello")
    public User sayGet(User user,String uid) {
        System.out.println(user);
        System.out.println("uid:"+uid);
        return user;
	}
}
②有@RequestBody

没有声明在@RequestBody中的参数必须以url形式传参

声明在@RequestBody中的参数必须以请求体的形式传输

请求形式content-type:application/json

localhost:8080/hello/?uid=1001

{
	"id":2,
	"name":"李四"
}
@GetMapping("/hello")
    public User sayGet(@RequestBody User user,@RequestParam("uid") String uid) {
        System.out.println(user);
        System.out.println("uid:"+uid);
        return user;
    }
}
(2)POST请求(默认x-www-form-urlencoded)
①有@RequestBody

被@RequestBody标记必须以json的形式传参

没有被@RequestBody标记必须以url的形式传参

请求形式content-type:application/json

localhost:8080/hello/?uid=222

{
	"id":2,
	"name":"李四"
	
}
 @PostMapping("/hello")
    public User sayPost(@RequestBody User user,@RequestParam("uid") String uid){
        System.out.println(user);
        System.out.println("uid:"+uid);
        return user;
    }
}
②无@RequestBody

默认类型content-type:x-www-form-urlencoded

这种类型只是将参数编码放在url地址栏中

所以请求参数放在url中也可以,当是编码content-type:x-www-form-urlencoded过后再放更合理

keyvalue
id3
name王五
uid1000

请求参数::localhost:8080/hello

@PostMapping("/hello")
    public User sayPost(User user, @RequestParam("uid")String uid){
        System.out.println(user);
        System.out.println("uid:"+uid);
        return user;
    }
}
(3)PUT请求(默认x-www-form-urlencoded)
①有@RequestBody

请求类型content-type:application/json

和POST请求方式一致,被标记的参数只能以application/json参数传递,非标记的以url形式传输参数

②无@RequestBody

请求类型content-type:x-www-form-urlencoded

与POST请求方式一致,只能以x-www-form-urlencoded形式传参。

所以参数编码后放入url地址栏中。

(4)DELETE请求(默认x-www-form-urlencoded)
①有@RequestBody

请求类型content-type:application/json

和POST请求方式一致,被标记的参数只能以application/json参数传递,非标记的以url形式传输参数

②无@RequestBody

请求类型content-type:x-www-form-urlencoded

与POST请求方式一致,只能以x-www-form-urlencoded形式传参。

所以参数编码后放入url地址栏中。

(5)总结
①从content-type方面:

form-data、x-www-form-urlencoded:不可以用@RequestBody。可以用@RequestParam。这种请求类型只是将参数编码放入url地址栏中

application/json:json字符串部分可以用@RequestBody;url中的?后面参数可以用@RequestParam。

②从注解方面

@RequestBody

(@RequestBody Map map)
(@RequestBody Object object)
application/json时候可用
form-data、x-www-form-urlencoded时候不可用

@RequestParam

(@RequestParam Map map)
application/json时候,json字符串部分不可用,url中的?后面添加参数即可用
(@RequestParam Object object)
不管application/json、form-data、x-www-form-urlencoded都不可用
(6)通过Delete传输数组参数
@DeleteMapping("/user1")
public void deleteUser1(@RequestParam("ids") Integer[] ids){
    System.out.println(Arrays.toString(ids));
}

传递数组参数只能通过在url参数上拼接才能传输,不能通过json形式,即@RequestBody形式传输

例如:http://localhost:9001?ids=1,2,3open in new window 或者 http://localhost:9001?ids=1&ids=2&ids=3open in new window

12、springboot整合servlet,filter,lister(web基础组件)

定义servlet==>

//  映射/myservlet请求
@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("myServlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

定义过滤器==>

//过滤拦截所有请求
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("myFilter");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

定义监听器==>

//标记此类为监听器
//根据监听器的不同类型实现不同的监听器的接口
//实现请求的监听器
@WebListener
public class MyRequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("requestListenerDestroyed");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("requestListenerInitialized");
    }
}

扫描servlet,listener,filter

@ServletComponentScan(basePackages = "org.lc.xml.servlet")

扫描指定包下servlet组件

@SpringBootApplication
//扫描所有的servlet,filter,listener
@ServletComponentScan(basePackages = "org.lc.xml.servlet")
public class XmlApplication {
    public static void main(String[] args) {
        SpringApplication.run(XmlApplication.class,args);
    }
}

请求路径http://localhost:8080/myservlet

输出顺序:

ServletRequestListener监听器请求初始化=>

过滤器执行==>

Servlet执行=>

ServletRequestListener监听器请求销毁

requestListenerInitialized
myFilter
myServlet
requestListenerDestroyed

当有springboot中的拦截器时:

注意:这里的拦截器只对controller中的请求有效,对servlet中定义的请求无效

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行之前");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行后");
    }
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor()).addPathPatterns("/**");
    }

    @Bean
    MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }
}

srtvlet请求:http://localhost:8080/myservlet

requestListenerInitialized
myFilter
myServlet
requestListenerDestroyed

controller请求http://localhost:8080/say

请求结果==>

requestListenerInitialized
myFilter
执行之前
执行中
执行后
requestListenerDestroyed
@RestController
public class UserController {

    @GetMapping("/say")
    public String sayHello(){
        return "say hello";
    }
}

13、springboot中的路径映射

当我们只需要返回一个页面时,通常做法,使用控制器返回一个页面(这里使用thmeleaf模板引擎)

@Controller
public class LoginController {
    @GetMapping("/login")
    public String loginView() {
        return "login";
    }
}

这里我们摒弃上面的这个做法:

只需要实现WebMvcConfigurer接口即可

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //添加请求的路径  响应的视图名称
        registry.addViewController("/login").setViewName("login");
         registry.addViewController("/register").setViewName("register");
    }
}

14、springboot中使用类型转换器

其中S代表需要转换的参数类型

T代表要转成的目标参数类型

@FunctionalInterface
public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}

实现转换器接口==>

@Component
public class DateConventer implements Converter<String, Date> {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    @Override
    public Date convert(String s) {
        if (s != null && !s.equals("")) {
            try {
                return simpleDateFormat.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
@RestController
public class LoginController {
    @GetMapping("/birthday")
    public void sayHello(Date date) {
        System.out.println(date);
    }
}

请求http://localhost:8080/birthday/?date=2019-01-01

15、springboot使用AOP

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
@Service
public class EmpService {
    public String getAllEmp(){
        System.out.println("getAllEmp--service");
        return "查询所有成功";
    }
    public String deleteEmpById(Integer id){
        System.out.println("deleteEmpById--service");
        return "修改成功";
    }
}
@RestController
public class EmpController {
    @Autowired
    private EmpService empService;
    @GetMapping("/allEmp")
    public String getAllEmp(){
        String allEmp = empService.getAllEmp();
        System.out.println(allEmp);
        return "getAllEmp--controller";
    }
    @DeleteMapping("/delEmp")
    public String deleteEmp(){
        String s = empService.deleteEmpById(12);
        System.out.println(s);
        return "deleteEmp--controller";
    }
}

定义切面文件==>

@Component
//定义类为切面(切点和通知的结合)
@Aspect
public class AopLogger {

    /**
     * 定义切点表达式 用来确定哪些类需要代理
     * 切点表达式定义:==>
     * ①方法的可见性  如public,protected;(可省略) (不可用*表示,但是可省略)
     * ②方法的返回类型 如int,void等;(*代表任意类型)
     * ③方法所在类的全包名,如com.spring.Aspect;
     * ④所在类的全名;(*代表该包下的所有类)
     * ⑤所在类的方法全名 (*代表该类下的所有方法)
     * ⑥方法的个数 (..代表0个或多个)
     */
    @Pointcut("execution(public * org.lc.xml.service.*.*(..))")
    public void pc1(){

    }

//    定义通知
//    即对拦截的方法的处理方式(时机)
    /**
     * 定义前置通知(代理的方法执行之前的通知)
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息,
     *  在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
     */
    @Before("pc1()")
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("前置通知执行的方法:"+name);
    }

    /**
     * 定义后置通知(代理方法执行之后执行的通知)
     * @param joinPoint
     */
    @After("pc1()")
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知执行的方法:"+name);
    }

    /**
     * 定义返回通知(方法执行完毕执行的通知)
     * @param joinPoint
     * @param result 该代理的方法的返回值
     *       在注解上的returning = "result"则只会匹配只有返回值的方法
     */
    @AfterReturning(value = "pc1()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("返回通知执行的方法:"+name+",该代理方法的返回值为:"+result);
    }

    /**
     * 定义异常通知(方法执行异常之后的通知)
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "pc1()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        String name = joinPoint.getSignature().getName();
        System.out.println("异常通知执行的方法:"+name+",该方法的异常为:"+e.getMessage());
    }

    /**
     * 定义环绕通知 (可同时使用上面的四种通知)
     * proceedingJoinPoint.proceed();代表执行代理的该方法
     */
    @Around(value = "pc1()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //调用该方法之前的处理的代码(相当于前置通知)
        System.out.println("------前置通知-around");
        Object proceed = null;
        try {
            proceed = proceedingJoinPoint.proceed();
           //注意这里的后置通知只是在环绕通知中代理方法的返回之前提前处理了
            // 实际在@After是在@AfterReturning之前执行的
            //使用该proceed变量,对其进行处理的代码。(相当于返回通知)
            System.out.println("-------返回通知-around");
        } catch (Throwable throwable) {
            //对异常处理的代码(相当于异常通知)
            System.out.println("------异常通知-around");
            throwable.printStackTrace();
        }
//        注意:这里的后置通知只是在环绕通知中代理方法的返回之前提前处理了
//        实际在@After是在@AfterReturning之前执行的
        //调用该方法之后之后处理的代码(相当于后置通知)
        System.out.println("-------后置通知---around");
        
        //该返回语句执新之前的所有代码 都是未正真正执行的。
        return proceed;
    }
}

执行的结果为==>

------前置通知-around
前置通知执行的方法:getAllEmp
getAllEmp--service
-------返回通知-around
-------后置通知---around
后置通知执行的方法:getAllEmp
返回通知执行的方法:getAllEmp,该代理方法的返回值为:查询所有成功
查询所有成功

总结执行的顺序==>

@Around先进入环绕通知==>
处理环绕通知proceed = proceedingJoinPoint.proceed()方法之前的代码(相当于前置通知)

@After进入前置通知

@Around处进入环绕通知proceed = proceedingJoinPoint.proceed()方法之后的代码(相当于后置通知和返回通知)

若出现异常则直接执行@Aroundcatch的代码(相当于异常通知)

@After进入后置通知

@AfterReturning进入返回通知

16、springboot自定义欢迎页

这里我们使用thmeleaf

@Controller
public class IndexController {
    @GetMapping("/index")
    public String index() {
        return "index";
    }
}

默认启动服务器:localhost:8080

首先访问static中的index.html页面

再访问templates中的index.html页面

17、springboot自定义favicon.ico

https://tool.lu/favicon/open in new window

将favicon.ico放在resources下或static下即可

static下的优先级高于resources

当出现无法访问图标的时候:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //放在resources下
        registry.addResourceHandler("/favicon.ico").addResourceLocations("classpath:/");
        //放在static下
  //registry.addResourceHandler("/favicon.ico").addResourceLocations("classpath:/static/");
    }
}

18、springboot排除自动配置

排除不需要的自动配置(所有自动配置全部失效)

@SpringBootApplication(exclude = WebMvcAutoConfiguration.class)
public class XmlApplication {
    public static void main(String[] args) {
        SpringApplication.run(XmlApplication.class,args);
    }
}

或者==>

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration