Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | 51CTO学院 | CSDN程序员研修院 | OSChina 博客 | 腾讯云社区 | 阿里云栖社区 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏多维度架构

3.3. @RestController

3.3.1. 返回实体

		
	@RequestMapping("/get/{id}")
	public Member getStatistics(@PathVariable long id) {
		Member statistics = memberRepostitory.findOne(id);
		if (statistics == null) {
			statistics = new Member();
		}
		return statistics;
	}
		
		

3.3.2. JSON

MediaType.APPLICATION_JSON_VALUE 执行结果反馈json数据

		
@RestController
@RequestMapping("/api/persons")
public class MainController {

    @RequestMapping(
            value = "/detail/{id}", 
            method = RequestMethod.GET, 
            produces = MediaType.APPLICATION_JSON_VALUE
            )
    public ResponseEntity<Persons> getUserDetail(@PathVariable Long id) {

        Persons user = personsRepository.findById(id);

        return new ResponseEntity<>(user, HttpStatus.OK);
    }

}
		
		

3.3.3. 处理原始 RAW JSON 数据

		
	@RequestMapping(value = "/create", method = RequestMethod.POST, produces = { "application/xml", "application/json" })
	public ResponseEntity<Member> create(@RequestBody Member member) {
		memberRepository.save(member);
		return new ResponseEntity<Member>(member, HttpStatus.OK);
	}		
		
		

3.3.4. 返回 JSON 对象 NULL 专为 "" 字符串

		
package api.config;

import java.io.IOException;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;

@Configuration
public class JacksonConfig {
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
                jsonGenerator.writeString("");
            }
        });
        return objectMapper;
    }
}

		
		

3.3.5. XML

restful 将同时支持 json 和 xml 数据传递

		
package com.example.api.restful;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.api.domain.RecentRead;
import com.example.api.repository.RecentReadRepostitory;

@RestController
@RequestMapping("/restful/article")
public class ArticleRestController {

	@Autowired
	private RecentReadRepostitory recentReadRepostitory;

	@RequestMapping(value = "/recent/read/add/{memberId}/{articleId}", method = RequestMethod.GET, produces = { "application/xml", "application/json" })
	public ResponseEntity<RecentRead> recentAdd(@PathVariable long memberId, @PathVariable long articleId) {
		RecentRead recentRead = new RecentRead();
		recentRead.setMemberId(memberId);
		recentRead.setArticleId(articleId);
		recentReadRepostitory.save(recentRead);
		return new ResponseEntity<RecentRead>(recentRead, HttpStatus.OK);
	}

	@RequestMapping(value="/recent/read/list/{id}", produces = { "application/xml", "application/json" })
	public List<RecentRead> recentList(@PathVariable long id) {
		int page = 0;
		int limit = 20;
		List<RecentRead> recentRead = recentReadRepostitory.findByMemberId(id, new PageRequest(page, limit));
		return recentRead;
	}
}
		
		

3.3.6. 兼容传统 json 接口

开发中发现很多人不适应新的接口方式,有时候只能妥协,这些顽固不化的人需要这样的数据库格式

		
{  
   "status":true,
   "reason":"登录成功",
   "code":1,
   "data":{
      "id":2,
      "name":null,
      "sex":null,
      "age":0,
      "wechat":null,
      "mobile":"13113668890",
      "picture":null,
      "ipAddress":"0:0:0:0:0:0:0:1"
   }
}
		
		

返回数据必须放在 data 字典中, 而我通常是采用 http status code 来返回状态,返回结果是对象。实现上面的需求我们需要加入一个data成员变量,因为我们不清楚最终要返回什么对象。所以声明为 java.lang.Object

		
package com.example.api.pojo;

import java.io.Serializable;

public class RestfulResponse implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -4045645995352698349L;

	private boolean status;
	private String reason;
	private int code;
	private Object data;

	public RestfulResponse(boolean status, int code, String reason, Object data) {
		this.status = status;
		this.code = code;
		this.reason = reason;
		this.data = data;

	}

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
	}

	public String getReason() {
		return reason;
	}

	public void setReason(String reason) {
		this.reason = reason;
	}

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	@Override
	public String toString() {
		return "RestfulResponse [status=" + status + ", reason=" + reason + ", code=" + code + ", data=" + data + "]";
	}

}
		
		

Service

		
	public RestfulResponse bindWechat(String mobile, String wechat) {
		Member member = memberRepository.findByMobile(mobile);
		member.setWechat(wechat);
		memberRepository.save(member);
		return new RestfulResponse(true, 1, "微信绑定成功", member);
	}
		
		

Controller

		
	@RequestMapping("/login/sms/{mobile}/{code}")
	public RestfulResponse sms(@PathVariable String mobile, @PathVariable String wechat) {
		return memberService.bindWechat(mobile, wechat);
	}
		
		

