解决跨域的5种方法
什么时跨域
什么是跨域 浏览器从⼀个域名的⽹⻚去请求另⼀个域名的资源时,协议、域名(主机)、端⼝任⼀不同,都是跨域。在前后端分离的模式下,前后端的域名是不⼀致的,此时就会发⽣跨域访问问题。跨域出于浏览器的同源策略限制。
对于 CORS的跨域请求,主要有以下⼏种⽅式可供选择:
返回新的CorsFilter
重写 WebMvcConfigurer
使⽤注解 @CrossOrigin
⼿动设置响应头 (HttpServletResponse)
⾃定web filter 实现跨域
方法1 返回新的 CorsFilter
在任意配置类,返回⼀个新的 CorsFilter 的对象 ,并添加映射路径和具体的 CORS 配置路径
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放⾏哪些原始域
config.addAllowedOrigin("*");
//放⾏哪些请求⽅式
config.addAllowedMethod("*");
//放⾏哪些原始请求头部信息
config.addAllowedHeader("*");
// 添加最⼤存活时间 单位:秒
config.setMaxAge(1800L);
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new
UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
方法2 重写 WebMvcConfigurer
@Configuration
public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/*/**")
.allowedHeaders("*")
.allowedMethods("*")
.maxAge(1800L)
.allowedOrigins("*");
}
}
方法3 使⽤注解 @CrossOrigin
这个注解可以⽤于两个地⽅:
类:表示该类的所有⽅法允许跨域。
⽅法:表示当前⽅法允许跨域
@RestController
@CrossOrigin(origins = "*")
public class DailyblueController {
@RequestMapping("/a")
public String a() {
return "This is DailyblueController`a method!";
}
@RequestMapping("/b")
@CrossOrigin(origins = "http://localhost:8081") //指定具体ip允许跨域
public String b() {
return "This is DailyblueController`b method!";
}
@RequestMapping("/c")
public String c() {
return "This is DailyblueController`c method!";
}
}
其中 @CrossOrigin 中的2个参数:
origins:允许可访问的域列表。 maxAge:准备响应前的缓存持续的最⼤时间(以秒为单位)
方法4 ⼿动设置响应头
使⽤ HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这⾥ Origin 的值也可以 设置为 “*”,表示全部放⾏。
@RequestMapping("/index")
public String index(HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin","*");
return "index";
}
方法5 使⽤⾃定义 filter 实现跨域
可以⾃定义⼀个多滤器,对请求的数据头进⾏过来,允许其他域请求访问:
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*") // 过滤器解决跨域
public class HttpFilter1 extends javax.servlet.http.HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
chain.doFilter(request, response);
}
}
此时必须让启动项扫描到过滤器
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
//@ServletComponentScan("com.jiazhong.bookstore.filter")
@EnableCaching
public class BookStoreApplication {
public static void main(String[] args) {
SpringApplication.run(BookStoreApplication.class, args);
}
}
在项目中应用到 Swagger
第一步:导入依赖包
<!--测试用的-->
<!--添加Knife4j依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
第二步 设置配置类,进行配置
Knife4jConfig .java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
// http://localhost:8080/doc.html
public class Knife4jConfig {//对于配置类要求可以看懂即可,不用反复去写,将来可以CV
//配置Swagger2的Docket的Bean实例
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// apiInfo():配置 API 的一些基本信息,比如:文档标题title,文档描述description,文档版本号version
.apiInfo(apiInfo())
// select():生成 API 文档的选择器,用于指定要生成哪些 API 文档
.select()
// apis():指定要生成哪个包下的 API 文档
.apis(RequestHandlerSelectors.basePackage("com.jiazhong.controller"))
// paths():指定要生成哪个 URL 匹配模式下的 API 文档。这里使用 PathSelectors.any(),表示生成所有的 API 文档。
.paths(PathSelectors.any())
.build();
}
//文档信息配置
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 文档标题
.title("书城开发接口文档")
// 文档描述信息
.description("书城开发接口文档")
// 文档版本号
.version("1.0")
.build();
}
}
第三步 常用注解
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
注解 | 说明 |
---|---|
@Api | 用在类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,例如entity、DTO、VO |
@ApiModelProperty | 用在属性上,描述属性信息 |
@ApiOperation | 用在方法上,例如Controller的方法,说明方法的用途、作用 |
@RestController
@RequestMapping("/book")
//@CrossOrigin
@Api("图书接口")
public class BookController {
@Autowired
private BookService bookService;
@Autowired
private BookServiceImpl bookServiceImpl;
@Autowired
private CategoryServiceImpl categoryService;
@GetMapping("/list")
@ApiOperation("全部图书")
public JsonResult list() {
List<Book> list = bookService.list();
return ResultTool.success(list);
}
}
全局异常处理
在项⽬开发中出现异常时很平常不过的事情,我们处理异常也有很多种⽅式。可能如下
public int div(int a ,int b){
int c = 0;
try{
c = a / b;
}catch (Exception ex){
ex.printStackTrace();
}
return c;
}
这就引出了我们要介绍的全局异常处理⽅法,主要有两种⽅式:
1. 使⽤ @RestControllerAdvice 和 @ExceptionHandler 注解
2. 使⽤ ErrorController 类来实现
@RestControllerAdvice ⽅式
全局异常处理类
书写⼀个全局异常处理类,代码如下
// 注解@RestControllerAdvice表示这是⼀个控制器增强类,当控制器发⽣异常且符合类中定义的拦截异常类,将会
被拦截。
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 对那些异常进⾏拦截
@ExceptionHandler(Exception.class)
public JsonResult globalException(HttpServletResponse response, Exception e) {
log.info("进⼊了全局异常处理⽅法");
log.info("异常信息是:{}", e.getMessage());
return ResultTool.fail(e.getMessage());
}
}
控制器
@RequestMapping("/second")
@RestController
public class SecondController {
@RequestMapping("/login")
public String login() {
System.out.println("aaa");
return "login";
}
// 这个⽅法会出现错误
@RequestMapping("/b")
public String b() {
System.out.println("bbb");
System.out.println(9/0);
return "b";
}
}
启动
启动并调⽤错误⽅法,⻚⾯效果:
控制台效果
使⽤ ErrorController 类
系统默认的错误处理类为 BasicErrorController,当出现错误时显示默认的错误⻚⾯。这⾥编写⼀个⾃⼰的错误处 理类,上⾯默认的处理类将不会起作⽤。 getErrorPath()返回的路径服务器将会重定向到该路径对应的处理类,本例中为error⽅法。
@RestController
@Slf4j
public class HttpErrorController implements ErrorController {
private final static String ERROR_PATH = "/error";
@RequestMapping(path = ERROR_PATH)
public JsonResult error(HttpServletRequest request, HttpServletResponse response) {
log.info("访问/error" + " 错误代码:" + response.getStatus());
return ResultTool.fail();
}
public String getErrorPath() {
return ERROR_PATH;
}
}
还是⽤刚才的错误控制器⽅法,继续启动并访问,浏览器效果
控制台效果
两者区别
1. 注解 @RestControllerAdvice ⽅式只能处理控制器抛出的异常。此时请求已经进⼊控制器中。 2. 类 ErrorController ⽅式可以处理所有的异常,包括未进⼊控制器的错误,⽐如404,401等错误 3. 如果应⽤中两者共同存在,则 @RestControllerAdvice ⽅式处理控制器抛出的异常,类 ErrorController ⽅式 未进⼊控制器的异常。 4. @RestControllerAdvice ⽅式可以定义多个拦截⽅法,拦截不同的异常类,并且可以获取抛出的异常信息,⾃ 由度更⼤
例如:
GlobalExceptionHandler.java
import com.jiazhong.util.JsonResult;
import com.jiazhong.util.ResultTool;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RemoveBookException.class)
public JsonResult removeBook(Throwable e) {
return ResultTool.fail(5001, e.getMessage());
}
@ExceptionHandler(ChangeBookStatusException.class)
public JsonResult chanChangeBookStatusExceptionge(Throwable e) {
return ResultTool.fail(5002, e.getMessage());
}
}
ChangeBookStatusException.java
public class ChangeBookStatusException extends RuntimeException {
public ChangeBookStatusException(String message) {
super(message);
}
}
RemoveBookException.java
public class RemoveBookException extends RuntimeException {
public RemoveBookException(String message) {
super(message);
}
}