正文
五. 跨域问题的解决方案
关于跨域问题,前后端的解决方案还是挺多的,这里我重点说说spring的解决方案,目前有三种:
一.使用@CrossOrigin注解
@RequestMapping("/user") @RestController public class UserController { @CrossOrigin(origins = "http://localhost:8016") @RequestMapping("/getUser") public String getUser(@RequestParam("name") String name) { System.out.println("name:" + name); return "success"; } }
该方案需要在跨域访问的接口上加@CrossOrigin
注解,访问规则可以通过注解中的参数控制,控制粒度更细。如果需要跨域访问的接口数量较少,可以使用该方案。
二.增加全局配置
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); } }
该方案需要实现WebMvcConfigurer
接口,重写addCorsMappings
方法,在该方法中定义跨域访问的规则。这是一个全局的配置,可以应用于所有接口。
三.自定义过滤器
@WebFilter("corsFilter") @Configuration public class CorsFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET"); httpServletResponse.setHeader("Access-Control-Max-Age", "3600"); httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with"); chain.doFilter(request, response); } @Override public void destroy() { } }
该方案通过在请求的header
中增加Access-Control-Allow-Origin
等参数解决跨域问题。
顺便说一下,使用@CrossOrigin
注解 和 实现WebMvcConfigurer
接口的方案,spring在底层最终都会调用到DefaultCorsProcessor
类的handleInternal
方法:
最终三种方案殊途同归,都会往header
中添加跨域需要参数,只是实现形式不一样而已。
六. 如何自定义starter
以前在没有使用starter
时,我们在项目中需要引入新功能,步骤一般是这样的:
- 在maven仓库找该功能所需jar包
- 在maven仓库找该jar所依赖的其他jar包
- 配置新功能所需参数
以上这种方式会带来三个问题:
- 如果依赖包较多,找起来很麻烦,容易找错,而且要花很多时间。
- 各依赖包之间可能会存在版本兼容性问题,项目引入这些jar包后,可能没法正常启动。
- 如果有些参数没有配好,启动服务也会报错,没有默认配置。
「为了解决这些问题,springboot的starter
机制应运而生」。
starter机制带来这些好处:
- 它能启动相应的默认配置。
- 它能够管理所需依赖,摆脱了需要到处找依赖 和 兼容性问题的困扰。
- 自动发现机制,将spring.factories文件中配置的类,自动注入到spring容器中。
- 遵循“约定大于配置”的理念。
在业务工程中只需引入starter包,就能使用它的功能,太爽了。
下面用一张图,总结starter的几个要素:
接下来我们一起实战,定义一个自己的starter。
第一步,创建id-generate-starter工程:
其中的pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <version>1.3.1</version> <groupId>com.sue</groupId> <artifactId>id-generate-spring-boot-starter</artifactId> <name>id-generate-spring-boot-starter</name> <dependencies> <dependency> <groupId>com.sue</groupId> <artifactId>id-generate-spring-boot-autoconfigure</artifactId> <version>1.3.1</version> </dependency> </dependencies> </project>
第二步,创建id-generate-spring-boot-autoconfigure工程:
该项目当中包含:
- pom.xml
- spring.factories
- IdGenerateAutoConfiguration
- IdGenerateService
- IdProperties pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <modelVersion>4.0.0</modelVersion> <version>1.3.1</version> <groupId>com.sue</groupId> <artifactId>id-generate-spring-boot-autoconfigure</artifactId> <name>id-generate-spring-boot-autoconfigure</name> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
spring.factories配置如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sue.IdGenerateAutoConfiguration
IdGenerateAutoConfiguration类:
@ConditionalOnClass(IdProperties.class) @EnableConfigurationProperties(IdProperties.class) @Configuration public class IdGenerateAutoConfiguration { @Autowired private IdProperties properties; @Bean public IdGenerateService idGenerateService() { return new IdGenerateService(properties.getWorkId()); } }
IdGenerateService类:
public class IdGenerateService { private Long workId; public IdGenerateService(Long workId) { this.workId = workId; } public Long generate() { return new Random().nextInt(100) + this.workId; } }
IdProperties类:
@ConfigurationProperties(prefix = IdProperties.PREFIX) public class IdProperties { public static final String PREFIX = "sue"; private Long workId; public Long getWorkId() { return workId; } public void setWorkId(Long workId) { this.workId = workId; } }
这样在业务项目中引入相关依赖:
<dependency> <groupId>com.sue</groupId> <artifactId>id-generate-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency>
就能使用注入使用IdGenerateService的功能了
@Autowired private IdGenerateService idGenerateService;
完美。
七.项目启动时的附加功能
有时候我们需要在项目启动时定制化一些附加功能,比如:加载一些系统参数、完成初始化、预热本地缓存等,该怎么办呢?
好消息是springboot
提供了:
- CommandLineRunner
- ApplicationRunner
这两个接口帮助我们实现以上需求。
它们的用法还是挺简单的,以ApplicationRunner
接口为例:
@Component public class TestRunner implements ApplicationRunner { @Autowired private LoadDataService loadDataService; public void run(ApplicationArguments args) throws Exception { loadDataService.load(); } }
实现ApplicationRunner
接口,重写run
方法,在该方法中实现自己定制化需求。
如果项目中有多个类实现了ApplicationRunner
接口,他们的执行顺序要怎么指定呢?
答案是使用@Order(n)
注解,n的值越小越先执行。当然也可以通过@Priority
注解指定顺序。
springboot项目启动时主要流程是这样的:
在SpringApplication
类的callRunners
方法中,我们能看到这两个接口的具体调用:
最后还有一个问题:这两个接口有什么区别?
- CommandLineRunner接口中run方法的参数为String数组
- ApplicationRunner中run方法的参数为ApplicationArguments,该参数包含了String数组参数 和 一些可选参数。
唠唠家常
写着写着又有这么多字了,按照惯例,为了避免篇幅过长,今天就先写到这里。预告一下,后面会有AOP、BeanPostProcessor、Configuration注解等核心知识点的专题,每个主题的内容都挺多的,可以期待一下喔。