之前在网上看到有自己手写Json注解的帖子,感觉很是受用那个帖子名字大概叫“Spring MVC 更灵活的控制 json 返回(自定义过滤字段)”,地址是https://my.oschina.net/diamondfsd/blog/836727,不过貌似这个帖子已经无法访问,不过网上有很多关于这个帖子的转载,我这里要解决的问题就是原贴中一笔带过的@JSONS注解。
我遇到的问题是我要返回的实体中包含其他实体,原贴中的@JSON没有办法处理实体中的实体的属性,它只能处理外层的实体,而无法处理实体中的实体的属性。另外如果不需要的属性开启了懒加载,还会因此报错。
那么现要解决问题的是,@JSONS内的@JSON注解对实体里的实体同样生效。
因为我用的是kotlin这里给出涉及全部的代码
import kotlin.reflect.KClass
/**
* @author hsdllcw
* 将下列注解标记在需要返回json的Controller中。要注意的是,该controller不要有@ResponseBody
*/
@Repeatable
annotation class JSON(val type: KClass<*>, val include: String = "", val filter: String = "")
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class JSONS(vararg val value: JSON)
要注意,JsonReturnHandler类已经和原博客有所不同了
import org.springframework.web.method.support.HandlerMethodReturnValueHandler
import javax.servlet.http.HttpServletResponse
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.ModelAndViewContainer
import org.springframework.core.MethodParameter
import org.springframework.http.MediaType
open class JsonReturnHandler : HandlerMethodReturnValueHandler{
override fun supportsReturnType(returnType: MethodParameter): Boolean {
// 如果有我们自定义的 JSON、JSONS 注解 就用我们这个Handler 来处理
return (returnType.getMethodAnnotation(JSON::class.java) != null)||(returnType.getMethodAnnotation(JSONS::class.java) != null)
}
override fun handleReturnValue(returnValue: Any, returnType: MethodParameter, mavContainer: ModelAndViewContainer,
webRequest: NativeWebRequest) {
// 设置这个就是最终的处理类了,处理完不再去找下一个类进行处理
mavContainer.isRequestHandled = true
// 获得注解并执行filter方法 最后返回
val response = webRequest.getNativeResponse(HttpServletResponse::class.java)
val annos = returnType.methodAnnotations
val jsonSerializer = JacksonJsonSerializer()
annos.forEach { // 解析注解,设置过滤条件
if (it is JSON) {
jsonSerializer.filter(it)
} else if (it is JSONS) { // 传入多个@JSON
jsonSerializer.filter(*it.value)
}
}
response.contentType = MediaType.APPLICATION_JSON_UTF8_VALUE
val json = jsonSerializer.toJson(returnValue)
response.writer.write(json)
}
}
JacksonJsonSerializer类做的工作是处理@JSON里面的数据,并准备放入JacksonJsonFilter类中等待处理。在JsonReturnHandler类中已经将@JSONS中的@JSON处理为数组,所以此时处理的过程中不区分@JSON与@JSONS,统一当做数组处理,并且将include、filter也变为数组。
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import kotlin.reflect.KClass
class JacksonJsonSerializer {
var mapper = ObjectMapper()
var jacksonFilter = JacksonJsonFilter()
/**
* @param clazz target type
* @param include include fields
* @param filter filter fields
*/
fun filter( clazzs: Map?, Map>?) {
if (clazzs == null) return
clazzs.entries.forEach{ clazz ->
clazz.value["include"]?.let { include ->
if (include.isNotEmpty()) {
jacksonFilter.include(clazzs,clazz.key, include.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
}
}
clazz.value["filter"]?.let { filter ->
if (filter.isNotEmpty()) {
jacksonFilter.filter(clazzs,clazz.key, filter.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
}
}
mapper.addMixIn(clazz.key?.java, jacksonFilter.javaClass)
}
}
@Throws(JsonProcessingException::class)
fun toJson(`object`: Any): String {
mapper.setFilterProvider(jacksonFilter)
return mapper.writeValueAsString(`object`)
}
fun filter(json: JSON) {
this.filter(mapOf?, Map>(json.type to mapOf("include" to json.include,"filter" to json.filter)))
}
fun filter(vararg jsons: JSON){
this.filter(jsons.fold(ArrayList?, Map>>()) { acc, elements ->
acc.apply { addAll(mapOf(elements.type to mapOf("include" to elements.include,"filter" to elements.filter) ).entries) }
}.associateBy({it.key},{it.value}))
}
}
JacksonJsonFilter类是核心类,它的apply方法决定了哪些属性应该被返回。为了判断将要返回实体的类和注解里的类是否是同一个类,需要用用反射来判断,但是要返回实体作为代理对象获取的类名和注解里的类的类名是不同的,因此需要使用superclass来获取代理对象的目标对象的类名。
import com.fasterxml.jackson.annotation.JsonFilter
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.BeanPropertyFilter
import com.fasterxml.jackson.databind.ser.FilterProvider
import com.fasterxml.jackson.databind.ser.PropertyFilter
import com.fasterxml.jackson.databind.ser.PropertyWriter
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter
import java.util.*
import kotlin.reflect.KClass
@JsonFilter(value = "JacksonFilter")
class JacksonJsonFilter : FilterProvider() {
private var includeMap: MutableMap, Set> = HashMap()
private var filterMap: MutableMap, Set> = HashMap()
private var clazzs: Map?, Map>?=null
fun include(clazzs: Map?, Map>?,type: KClass<*>?, fields: Array) {
addToMap(clazzs,includeMap, type, fields)
}
fun filter(clazzs: Map?, Map>?,type: KClass<*>?, fields: Array) {
addToMap(clazzs,filterMap, type, fields)
}
private fun addToMap(clazzs: Map?, Map>?,map: MutableMap, Set>, type: KClass<*>?, fields: Array) {
val fieldSet = (map as Map, MutableSet>).getOrDefault(type, HashSet())
fieldSet.addAll(fields)
map[type!!] = fieldSet
this.clazzs=clazzs
}
override fun findFilter(filterId: Any): BeanPropertyFilter {
throw UnsupportedOperationException("Access to deprecated filters not supported")
}
override fun findPropertyFilter(filterId: Any, valueToFilter: Any?): PropertyFilter {
return object : SimpleBeanPropertyFilter() {
override fun serializeAsField(pojo: Any, jgen: JsonGenerator, prov: SerializerProvider, writer: PropertyWriter) {
if (apply(pojo.javaClass.kotlin, writer.name)) {
writer.serializeAsField(pojo, jgen, prov)
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, prov)
}
}
}
}
/**
* 用于判断pojo的当前属性"name"是否在@JSON标记的条件中(在include里或不在filter里),符合条件会把此属性及其值放入json中
*/
fun apply(type: KClass<*>, name: String) = includeMap[type].run { this!= null && this.contains(name) }||filterMap[type].run { this!= null && !this.contains(name) }||(includeMap[type] == null && filterMap[type] == null).run{clazzs?.filter {type.java.superclass.name==it.key!!.java.name&&(it.value.getValue("include").run {this.isNotBlank()&&this.contains(name) }||it.value.getValue("filter").run { this.isNotBlank()&&!this.contains(name)})}!!.any()}
}
这是这个问题的最终解决方案么?不尽然,因为本帖虽然解决了处理实体中实体的属性,但是没有去做自定义的工作,比如不同的实体(A、B)中,相同的实体(C)的属性是不一样的,不过因为我目前没有到这样的需求(笑)所以就不继续研究了。
最后解释一下apply方法,虽然只有一行(大雾)但是有点复杂。
/**
* 用于判断pojo的当前属性"name"是否在@JSON标记的条件中(在include里或不在filter里),符合条件会把此属性及其值放入json中
*/
fun apply(type: KClass<*>, name: String) = //处理当前实体的属性(includeMap、filterMap)
includeMap[type].run { this!= null && this.contains(name) }||//只有includeMap中存在name才返回这个属性
filterMap[type].run { this!= null && !this.contains(name) }||//如果filterMap存在name这个属性则不返回
(includeMap[type] == null && filterMap[type] == null).run{//当前实体中的实体的属性处理
clazzs?.filter {
type.java.superclass.name==it.key!!.java.name&&//确保当前的实体中的实体类在@JSONS中提到过
(it.value.getValue("include").run {//当前的实体的include处理
this.isNotBlank()&&this.contains(name) }||
it.value.getValue("filter").run { //当前的实体的filter处理
this.isNotBlank()&&!this.contains(name)
}
)
}!!.any()//最后判断当前实体中的实体的属性是否符合要求
}
使用例(突然变成JAVA)
@RequestMapping(value = {"/comment","/comment_{page:[0-9]+}_{size:[0-9]+}",Constants.SITE_PREFIX_PATH + "/comment",Constants.SITE_PREFIX_PATH + "/comment_{page:[0-9]+}_{size:[0-9]+}"},method = RequestMethod.POST)
@JSONS({@JSON(type = InfoComment.class,filter = "site,ip,score,status,anchor"),@JSON(type = Info.class,include = "id,title"),@JSON(type = User.class,include = "id,username,customs")})
public Object view(CommentService.FindPage findPage, @PathVariable(required = false) Integer page, @PathVariable(required = false) Integer size) {
Page pagedList = service.findPage(findPage.getFtype(), findPage.getFid(), findPage.getCreatorId(), findPage.getStatus(), findPage.getSiteId(), new PageRequest(page==null?0:page,size==null?Integer.MAX_VALUE:size));
return new ResponseBean("200", "操作成功", pagedList);
}
出至CSDN,禁止转载。