跨域问题
同源策略
同源策略是一个重要的web安全策略,只有协议、域名、端口三个全部一致才是同源的,当有一个不同时,浏览器对服务器的访问就可能面临一个跨域问题
什么时候会存在跨域问题
当不同源时,也并非一定会面临跨域问题,比如当表单提交、连接跳转等不会出现跨域问题;只有当ajax请求时,才会出现跨域问题所以出现跨域问题的条件是:ajax请求、不同源
跨域后面临的问题
- 浏览器无法接收响应
- 服务器无法判断浏览器的身份,每次的session都会新建
解决方式
SpringMVC—CORS
流程
对于get请求,浏览器发送之后经由服务器处理,当服务器响应后检查响应头的Access-Control-Allow-Origin是否有自己,如果没有则不会接收请求;对于post请求,浏览器会先发送一个Options请求,用于查看服务器的响应中是否有自己,如果有才会发送post请求。这是因为post请求一般是为了提交数据,为了防止直接被服务器执行
办法
- 在控制器使用@CrossOrigin注解,在前端ajax请求时需要在xhrFields中设置一个withCredentails为true带去一个凭证,如Cookie;并指定@CrossOrigin的value属性为前端的站点,allowCredentials为true来同意接收凭证
- 使用CorsFilter,该过滤器的主要目的就是为了将Access-Control-Allow-Origin设置为请求方的站点;两个方式效果相同,只是配置方式和作用域的区别(注解用于单处理器,过滤器用于所有处理器)
$.ajax({
url:"http://localhost:8081/user/register",
type:"get",
dataType:"json",
xhrFields:{
withCredentials:true,
},
data:{
name:function(){
return $("#name").val();
}
}
})
@Controller
@RequestMapping("user")
@CrossOrigin(value = "http://localhost:8848",allowCredentials = "true")
public class UserController {
@ResponseBody
@GetMapping("register")
public String register(HttpServletRequest req){
System.out.println(req.getSession().getId());
System.out.println(req.getRequestedSessionId());
return "ok";
}
}
注意
1. 使用withCredentials来带cookies的方式仅限于根域(顶级域名,如.cn、.com)相同,如果根域不同那么前端依然不会发送cookies
2. ip地址和域名不同,比如localhost和127.0.0.1是不一样的,所以既不能在后端指定允许访问的站点时,对方是域名而用ip,也不能前后端一方使用IP地址一方使用域名,这样会认为根域不同出现第一点的错误
使用JSONP
jsonp是json为解决跨域问题的一种形式,前端发送ajax请求时,使用jsonp来发送数据和接收不同源的站点响应的数据是不会被浏览器拦截的,即使后端没有使用过滤器来设置响应头
总的来说,跨域问题是浏览器为了同源策略导致的,与服务器端无关,跨域的请求来到服务器端服务器依然会执行,但是响应回浏览器的时候,如果浏览器在响应头里面的Access-Control-Allow-Credentials没有找到自己的站点,就会报错;如果需要发送cookies,服务器端使用通配符*来指定允许跨域访问的站点是无效的
异常处理
@ExceptionHandler
SpringMVC提供了异常处理相关的注解@ExceptionHandler,该注解用于修饰方法,注解的value值为异常类的class数组类型,当该Controller发生异常时,会自动跳转到该注解修饰的方法,相当于try-catch然后在catch中执行该方法。如果没有为注解传参数,那么方法参数中的异常列表将会作为方法将处理的异常类型,该注解修饰方法,只对一个Controller起作用
代码示例
@Controller
@RequestMapping("exception")
public class TestException {
@GetMapping("test1")
public String test1(){
User user = new User();
// user.setId(12);
System.out.println(user.getId());
return "test1";
}
@ExceptionHandler
public void handlerError(NullPointerException e){
System.out.println("error");
}
}
@ControllerAdvice
@ControllerAdvice注解可以捕获到其他Controller发生的异常,当其他Controller出现异常的时候,不哦她那个Controller的异常处理器无法处理到,但是@ControllerAdvice修饰的Controller下的处理器可以全局处理异常
代码示例
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(NullPointerException.class)
public void test1(){
System.out.println("error");
}
}
自定义视图
SpringMVC可以自定义视图解析器,只需要创建一个类继承自AbstractXXXView(XXX为对应类型的视图),实现其内部的build方法作为解析视图的逻辑,然后使用ModelAndView选择数据和视图即可
导出Excel视图代码示例
- 先导入apache-poi依赖,用于生成excel
controller
@Controller
@RequestMapping("view")
public class TestView {
@GetMapping("test1")
public ModelAndView test1(){
User user = new User();
user.setName("张三");
User user1 = new User();
user1.setName("李四");
List<User> users = new ArrayList<>();
users.add(user);
users.add(user1);
ModelAndView mv = new ModelAndView();
mv.setView(new ExcelView());
mv.addObject("users",users);
return mv;
}
}
View
public class ExcelView extends AbstractXlsView {
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
Sheet sheet = workbook.createSheet("用户信息");//创建表
Row header = sheet.createRow(0);//创建行
header.createCell(0).setCellValue("用户名");//创建单元格并添加数据
List<User> data = (List<User>)model.get("users");
for (int i=0;i<data.size();i++){
Row row = sheet.createRow(i+1);
row.createCell(0).setCellValue(data.get(i).getName());
}
response.setHeader("Content-Disposition","attachment;fileName"+ URLEncoder.encode("用户","UTF-8")+".xlsx");
}
}
事务
事务的xml配置
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
name表示添加事务的方法,如get*表示通配get开头的方法名
propagation配置事务的传播机制
isolation配置事务的隔离级别,大部分情况下默认的隔离级别最优
read-only表示事务为只读,即不对数据库的数据进行操作,只读取数据,性能会高一些
rallback-for/no-rallback-for表示对某类异常进行事务处理,详见回滚
传播机制
- REQUIRED:默认,支持当前事务,如果当前事务不存在,则创建一个新事务
- SUPPORTS:支持当前事务,如果当前事务不存在,则不使用事务
- MANDATORY:支持当前事务,如果当前事务不存在,则抛出异常(表示强制必须有事务)
- REQUIRES_NEW:创建一个新事务,如果当前存在事务,则将当前事务挂起
- NO_SUPPORTED:无事务执行,如果当前事务存在,则将当前事务挂起
- NEVER:无事务执行,如果当前存在事务,则抛出异常
- NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行;否则同REQUIRED
传播机制详解
伪代码:
@Transactional
public void ServiceB(){
@Transactional
ServiceA();
}
当前事务:这里B方法调用了A方法,当B方法存在事务时,执行到A方法时,A方法也会存在事务,对于A来说,这个事务就是当前已存在的事务
- REQUIRED:如果B方法存在事务,则支持事务,如果不存在那么在A方法上会新建一个事务
- SUPPORTS:如果B方法存在事务,则支持事务,如果不存在那么不使用事务
- MANDATORY:如果B方法存在事务,则支持事务,如果不存在就抛出异常
1,2,3三种事务传播机制内外层共用一个事务,当内层或外层抛出异常时,事务将回滚- REQUIRES_NEW:创建一个新事务,如果B方法已经存在事务,挂起当前事务(外层事务)。内层事务结束了内层直接提交,不用等外层;内层抛出异常时,如果外层没有捕获那么内层的异常则内外层均回滚,如果捕获了则内层回滚外层不回滚
- NESTED:创建一个新事物,如果B方法已存在事务,那么将会嵌套一个子事务。内层事务结束后需要等外层的父事务一起提交,如果内层抛出异常,无论外层是否捕获,内外层都会一起回滚
4,5两种事务传播机制内外层有两个事务- NO_SUPPORTED:这里当A方法指定传播机制为NO_SUPPORTED时,则表示A方法不执行事务,当B存在事务时,将事务挂起,当A方法执行结束后事务才继续(A方法抛出的异常不会导致事务的回滚)
- NEVER:这里A方法执行传播机制为NEVER而B方法存在事务时,就会抛出异常
回滚
Spring整合MyBatis之后,事务的回滚是指捕获到RuntimeException或者Error时,Spring会将底层的各种数据库操作异常包装成DataAccessException(该异常是运行时异常);对于非运行时异常,如IOException,Spring默认不会进行事务回滚,所以有需要的时候就使用rallback-for="IOException"来为指定的异常添加回滚