spring boot定制Jackson ObjectMapper,为什么不生效

先说结论:

项目中定制了spring 的redisTemplate,而这个template没有使用我自定义的Jackson ObjectMapper。所以不生效。

下面是详细过程:

起因是spring boot项目加入了shiro,我打算使用redis去存储shiro的会话,方便以后横向扩展。

参考了网上的实现后,决定通过扩展org.apache.shiro.session.mgt.eis.AbstractSessionDAO来实现。

以下是实现代码:

  1 package com.ceiec.baseplatform.config;
  2
  3 import com.ceiec.baseplatform.redis.StringKeyRedisTemplate;
  4 import org.apache.commons.collections.CollectionUtils;
  5 import org.apache.shiro.session.Session;
  6 import org.apache.shiro.session.UnknownSessionException;
  7 import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
  8 import org.slf4j.Logger;
  9 import org.slf4j.LoggerFactory;
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.stereotype.Component;
 12
 13 import java.io.Serializable;
 14 import java.util.Collection;
 15 import java.util.concurrent.TimeUnit;
 16
 17 @Component
 18 public class RedisSessionDAO extends AbstractSessionDAO {
 19     private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
 20
 21     @SuppressWarnings("rawtypes")
 22     @Autowired
 23     private StringKeyRedisTemplate<String, Object> redisTemplate;
 24
 25     private static final String DEFAULT_SESSION_KEY_PREFIX = "shirosession:";
 26
 27     private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;
 28
 29     private long expireTime = 120000;
 30
 31     public RedisSessionDAO() {
 32         super();
 33     }
 34
 35     public RedisSessionDAO(long expireTime) {
 36         super();
 37         this.expireTime = expireTime;
 38     }
 39
 40     @Override // 更新session
 41     public void update(Session session) throws UnknownSessionException {
 42         System.out.println("===============update================");
 43         if (session == null || session.getId() == null) {
 44             return;
 45         }
 46         session.setTimeout(expireTime);
 47         String key = getKey(session);
 48         redisTemplate.opsForValue().set(key, session, expireTime, TimeUnit.MILLISECONDS);
 49     }
 50
 51     private String getKey(Session session) {
 52         return this.keyPrefix + String.valueOf(session.getId());
 53     }
 54     private String getSessionIdKey(String sessionId) {
 55         return this.keyPrefix + String.valueOf(sessionId);
 56     }
 57
 58     @Override // 删除session
 59     public void delete(Session session) {
 60         System.out.println("===============delete================");
 61         if (null == session) {
 62             return;
 63         }
 64         redisTemplate.opsForValue().getOperations().delete(getKey(session));
 65     }
 66
 67     @Override
 68 // 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
 69     public Collection<Session> getActiveSessions() {
 70 //        System.out.println("==============getActiveSessions=================");
 71 //        return redisTemplate.keys("*");
 72         return CollectionUtils.EMPTY_COLLECTION;
 73     }
 74
 75     @Override// 加入session
 76     protected Serializable doCreate(Session session) {
 77         System.out.println("===============doCreate================");
 78         Serializable sessionId = this.generateSessionId(session);
 79         this.assignSessionId(session, sessionId);
 80
 81         redisTemplate.opsForValue().set(getKey(session), session, expireTime, TimeUnit.MILLISECONDS);
 82         return sessionId;
 83     }
 84
 85     @Override// 读取session
 86     protected Session doReadSession(Serializable sessionId) {
 87         System.out.println("==============doReadSession=================");
 88         if (sessionId == null) {
 89             return null;
 90         }
 91         return (Session) redisTemplate.opsForValue().get(getSessionIdKey(String.valueOf(sessionId)));
 92     }
 93
 94     public long getExpireTime() {
 95         return expireTime;
 96     }
 97
 98     public void setExpireTime(long expireTime) {
 99         this.expireTime = expireTime;
100     }
101
102
103 }

然后将该RedisSessionDao注册到sessionManager等,这个不在本文范围内,有兴趣可搜索相关shiro配置。

    @Autowired
    private RedisSessionDAO redisSessionDAO;

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO);
        securityManager.setSessionManager(sessionManager);
        //设置realm.
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

一切看起来不错。运行,登录,然后,报错了。

现在不太能重现那个错误,大概是,登录时,会把org.apache.shiro.session.mgt.SimpleSession的实例写入到redis。

其中有一个方法如下:

/** * @since 0.9 */public boolean isValid() {    return !isStopped() && !isExpired();}序列化时,会序列化一个valid:true的属性到json中。

而在后续读会话时,会反序列化session。然后报错,提示不认识valid属性。

于是在网上查询spring boot如何定制objectMapper去忽略不认识的属性,
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
按照网上说法和文档,如果只要替换ObjectMapper的话,只要按照下面说的这样去定义一个@Bean和@Primary标注的class。

但是在按照上述方法去配置后,发现没有效果。

后边想了很久。。。。突然发现自己的项目中,因为不想用jdk的序列化器,所以自定义了

Spring的RedisTemplate,去使用jackson的序列化器。而这个template中可能没有使用我自定义的ObjectMapper。

后边发现,果然如此。然后修改后如下:
package com.ceiec.baseplatform.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * desc: redis操作类
 * @author:
 * creat_date: 2018/1/4
 * creat_time: 17:18
 **/
@Component
public class StringKeyRedisTemplate<K, V> extends RedisTemplate<K, V> {

    /**
     * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
     * and {@link #afterPropertiesSet()} still need to be called.
     */
    public StringKeyRedisTemplate() {

    }

    /**
     * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
     *
     * @param connectionFactory connection factory for creating new connections
     */
    @Autowired
    public StringKeyRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        //设置key序列化器
        setKeySerializer(stringSerializer);
        setHashKeySerializer(stringSerializer);
        //设置value的序列化器
        ObjectMapper mapper = jacksonObjectMapper();
        setValueSerializer(new GenericJackson2JsonRedisSerializer(mapper));
        setHashValueSerializer(new GenericJackson2JsonRedisSerializer(mapper));

        setConnectionFactory(connectionFactory);
        afterPropertiesSet();
    }

    @Override
    protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
        return new DefaultStringRedisConnection(connection);
    }

    public ObjectMapper jacksonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        logger.info("construct complete! " + objectMapper);
        return objectMapper;
    }
}


原文地址:https://www.cnblogs.com/grey-wolf/p/8602382.html

时间: 2024-11-08 23:14:59

spring boot定制Jackson ObjectMapper,为什么不生效的相关文章

Spring boot与Jackson ObjectMapper

Spring Boot支持与三种JSON mapping库集成:Gson.Jackson和JSON-B.Jackson是首选和默认的. Jackson是spring-boot-starter-json的一部分,spring-boot-starter-web中包含spring-boot-starter-json.也就是说,当项目中引入spring-boot-starter-web后会自动引入spring-boot-starter-json. <dependency> <groupId>

Spring Boot 定制URL匹配规则的方法

事情的起源:有人问我,说编写了一个/hello访问路径,但是吧,不管是输入/hello还是/hello.html,还是/hello.xxx都能进行访问.当时我还以为他对代码进行处理了,后来发现不是,后来发现这是Spring Boot路由规则.好了,有废话了下,那么看看我们解决上面这个导致的问题. 构建web应用程序时,并不是所有的URL请求都遵循默认的规则.有时,我们希望RESTful URL匹配的时候包含定界符“.”,这种情况在Spring中可以称之为“定界符定义的格式”:有时,我们希望识别斜

Spring MVC或Spring Boot配置默认访问页面不生效?

相信在开发项目过程中,设置默认访问页面应该都用过.但是有时候设置了却不起作用.你知道是什么原因吗?今天就来说说我遇到的问题. 首先说说配置默认访问页面有哪几种方式. 1.tomcat配置默认访问页面 进入 tomcat 的 conf 目录,编辑 web.xml 文件.在 <web-app></web-app> 添加默认访问页面. <welcome-file-list> <welcome-file>index.html</welcome-file>

【Spring Boot】Spring Boot修改静态资源后立即生效

application.properties属性文件增加一行配置: spring.thymeleaf.cache=false

Spring boot 定制自己的错误

1).如何定制错误的页面: ? 1).有模板引擎的情况下:error/状态码; [将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下],发生此状态码的错误就会来到 对应的页面: ? 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html): ? 页面能获取的信息: ? timestamp:时间戳 ? status:状态码 ? error:错误提示 ? exception:异常对象 ? message:异常

Spring Boot项目中如何定制PropertyEditors

本文首发于个人网站:Spring Boot项目中如何定制PropertyEditors 在Spring Boot: 定制HTTP消息转换器一文中我们学习了如何配置消息转换器用于HTTP请求和响应数据,实际上,在一次请求的完成过程中还发生了其他的转换,我们这次关注将参数转换成多种类型的对象,如:字符串转换成Date对象或字符串转换成Integer对象. 在编写控制器中的action方法时,Spring允许我们使用具体的数据类型定义函数签名,这是通过PropertyEditor实现的.Propert

最全spring boot视频系列,你值得拥有

================================== 从零开始学Spring Boot视频 ================================== àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm_campaign=commission&utm_source=400000000155061&utm_medium=share [截止到201

43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

[视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm_campaign=commission&utm_source=400000000155061&utm_medium=share à SpringCloud视频 http://study.163.com/course/introduction.htm?courseId=1004638001&a

Spring Boot实战与原理分析

1:Spring Boot概述与课程概要介绍 2:Spring4 快速入门 3:Spring4 扩展分析(一) 4:Spring4 扩展分析(二) 5:Spring Boot 快速入门 6:Spring Boot 配置分析(一) 7:Spring Boot 配置分析(二) 8:Spring Boot 自动配置 9:Spring Boot @Enable*注解的工作原理 10:Spring Boot @EnableAutoConfiguration深入分析 11:Spring Boot 事件监听