演示自定义转换器的使用。
(1)save.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>save</title>
</head>
<body>
<form action="/saveMonster" method="post">
编号:<input name="id" value="10001"/><br/>
姓名:<input name="name" value="齐天大圣"/><br/>
年龄:<input name="age" value="888"/><br/>
婚否:<input name="isMarried" value="false"/><br/>
生日:<input name="birth" value="1456/12/12"/><br/>
<!--使用自定义转换器关联car,value字符串整体添加,使用逗号间隔-->
坐骑:<input name="car" value="避水金晶兽,666.6"/><br/>
<input type="submit" value="保存"/>
</form>
</body>
</html>
(2)自定义转换器(String-->Car)
package com.li.config;
import com.li.bean.Car;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author 李
* @version 1.0
* (proxyBeanMethods = false)
* 1.表示启用Lite模式,保证修饰的配置类中,每个@Bean方法被调用多少次返回的组件都是新创建的,
* 是多例对象,是非代理方式。
* 2.proxyBeanMethods 在调用 @Bean 方法时才生效,因此需要先获取BeanConfig 组件,再调用方法
*/
@Configuration(proxyBeanMethods = false)//设置为设置类
public class WebConfig {
//注入bean-WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addFormatters(FormatterRegistry registry) {
/**
* 1.在 addFormatters()方法中添加一个自定义的转换器
* 2.自定义转换器要完成的功能是 String -> Car
* 3.增加的转换器会注册到 converters 容器中
* 4.converters 底层结构是 ConcurrentHashMap,默认内置了124个转换器
*/
registry.addConverter(new Converter<String, Car>() {
//<sourceType,targetType>
//匿名内部类
@Override
public Car convert(String source) {//source即传入的字符串
//加入转换的业务代码
if (!ObjectUtils.isEmpty(source)) {
Car car = new Car();
String[] strings = source.split(",");
car.setName(strings[0]);
car.setPrice(Double.parseDouble(strings[1]));
return car;
}
return null;
}
});
//还可以增加更多的转换器
}
};
}
}
(3)控制器
Monster和Car是级联对象,Car为Monster对象的属性。
//处理添加Monster的方法
@PostMapping("/saveMonster")
@ResponseBody
public String saveMonster(Monster monster) {
System.out.println("monster=" + monster);
return "success";
}
(4)浏览器提交表单,后台输出如下:
monster=Monster(id=10001, name=齐天大圣, age=888, isMarried=false, birth=Sun Dec 12 00:00:00 CST 1456, car=Car(name=避水金晶兽, price=666.6))
可以看到服务器成功获取到表单数据,并将car表单项的value值转换为Car对象的属性值。
不同转换器通过key值区分,key=[源类型->目标类型]

如果实现的两个自定义转换器的 key 值相同(即源类型和目标类型相同),则在注入容器时,根据注入的顺序,后一个转换器会覆盖前一个转换器!
SpringBoot 支持返回 JSON 格式的数据,在启用 WEB 开发场景时,已经引入了相关的依赖:spring-boot-starter-json。
例子-使用@ResponseBody处理返回 json
package com.li.controller;
import com.li.bean.Car;
import com.li.bean.Monster;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
/**
* @author 李
* @version 1.0
*/
@Controller
public class ResponseController {
//编写方法,以json格式返回数据
@GetMapping("/get/monster")
@ResponseBody
public Monster getMonster() {
Monster monster = new Monster();
monster.setId(199);
monster.setName("孙悟空");
monster.setAge(23);
monster.setIsMarried(false);
monster.setBirth(new Date());
Car car = new Car();
car.setName("奔驰");
car.setPrice(20000.0);
monster.setCar(car);
return monster;
}
}
浏览器访问该方法,返回如下:
为什么SpringBoot可以将Monster对象以 JSON格式返回呢?
它的底层仍然用到了一个转换器:AbstractJackson2HttpMessageConverter
其中一个重要的方法如下:
返回数据的格式是按照你设置的contentType类型。如果没有指定,默认为json格式。
//参数Object object就是控制器方法返回的对象类型,如Monster
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//当控制器方法返回的时候,获取返回对象的contentType,一般为application/json
MediaType contentType = outputMessage.getHeaders().getContentType();
//获取编码,一般为utf-8
JsonEncoding encoding = this.getJsonEncoding(contentType);
Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
try {//生成一个UTF8JsonGenerator对象
JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);
Throwable var10 = null;
try {//对返回的数据类型进行一系列处理
this.writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue)object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = this.getJavaType(type, (Class)null);
}
ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
//UTF8JsonGenerator对象处理返回的数据
objectWriter.writeValue(generator, value);
this.writeSuffix(generator, object);
generator.flush();
} catch (Throwable var26) {
var10 = var26;
throw var26;
} finally {
if (generator != null) {
if (var10 != null) {
try {
generator.close();
} catch (Throwable var25) {
var10.addSuppressed(var25);
}
} else {
generator.close();
}
}
}
} catch (InvalidDefinitionException var28) {
throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
} catch (JsonProcessingException var29) {
throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
}
}
内容协商:服务端和请求端协商决定最终返回什么格式的内容。客户端发送请求的时候可以告知服务器,自己希望对方返回的数据格式列表,而服务器的接口也有能支持响应的格式列表,最终返回的结果会根据这两个类型列表,找到一种两边都能支持的类型返回,如果找不到合适的类型,则报错。
简单来说就是:根据客户端接收能力不同,SpringBoot 返回不同媒体类型的数据
比如:
Accept: application/xml 则返回 xml 数据Accept: application/json 则返回 json 数据例子1:使用postman测试
(1)使用postman发送Http请求,在此期间服务器的代码不变,根据请求头不同,返回的数据格式也会不同
返回json格式:
返回xml格式:
SpringBoot 默认支持返回 Json数据,不支持返回xml数据,所以需要导入jackson-dataformat-xml
<!--引入处理xml的依赖-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
例子2:使用浏览器测试
浏览器不能指定Accept属性,我们在浏览器发出请求,发现返回的数据为xml格式:
这是因为浏览器的Accept中指定了多种媒体类型,如下:
*/* [所有类型] 格式的权重为0.8
如上,客户端通过Http请求的Accept来指定能接收的媒体类型。那么服务端的接口是怎么响应这个类型的呢?
内容协商原理:
Postman可以通过修改Accept的值来返回不同的数据格式。对于浏览器来说,我们无法修改其Accept的值,这时如果要指定返回json格式,怎么办呢?
解决方案:开启支持基于请求参数的内容协商功能
(1)修改application.yml
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启基于请求参数的内容协商,默认不开启
(2)在浏览器请求的时候带上format参数,此时返回的就是指定的格式了
注意:参数format的值是规定好的,在开启请求参数的内容协商功能后,SpringBoot底层ParameterContentNegotiationStrategy会通过format来接收参数,然后返回对应的媒体类型/数据格式,因此format的值也要是SpringBoot能处理的才行。
当然format这个属性名本身也可以通过配置文件修改:
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou
我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',