集成到前台系统
上一篇博文:【SSM分布式架构电商项目-11】后台系统开发接口返回商品类目数据
我们已经实现了后台的接口,我们可以直接访问接口获取到数据了。现在我们要集成到前台系统,通过ajax去访问后台系统获取数据。
启动前台和后台进行测试:
可以看到数据是以及获取到。
但是,出错(JS解析出错):
跨域问题
浏览器对ajax请求的限制,不允许跨域请求资源。
http://www.a.com -> http://www.b.com 是跨域
http://www.a.com -> http://www.a.com:8080 是跨域
http://a.a.com -> http://b.a.com 是跨域
http://www.a.com -> http://www.a.com/api 不是
总结:
不同的域名或不同的端口都是跨域请求。
如何解决跨域问题? – jsonp
Jsonp
编写json.jsp(后台系统)
在后台系统中编写test-json.htm
在后台测试是可以请求成功的
将test-json.htm拷贝到前台系统进行测试
发现:
1、 alert($) 可以正常弹出
2、 alert(data.abc) 不能够正常的弹出,出现跨域问题
结论:script标签的src可以跨域请求资源,但是ajax请求不可以跨域请求。
疑问:能否借助script标签的src进行加载数据? – 可以的。
借助script的src跨域加载资源
发现:
请求资源可以正常请求,但是,报js解析出错。
原因:
Script标签加载到资源后,会将资源当做是js脚本解析,但是我们返回的是json数据,所以导致解析失败。
解决:只需要返回js脚本即可。
后端系统返回js脚本
测试:
发现:
返回的js脚本成功解析,但是,fun没有定义。
解决:定义个一个fun方法即可。
定义fun方法
测试:
总结
Jsonp的原理:
1、 jsonp通过script标签的src可以跨域请求的特性,加载资源
2、 将加载的资源(通过一个方法名将数据进行包裹)当做是js脚本解析
3、 定义一个回调函数,获取传入的数据
优化
将回调函数名传递到服务端,返回:
调用方:
通过jQuery使用jsonp请求
解决项目中跨域问题
后台系统Controller
package com.taotao.manage.controller.api;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.common.bean.ItemCatResult;
import com.taotao.manage.service.ItemCatService;
@RequestMapping("api/item/cat")
@Controller
public class ApiItemCatController {
@Autowired
private ItemCatService itemCatService;
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 对外提供接口服务,查询所有类目数据
*
* @return
*/
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> queryItemCatList(
@RequestParam(value = "callback", required = false) String callback) {
try {
ItemCatResult itemCatResult = this.itemCatService.queryAllToTree();
String json = MAPPER.writeValueAsString(itemCatResult);
if (StringUtils.isEmpty(callback)) {
//无需跨域支持
return ResponseEntity.ok(json);
}
//需要跨域支持
return ResponseEntity.ok(callback + "(" + json + ");");
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
测试:
问题解决,但是带来了新问题,乱码问题。
解决乱码问题
分析乱码产生的原因
我们debug发现:
所以并不是对象转为json字符串的时候出现乱码。
在SpringMVC中产生的响应有2类:
1、 ModelAndView
2、 返回数据响应
返回类型为ResponseEntity<T>
代表我们返回的数据是一个对象,在springMVC中,请求数据到对象和对象到响应数据的转换是通过消息转换器来完成的。
HttpMessageConverter是消息转换器的顶层接口,所有的消息转换器都必须实现这个接口
a) 使用消息转化器完成
通常我们在springMVC配置文件中会配置<mvc:annotation-driven></mvc:annotation-driven>
注解驱动,它对应的类是AnnotationDrivenBeanDefinitionParser
在 spring-webmvc.jar包下我们可以找到AnnotationDrivenBeanDefinitionParser类
配置:
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
messageConverters.setSource(source);
for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
messageConverters.add(object);
}
}
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
messageConverters.setSource(source);
messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));
if (romePresent) {
messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
}
if (jackson2XmlPresent) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2XmlHttpMessageConverter.class, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonFactoryDef.getPropertyValues().add("createXmlMapper", true);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
}
else if (jaxb2Present) {
messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jackson2Present) {
RootBeanDefinition jacksonConverterDef = createConverterDefinition(MappingJackson2HttpMessageConverter.class, source);
GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
messageConverters.add(jacksonConverterDef);
}
else if (gsonPresent) {
messageConverters.add(createConverterDefinition(GsonHttpMessageConverter.class, source));
}
}
return messageConverters;
}
分析getMessageConverters()方法后发现执行流程如下:
1.它首先会去配置中查找<mvc:annotation-driven>
父标签中是否含有<mvc:message-converters>
子标签,如果有则把<mvc:message-converters>
下的所有自定义消息转化器封装在message-converter对象中
2.然后判断message-converter是否为空。如果不为空,则把message-converter中封装的的所有自定义消息转换器添加到managedList集合中。
3.a)如果<mvc:annotation-driven>
的属性register-defaults为真,还会加载默认列出的消息转换器,并加入到managedList集合里。b)如果为假,则只会加载我们自定义的。tips:通常我们都会是它为真,或不配置这个属性,不配置,他默认为真。
从其中我们可以找到添加StringHttpMessageConverter转换器的代码段
和输出JSON用到的MappingJackson2HttpMessageConverter转换器,这也可以看处springMVC对JSON的支持为什么要加入jackson的依赖
a)之前如果我们要把对象以JSON格式输出,可以给controller的方法加@ResponseBody注解,到后来我们依照resultful的思想来做项目时,函数的返回值不会再是POJO,而是ResponseEntity<T>
,有了它我们向往前台输出对象对应的JSON就不再需要再添加@ResponseBody注解了,ResponseEntity的优先级高于@ResponseBody。在不是ResponseEntity的情况下才去检查有没有@ResponseBody注解。如果响应类型是ResponseEntity可以不写@ResponseBody注解,写了也没有关系。
b)在SpringMVC中是怎么把我们的对象转换为JSON输出的呢,这里依旧要提到spring mvc的消息转换器,是它完成了这个转换过程。
c)但是函数值为ResponseEntity<T>
并不是所有的对象都会再后来转换为json,比如String,它会调用对应的StringHttpMessageConverter的消息转换器,而不是MappingJackson2HttpMessageConverter转换器。所以如果返回值类型为ResponseEntity<String>
,要输出json需要我们自己把json数据写到string里,spring是不会帮我们转换的。
接下来我们看StringHttpMessageConverter.class类 org.springframework.http.converter.StringHttpMessageConverter
发现,默认使用ISO-8859-1,所以会产生乱码。
解决乱码
添加完自定义String类型的消息转换器,Spring MVC中有几个String类型的消息转化器?
添加完自定义String类型的消息转换器,SpringMVC容器里面有两个String类型的消息转化器,一个是默认的,一个是自定义的。我们可以从源码可以看出,Spring MVC会看<mvc:annotation-driven>
下面有没有配置自定义消息转化器,有就添加,之后才会添加默认的消息转化器,所以自定义添加的会排在最前面,默认的在后面。如果不想添加默认的可以设置register-defaults的值:
更多关于SpringMVC中controller中方法返回类型为ResponseEntity<String>
乱码的问题的参考。
测试效果: