Spring boot 过滤返回对象属性遇到的各种坑
在很多的应用场景中,我们从后台查询出来的对象数据并不想把所有的字段返回到前台,特别是一些敏感的字段,如密码,解决这种问题有一下几种方式
- 可以查询数据库的时候不要查询出来,这里我不想讨论这个这种情况了
- 还有一种情况就是在实体中,如果某个字段不要显示,则在其get方法前加上注解@JsonIgnore
以上两种都不灵活,如果有的地方要显示这个字段,有的地方不要显示这个字段,这样就不好办了
那能不能通过自定义注解的方式,在要返回页面前对返回的实体字段进行过滤呢?这当然是可以的了
首先定义注解,如下所示
package com.sf.ams.json;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017年10月20日 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonFieldFilter {
Class<?> type();//对哪个类的属性进行过滤
String include() default "";//包含哪些字段,即哪些字段可以显示
String exclude() default "";//不包含哪些字段,即哪些字段不可以显示
}
在spring boot中用把Object转换成json用的是jackson,那么jackson是怎么过滤Object的字段的呢,是通过ObjectMapper,例子如下
package com.sf.ams.json;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017年10月20日 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
public class JsonFilterSerializer {
private static final String DYNC_INCLUDE = "DYNC_INCLUDE";//包含的标识
private static final String DYNC_EXCLUDE = "DYNC_EXCLUDE";//过滤的标识
private ObjectMapper mapper = new ObjectMapper();
@JsonFilter(DYNC_EXCLUDE)
interface DynamicExclude{
}
@JsonFilter(DYNC_INCLUDE)
interface DynamicInclude{
}
public void filter(Class<?> clazz , String include , String exclude) {
if (clazz == null) return;
if (include != null && include.length() > 0) {//包含的操作
mapper.setFilterProvider(new SimpleFilterProvider()
.addFilter(DYNC_INCLUDE, SimpleBeanPropertyFilter.filterOutAllExcept(include.split(","))));//多个字段用,分割开
mapper.addMixIn(clazz, DynamicInclude.class);
} else if (exclude != null && exclude.length() > 0) {
mapper.setFilterProvider(new SimpleFilterProvider()
.addFilter(DYNC_EXCLUDE, SimpleBeanPropertyFilter.serializeAllExcept(exclude.split(","))));
mapper.addMixIn(clazz, DynamicExclude.class);
}
}
public String toJson(Object object) throws JsonProcessingException{
return mapper.writeValueAsString(object);
}
@Test
public void testFilterJson() throws JsonProcessingException{
JsonFilterSerializer jsonFilter = new JsonFilterSerializer();
jsonFilter.filter(Response.class, null, "status");//Response中有status、failReason、currentIndex三个属性
System.out.println(jsonFilter.toJson(new Response("fail","失败了",-1)));
}
}
测试结果如下,status字段没有显示,测试成功
在springMVC中返回到页面前可以通过实现HandlerMethodReturnValueHandler接口,在方法handleReturnValue中可以通过MethodParameter类扫描是否含有@JsonFieldFilter 注解,如果有则过滤返回对象指定的字段,代码如下
package com.sf.ams.json;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* 描述:对加了JsonFieldFilter注解的方法进行字段过滤处理
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017年10月20日 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
public class JsonReturnHandler implements HandlerMethodReturnValueHandler{
@Override
public void handleReturnValue(Object returnObject, MethodParameter paramter,
ModelAndViewContainer container, NativeWebRequest request) throws Exception {
container.setRequestHandled(true);
JsonFilterSerializer serializer = new JsonFilterSerializer();
if(paramter.hasMethodAnnotation(JsonFieldFilter.class)) {//如果有JsonFieldFilter注解,则过滤返回的对象returnObject
JsonFieldFilter jsonFilter = paramter.getMethodAnnotation(JsonFieldFilter.class);
serializer.filter(jsonFilter.type() == null ?returnObject.getClass() : jsonFilter.type(), jsonFilter.include(), jsonFilter.exclude());//调用过滤方法
}
HttpServletResponse response = request.getNativeResponse(HttpServletResponse.class);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().write(serializer.toJson(returnObject));
}
@Override
public boolean supportsReturnType(MethodParameter methodParameter) {
return methodParameter.hasMethodAnnotation(JsonFieldFilter.class);
}
}
到这似乎只要写一个Controller加上注解就可以了,如下
package com.sf.ams.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.sf.ams.biz.IDataStoreXmlAnalysisBiz;
import com.sf.ams.entity.SystemInfo;
import com.sf.ams.entity.TreeNode;
import com.sf.ams.json.JsonFieldFilter;
import com.sf.ams.utils.TreeNodeUtil;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017-09-26 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
@Controller
@RequestMapping("/dataStoreAnalysis")
public class DataStoreAnalysisController {
@Autowired
private IDataStoreXmlAnalysisBiz dataStoreXmlAnalysisBiz;
@RequestMapping("/getSystemDetail")
@ResponseBody
@JsonFieldFilter(type = SystemInfo.class,exclude = "gitPassword")//把gitPassword字段过滤掉
public SystemInfo getSystemDetail(String systemCode){
return dataStoreXmlAnalysisBiz.getSystemBySystemCode(systemCode);
}
}
然而发现这样写并没有过滤掉字段gitPassword,debug发现代码根本没有进入类JsonReturnHandler的 handleReturnValue方法,问题出在哪?是不是没有告诉spring boot有JsonReturnHandler这个类,而我发现,如果在springmvc中只要在配置文件中加入以下配置就可以了,但spring boot只能通过注解。
<mvc:annotation-driven >
<mvc:return-value-handlers>
<bean class="com.sf.ams.json.JsonReturnHandler"/>
</mvc:return-value-handlers>
</mvc:annotation-driven>
后来发现在spring boot中需要加入配置告诉spring boot有自定义的HandlerMethodReturnValueHandler,代码如下
package com.sf.springboot;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.sf.ams.json.JsonReturnHandler;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017年10月20日 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
@Configuration
@ComponentScan(basePackages = {"com.sf.ams"},useDefaultFilters = true)
@EnableWebMvc
public class ApplicationConfig extends WebMvcConfigurerAdapter{
@Bean
public JsonReturnHandler jsonReturnHandler(){
return new JsonReturnHandler();//初始化json过滤器
}
@Override
public void addReturnValueHandlers(
List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(jsonReturnHandler());
}
}
@SpringBootApplication
@Import(value = {ApplicationConfig.class})
public class AutomaticSqlApplication {
public static void main(String[] args) {
SpringApplication.run(AutomaticSqlApplication.class, args);
}
}
配置需继承WebMvcConfigurerAdapter,重写addReturnValueHandlers方法,把JsonReturnHandler加入进去,继承的作用我这里就不详细写了,加入这个后重启spring boot,访问页面的时候直接报如下错
javax.servlet.ServletException: Could not resolve view with name 'index' in servlet with name 'dispatcherServlet'
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1305) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:870) ~[spring-webmvc-5.0.1.BUILD-SNAPSHOT.jar:5.0.1.BUILD-SNAPSHOT]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687) ~[javax.servlet-api-3.1.0.jar:3.1.0]
index.jsp页面怎么就会找不到了呢?就是因为加了这个配置项?
后来我把@EnableWebMvc去了,又可以正常访问了,但是注解@JsonFieldFilter并没有起作用,没有把属性过滤掉,后来查各种资料,发现@EnableWebMvc会改变Spring boot 对静态资源的配置,我的Spring boot的application.properties文件有如下配置
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
加入@EnableWebMvc后肯定改变了这两个值,既然跟这个有关系那么肯定跟WebMvcConfigurerAdapter也有关系,后来发现WebMvcConfigurerAdapter确实有静态资源有关的方法,addResourceHandlers和configureViewResolvers,重写这两个方法,把jsp文件的路径和文件的结尾告诉spring boot,加上以下代码
package com.sf.springboot;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.sf.ams.json.JsonReturnHandler;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017年10月20日 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
@Configuration
@ComponentScan(basePackages = {"com.sf.ams"},useDefaultFilters = true)
@EnableWebMvc
public class ApplicationConfig extends WebMvcConfigurerAdapter{
@Bean
public JsonReturnHandler jsonReturnHandler(){
return new JsonReturnHandler();
}
@Override
public void addReturnValueHandlers(
List<HandlerMethodReturnValueHandler> returnValueHandlers) {
returnValueHandlers.add(jsonReturnHandler());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/");//所有
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
}
系统文件路径如下
重新启动,不会在报错了,但是发现也还没有进入JsonReturnHandler类的handleReturnValue方法,当然就没有过滤掉gitPassword字段了,问题还是没有解决,是什么原因呢?发现配置项的
@Override
public void addReturnValueHandlers(
List returnValueHandlers) {
returnValueHandlers.add(jsonReturnHandler());
}
方法是把JsonReturnHandler加入到了返回值过滤list中,但是他们是有顺序的,最后加进来的是JsonReturnHandler,我们知道把对象转换成json的是通过注解@ResponseBody,而ResponseBody也应该是HandlerMethodReturnValueHandler中的一员,并且先处理ResponseBody,然后就直接返回了,就不处理JsonReturnHandler了,如果我们把@ResponseBody去了是不是不返回@ResponseBody了呢,最后Controller改成了如下代码,即把@ResponseBody去了
package com.sf.ams.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.sf.ams.biz.IDataStoreXmlAnalysisBiz;
import com.sf.ams.entity.SystemInfo;
import com.sf.ams.entity.TreeNode;
import com.sf.ams.json.JsonFieldFilter;
import com.sf.ams.utils.TreeNodeUtil;
/**
* 描述:
*
* <pre>HISTORY
* ****************************************************************************
* ID DATE PERSON REASON
* 1 2017-09-26 Simba.Hua Create
* ****************************************************************************
* </pre>
* @author Simba.Hua
*/
@Controller
@RequestMapping("/dataStoreAnalysis")
public class DataStoreAnalysisController {
@Autowired
private IDataStoreXmlAnalysisBiz dataStoreXmlAnalysisBiz;
@RequestMapping("/getSystemDetail")
@JsonFieldFilter(type = SystemInfo.class,exclude = "gitPassword")
public SystemInfo getSystemDetail(String systemCode){
return dataStoreXmlAnalysisBiz.getSystemBySystemCode(systemCode);
}
}
最后发现终于不显示gitPassword字段了
这里还是发现对spring boot和springmvc不够熟悉。以后要多练习