3.3.7. @PageableDefault 分页

		
@RequestMapping(value = "/list", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable1(@PageableDefault( sort = { "id" }, direction = Sort.Direction.DESC) 
    Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@RequestMapping(value = "/blog", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable(@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC) 
    Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@RequestMapping(value = "/list", method=RequestMethod.GET)
public Page<Blog> getEntryByPageable2(@PageableDefault Pageable pageable) {
    return blogRepository.findAll(pageable);
}

@ModelAttribute("users")
public Page<User> users(@PageableDefault(size = 5) Pageable pageable) {
	return userManagement.findAll(pageable);
}
		
		
		
我们只需要在方法的参数中直接定义一个pageable类型的参数,当Spring发现这个参数时,Spring会自动的根据request的参数来组装该pageable对象,Spring支持的request参数如下:

page,第几页,从0开始,默认为第0页
size,每一页的大小,默认为20
sort,排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列
这样,我们就可以通过url的参数来进行多样化、个性化的查询,而不需要为每一种情况来写不同的方法了。

通过url来定制pageable很方便,但唯一的缺点是不太美观,因此我们需要为pageable设置一个默认配置,这样很多情况下我们都能够通过一个简洁的url来获取信息了。

Spring提供了@PageableDefault帮助我们个性化的设置pageable的默认配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)表示默认情况下我们按照id倒序排列,每一页的大小为15。		
		

		

3.3.8. 上传文件

		
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.http.multipart.enabled=false
		
		

RestController

		
package api.restful;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import api.pojo.RestfulResponse;

@RestController
@RequestMapping("/upload")
public class UploadRestController {

	private final static String FOLDER = "/tmp";

	public UploadRestController() {
		
	}

	@PostMapping("/single")
	public RestfulResponse upload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

		if (file.isEmpty()) {
			return new RestfulResponse(false, 0, "Please select a file to upload", "");
		}
		try {
			byte[] bytes = file.getBytes();
			Path path = Paths.get(FOLDER + "/" + file.getOriginalFilename());
			Files.write(path, bytes);

			return new RestfulResponse(true, 0, "", path.toString());
		} catch (Exception e) {
			return new RestfulResponse(false, 0, e.getMessage(), null);
		}
	}

	@PostMapping(value = "/group")
	public RestfulResponse group(@RequestParam("files") MultipartFile[] files) {
		List<String> filelist = new ArrayList<String>();
		try {

			for (MultipartFile file : files) {
				File tmpfile = new File(FOLDER + "/" + file.getOriginalFilename());
				file.transferTo(tmpfile);
				filelist.add(tmpfile.getPath());
			}
			return new RestfulResponse(true, 0, null, filelist);
		} catch (Exception e) {
			return new RestfulResponse(false, 0, e.getMessage(), null);
		}
	}
}
		
		

由于上传文件名可能存在空格等特殊字符,这里使用UUID替代文件名

		
	@PostMapping(value = "/file")
	public RestfulResponse file(@RequestParam("file") MultipartFile[] files) {
		List<Object> filelist = new ArrayList<Object>();
		try {

			for (MultipartFile file : files) {
			
				UUID uuid = UUID.randomUUID();
				String filename = String.format("%s/%s.%s", folder, uuid.toString(), this.getExtensionName(filename));

				File tmpfile = new File(filename);
				String filepath = tmpfile.getPath();
				System.out.println(filepath);
				file.transferTo(tmpfile);
				filelist.add(tmpfile.toString());
			}
			return new RestfulResponse(true, 0, null, filelist);
		} catch (Exception e) {
			return new RestfulResponse(false, 0, e.getMessage(), null);
		}
	}

	private String getExtensionName(String filename) {
		if ((filename != null) && (filename.length() > 0)) {
			int dot = filename.lastIndexOf('.');
			if ((dot > -1) && (dot < (filename.length() - 1))) {
				return filename.substring(dot + 1);
			}
		}
		return filename;
	}
		
		

3.3.9. Spring boot with csv

下面是一个导出 CSV 文件的例子

		
	@GetMapping("/export")
	public void export(HttpServletResponse response) throws IOException {
		response.setContentType("application/csv");
		// response.setContentType("application/csv;charset=gb18030");
		response.setHeader("Content-Disposition", "attachment; filename=\"file.csv\"");

		BufferedWriter writer = new BufferedWriter(response.getWriter());

		// 需要写入 utf8bom 头否则会出现中文乱码
		// byte[] uft8bom = { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
		String bom = new String(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
		writer.write(bom);
		writer.write("A,B,C");
		writer.newLine();
		tableRepository.findAll().forEach(table -> {
			try {
				String tmp = String.format("%s,%s,%s", table.getId(), table.getMethod(), table.getMoney());
				writer.write(tmp);
				writer.newLine();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		});

		writer.flush();
		writer.close();
	}		
		
		

3.3.10. Jackson

3.3.10.1. Jackson 相关配置

		
#序列化时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
#mvc序列化时候时区选择
spring.jackson.time-zone=GMT+8		
		
		

3.3.10.2. @JsonIgnore 返回json是不含有该字段

			
	@JsonIgnore
	private String entityName = this.getClass().getSimpleName();	
			
		

3.3.10.3. @JsonFormat 格式化 json 时间格式

日期格式化

默认 json 中的时间格式是这样的

			
"createDate":"2018-09-11T07:34:20.106+0000","updateDate":"2018-09-11T07:34:20.106+0000"			
			
			

@JsonFormat 可以格式化 json 返回的时间格式。

			
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")			
			
			

格式化后

			
"createDate":"2018-09-11 07:42:44","updateDate":"2018-09-11 07:42:44"			
			
			

解决时区问题,MongoDb 默认使用UTC,显示时间相差8小时

			
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")	
private Date createdDate = new Date();			
			
			
时区
			
public class Test {
   @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MMM-dd HH:mm:ss z", timezone="EST")
   @JsonProperty("pubDate")
   private Date recentBookPubDate;	
} 			
			
			
枚举
			
public class Test {
   @JsonFormat(shape=JsonFormat.Shape.NUMBER)
   @JsonProperty("birthDate")
   private Date birthDate;
} 
			
			
			
{
  "birthDate" : 1528702883858
} 			
			
			
枚举
			
package cn.netkiller;
import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape=JsonFormat.Shape.NUMBER)
enum Code {
    BLOCKING,
    CRITICAL,
    MEDIUM,
    LOW;
} 

@JsonFormat(shape=JsonFormat.Shape.STRING)
enum Lang {
	Java,
	PHP,
	Python
} 
			
			

3.3.10.4. @JsonComponent

		
package cn.netkiller.json;

public class Member {
	private String name;

	public Member() {
		// TODO Auto-generated constructor stub
	}

	public Member(String name) {
		// TODO Auto-generated constructor stub
		this.name = name;

	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Member [name=" + name + "]";
	}

}
		
		
		
		
package cn.netkiller.json;

import java.io.IOException;

import org.springframework.boot.jackson.JsonComponent;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.node.TextNode;

@JsonComponent
public class Json {

	public Json() {
		// TODO Auto-generated constructor stub
	}

	public static class MemberJsonSerializer extends JsonSerializer<Member> {

		@Override
		public void serialize(Member value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
			// TODO Auto-generated method stub
			gen.writeStartObject();
			gen.writeStringField("member", value.toString());
			gen.writeEndObject();

		}
	}

	public static class MemberJsonDeserializer extends JsonDeserializer<Member> {

		@Override
		public Member deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
			// TODO Auto-generated method stub
			TreeNode treeNode = p.getCodec().readTree(p);
			TextNode member = (TextNode) treeNode.get("member");
			return new Member(member.asText());
		}
	}
}		
		
		
		
package cn.netkiller.json.controller;

import cn.netkiller.json.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 *
 * @author neo
 */
@RestController
public class SimpleController {

	@Autowired
	public ObjectMapper objectMapper;

	@GetMapping("/")
	public String home() throws JsonMappingException, JsonProcessingException {
		String json = "{\"name\":\"netkiller\"}";
		Member member = objectMapper.readValue(json, Member.class);
		System.out.println(member.getName());
		return member.getName();
	}

}
		
		

3.3.10.5. Object to Json

		
ObjectMapper mapper = new ObjectMapper();
User user = new User();

//Object to JSON in file
mapper.writeValue(new File("c:\\user.json"), user);

//Object to JSON in String
String jsonInString = mapper.writeValueAsString(user);

//Convert object to JSON string and pretty print
String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
		
		
		
	Notification notification = new Notification(status, time, summary + info);
	ObjectMapper objectMapper = new ObjectMapper();
	String json = objectMapper.writeValueAsString(notification);		
		
		

3.3.10.6. Json To Object

		
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{'name' : 'mkyong'}";

//JSON from file to Object
User user = mapper.readValue(new File("c:\\user.json"), User.class);

//JSON from String to Object
User user = mapper.readValue(jsonInString, User.class);
		
		

3.3.11. synchronized

避免接口无序执行,被同时多次执行,同一时间只能有一个请求,请求完毕之后才能进行下一次请求。

		
   @GetMapping("/lock/{id}")
    public String lock1(@PathVariable("id") String id) throws InterruptedException {
        synchronized (id.intern()) {
            log.info(Thread.currentThread().getName() + " 上锁");
            Thread.sleep(10000);
            log.info(Thread.currentThread().getName() + " 解锁");
        }
        return Thread.currentThread().getName();
    }		
		
		

使用 ConcurrentHashMap 数据共享

		
	private final Map<String, Object> share = new ConcurrentHashMap<>();

    @GetMapping("/share/{id}")
    public Map<String, Object> shareTest(@PathVariable("id") String id) throws InterruptedException {

//        share.computeIfAbsent(id, k -> new Object());
        share.computeIfAbsent(id, key -> {
            return new Date();
        });

        synchronized (share) {
            log.info(Thread.currentThread().getName() + " 上锁");
            Thread.sleep(1000);
            log.info(Thread.currentThread().getName() + " 解锁");
        }
        return share;
    }