本篇记录一下使用前后端分离来开发SSM项目。
目前,前后端分离项目早已占据市场的主流开发方式。因为它能把web端的代码和java业务实现的代码完全分离开来,使得协同开发变得更加便捷。即,前端工程师和后端工程师只需要预先规定好统一的API,就可以同步开发,大大降低项目的开发周期。
实现前后端分离需要解决的文体就是跨域传输。
我们都知道,在SSM项目中我们使用JSP来访问Servlet接口,实现前后端的交互,归根结底还是在同一个域进行传输,即端口号是相同的。但是在使用前后端分离开发的时候,我们需要在不同的域中间进行数据传输。最直观的就是,后端我们一般使用8080端口,前端可能会使用5500端口,这就意味着,前端和后端处在不同的域内。
跨域传输的根本原因是浏览器端的一种保护程序的机制。当前端域向后端域发送了请求,一般情况下,会有正常的响应数据。但是由于浏览器的保护机制,使得我们无法使用这些响应数据。浏览器需要的是后端必须返回给它一个允许跨域的请求头,否则,返回的数据就无法呈现在前端。那么怎么让后端返回的数据携带正确的请求头就是我们需要解决的问题。
代码如下:
@Controller
@RequestMapping( "/login")
@CrossOrigin("*")
public class LoginController {
//自动注入业务层
@Autowired
private LoginService service;
@RequestMapping(value = "loginVerify",method = RequestMethod.POST)
@ResponseBody
public ResultLayUi<User> login(String username, String password, HttpServletRequest request){
createLoginSession(username,request);
return service.verifyLogin(username,password);
}
}
我们可以使用@CrossOrigin注解来实现一个临时的跨域传输访问。注解后边的括号可以填写允许跨域访问的域,例如@CrossOrigin(" * ")表示,允许所有的域访问。@CrossOrigin(“http://127.0.0.1:5500/login.html”)允许该IP访问。但是这种解决方案只适合简单请求。不适合复杂请求。
通过注解,我们只能实现简单的跨域请求,但无法实现复杂的跨域请求。但是我们可以通过在controller中设置请求头和请求参数来实现复杂请求的跨域传输。代码如下:
@Controller
@RequestMapping( "/login")
//@CrossOrigin("*")
public class LoginController {
//自动注入业务层
@Autowired
private LoginService service;
@RequestMapping(value = "loginVerify",method = RequestMethod.POST)
@ResponseBody
public ResultLayUi<User> login(String username, String password, HttpServletRequest request){
String origin = request.getHeader("Origin");
// 允许的跨域
response.setHeader("Access-Control-Allow-Origin",origin);
// 允许携带Cookie
response.setHeader("Access-Control-Allow-Credentials","true");
// 允许的请求头 预检请求需要这个设置
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Token,token");
response.setHeader("Access-Control-Expose-Headers", "*");//响应客户端的头部 允许携带Token 等等
response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的结果缓存时间
if (request.getMethod().equals("OPTIONS")){
return false;
}
return service.verifyLogin(username,password);
}
}
通过这种方式,我们可以实现跨域请求。但是我们需要在每一个Controller方法中都设置这些代码。
上边说到,我们需要在每一个controller中都配置如上代码,造成大量的代码冗余。我们可以把这些代码放进拦截器或者过滤器来实现一个封装的效果。
package com.glls.java2212.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @date 2022/10/10
* @desc 拦截器 一个临时的 跨域解决方案
*/
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String origin = request.getHeader("Origin");
// 允许的跨域
response.setHeader("Access-Control-Allow-Origin",origin);
// 允许携带Cookie
response.setHeader("Access-Control-Allow-Credentials","true");
// 允许的请求头 预检请求需要这个设置
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Token,token");
response.setHeader("Access-Control-Expose-Headers", "*");//响应客户端的头部 允许携带Token 等等
response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的结果缓存时间
if (request.getMethod().equals("OPTIONS")){
return false;
}
return true;
}
}
package com.glls.java2212.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @date 2022/10/10
* @desc
*/
@Component
@WebFilter(urlPatterns = "/*", filterName = "CorsFilter")
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String origin = request.getHeader("Origin");
// 允许的跨域
response.setHeader("Access-Control-Allow-Origin",origin);
// 允许携带Cookie
response.setHeader("Access-Control-Allow-Credentials","true");
//
response.setHeader("Access-Control-Allow-Headers", "Content-Type,content-type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Token,token");
response.setHeader("Access-Control-Expose-Headers", "*");//响应客户端的头部 允许携带Token 等等
response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的结果缓存时间
//
if (!request.getMethod().equals("OPTIONS")){
//非 预检请求 放行 ,预检请求 不放行 预检请求 直接响应允许跨域的信息即可 无需访问到后台接口
filterChain.doFilter(request, response);
}
}
}
跨域请求的方案有很多,此处只是简单的介绍了两种。分别对应简单的请求跨域和复杂的请求跨域。