• spring与spring-data-redis整合redis


    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:p="http://www.springframework.org/schema/p"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xmlns:task="http://www.springframework.org/schema/task"
        xmlns:aop="http://www.springframework.org/schema/aop" 
        xmlns:mvc="http://www.springframework.org/schema/mvc"  
        xmlns:tx="http://www.springframework.org/schema/tx" 
        xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans    
                            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd    
                            http://www.springframework.org/schema/task 
                            http://www.springframework.org/schema/task/spring-task-4.0.xsd
                            http://www.springframework.org/schema/context    
                            http://www.springframework.org/schema/context/spring-context-4.0.xsd  
                            http://www.springframework.org/schema/tx 
                            http://www.springframework.org/schema/tx/spring-tx-4.0.xsd    
                            http://www.springframework.org/schema/aop 
                            http://www.springframework.org/schema/aop/spring-aop-4.0.xsd    
                            http://www.springframework.org/schema/mvc    
                            http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> 
    
        <!-- 
            其中order属性代表其加载顺序,
            而ignoreUnresolvablePlaceholders为是否忽略不可解析的 Placeholder,
            如配置了多个PropertyPlaceholderConfigurer,则需设置为true
            其中classpath是引用src目录下的文件写法。
             -->
        <bean id="propertyConfigurerRedis" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
            <property name="order" value="1" />  
            <property name="ignoreUnresolvablePlaceholders" value="true" />  
            <property name="locations">  
                <list>  
                    <value>classpath:redis.properties</value>  
                </list>  
            </property>  
        </bean>  
        
        <!-- 单个redis配置 -->
         <!-- redis连接池的配置 -->
        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
          <property name="maxIdle" value="${redis.maxIdle}"/>
          <property name="minIdle" value="${redis.minIdle}"/>
          <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
          <property name="testOnReturn" value="${redis.testOnReturn}"/>
        </bean>
        <!-- redis的连接池pool,不是必选项:timeout/password  -->
        <bean id = "jedisPool" class="redis.clients.jedis.JedisPool">
          <constructor-arg index="0" ref="jedisPoolConfig"/>
          <constructor-arg index="1" value="${redis.host}"/>
          <constructor-arg index="2" value="${redis.port}" type="int"/> <!-- port-->
          <constructor-arg index="3" value="${redis.timeout}" type="int"/> <!-- timeout -->
          <constructor-arg index="4" value="${redis.password}"/>  <!-- password -->
        </bean>
        
        <!-- 以下是spring-data-redis配置方式 -->
        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  
            p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.password}" p:pool-config-ref="jedisPoolConfig"/>
          
        
         <!--  SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
            StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
            RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
            就是因为序列化策略的不同,即使是同一个key用不同的Template去序列化,结果是不同的。所以根据key去删除数据的时候就出现了删除失败的问题。 
         -->
        <!-- redis 序列化策略 ,通常情况下key值采用String序列化策略, -->
        <!-- 如果不指定序列化策略,StringRedisTemplate的key和value都将采用String序列化策略; -->
        <!-- 但是RedisTemplate的key和value都将采用JDK序列化 这样就会出现采用不同template保存的数据不能用同一个template删除的问题 -->
        <bean id="stringRedisSerializer"  class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
            <property name="connectionFactory" ref="connectionFactory" /> 
            <property name="keySerializer" ref="stringRedisSerializer" />
            <property name="hashKeySerializer" ref="stringRedisSerializer" />
            <property name="valueSerializer" ref="stringRedisSerializer"/>
        </bean>
        
        
        
        
        <!-- redis jedisCluster集群配置 -->
        <!-- <bean name="genericObjectPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig" >
                <property name="maxWaitMillis" value="-1" />
                <property name="maxTotal" value="1000" />
                <property name="minIdle" value="8" />
                <property name="maxIdle" value="100" />
        </bean>
        <bean id="jedisCluster" class="cn.zsmy.palmdoctor.redis.JedisClusterFactory">
            <property name="addressConfig">
                <value>classpath:redis.properties</value>
            </property>
            <property name="addressKeyPrefix" value="address" />    属性文件里  key的前缀
            <property name="password" value="123456" /> redis密码
            <property name="timeout" value="300000" />
            <property name="maxRedirections" value="6" />
            <property name="genericObjectPoolConfig" ref="genericObjectPoolConfig" />
        </bean> -->
        
        
    </beans>

    spring-context-jedis-cluster.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    
    
    <description>Jedis Cluster Configuration集群</description>
    <!-- 加载配置属性文件 按需加载 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath:redis-cluster.properties" />
    <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
    <property name="maxRedirects" value="${redis.maxRedirects}"></property>
    <property name="clusterNodes">
    <set>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host1}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port1}"></constructor-arg>
    </bean>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host2}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port2}"></constructor-arg>
    </bean>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host3}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port3}"></constructor-arg>
    </bean>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host4}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port4}"></constructor-arg>
    </bean>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host5}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port5}"></constructor-arg>
    </bean>
    <bean class="org.springframework.data.redis.connection.RedisClusterNode">
    <constructor-arg name="host" value="${redis.host6}"></constructor-arg>
    <constructor-arg name="port" value="${redis.port6}"></constructor-arg>
    </bean>
    </set>
    </property>
    </bean>
    <bean id="jedisPoolConfig"   class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxIdle" value="${redis.maxIdle}" /> 
    <property name="maxTotal" value="${redis.maxTotal}" /> 
       </bean>
    <bean id="jeidsConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"  >
    <constructor-arg ref="redisClusterConfiguration" />
    <constructor-arg ref="jedisPoolConfig" />
    </bean>
    
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jeidsConnectionFactory" />
    </bean>
    </beans>
    redis-cluster.properties
    #cluster configuration
    redis.host1=xx.xx.xx.xx
    redis.port1=7000
    
    redis.host2=xx.xx.xx.xx
    redis.port2=7001
    
    redis.host3=xx.xx.xx.xx
    redis.port3=7002
    
    redis.host4=xx.xx.xx.xx
    redis.port4=7000
    
    redis.host5=xx.xx.xx.xx
    redis.port5=7001
    
    redis.host6=xx.xx.xx.xx
    redis.port6=7002
    redis.maxRedirects=3
    redis.maxIdle=100
    redis.maxTotal=600
    package cn.zsmy.palmdoctor.redis;
    
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.BoundSetOperations;
    import org.springframework.data.redis.core.HashOperations;
    import org.springframework.data.redis.core.ListOperations;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    @Service
    public class RedisUtil
    {
    
        @Autowired 
        @Qualifier("redisTemplate")
        public RedisTemplate<String, Object> redisTemplate;
        
        @Autowired
        @Qualifier("redisTemplate")
           protected RedisTemplate<Serializable, Serializable> redisTemplateSerializable;
        
        /**
         * 缓存基本的对象,Integer、String、实体类等
         * @param key    缓存的键值
         * @param value    缓存的值
         * @return        缓存的对象
         */
        public void setCacheObject(String key, Object value)
        {
            redisTemplate.opsForValue().set(key,value);
        }
        
        /**
         * 获得缓存的基本对象。
         * @param key        缓存键值
         * @param operation
         * @return            缓存键值对应的数据
         */
        public Object getCacheObject(String key/*,ValueOperations<String,T> operation*/)
        {
            return redisTemplate.opsForValue().get(key); 
        }
        
        /**
         * 缓存List数据
         * @param key        缓存的键值
         * @param dataList    待缓存的List数据
         * @return            缓存的对象
         */
        public Object setCacheList(String key, List<Object> dataList)
        {
            ListOperations<String, Object> listOperation = redisTemplate.opsForList();
            if(null != dataList)
            {
                int size = dataList.size();
                for(int i = 0; i < size ; i ++)
                {
                    listOperation.rightPush(key,dataList.get(i));
                }
            }
            return listOperation;
        }
        
        /**
         * 获得缓存的list对象
         * @param key    缓存的键值
         * @return        缓存键值对应的数据
         */
        public List<Object> getCacheList(String key)
        {
            List<Object> dataList = new ArrayList<Object>();
            ListOperations<String, Object> listOperation = redisTemplate.opsForList();
            Long size = listOperation.size(key);
            
            for(int i = 0 ; i < size ; i ++)
            {
                dataList.add(listOperation.leftPop(key));
            }
            return dataList;
        }
        
        /**
         * 获得缓存的list对象
         * @Title: range 
         * @Description: TODO(这里用一句话描述这个方法的作用) 
         * @param @param key
         * @param @param start
         * @param @param end
         * @param @return    
         * @return List<T>    返回类型 
         * @throws
         */
        public List<Object> range(String key, long start, long end)
        {
            ListOperations<String, Object> listOperation = redisTemplate.opsForList();
            return listOperation.range(key, start, end);
        }
        
        /** 
         * list集合长度
         * @param key 
         * @return 
         */  
        public Long listSize(String key) {  
            return redisTemplate.opsForList().size(key);  
        }  
        
        /**
         * 覆盖操作,将覆盖List中指定位置的值
         * @param key
         * @param int index 位置
         * @param String
         *            value 值
         * @return 状态码
         * */
        public void listSet(String key, int index, Object obj) {
            redisTemplate.opsForList().set(key, index, obj);
        }
            
        /**
         * 向List尾部追加记录
         * 
         * @param String
         *            key
         * @param String
         *            value
         * @return 记录总数
         * */
        public long leftPush(String key, Object obj) {
            return redisTemplate.opsForList().leftPush(key, obj);
        }
    
        /**
         * 向List头部追加记录
         * 
         * @param String
         *            key
         * @param String
         *            value
         * @return 记录总数
         * */
        public long rightPush(String key, Object obj) {
            return redisTemplate.opsForList().rightPush(key, obj);
        }
        
        /**
         * 算是删除吧,只保留start与end之间的记录
         * 
         * @param String
         *            key
         * @param int start 记录的开始位置(0表示第一条记录)
         * @param int end 记录的结束位置(如果为-1则表示最后一个,-2,-3以此类推)
         * @return 执行状态码
         * */
        public void trim(String key, int start, int end) {
            redisTemplate.opsForList().trim(key, start, end);
        }
        
        /**
         * 删除List中c条记录,被删除的记录值为value
         * 
         * @param String
         *            key
         * @param int c 要删除的数量,如果为负数则从List的尾部检查并删除符合的记录
         * @param Object
         *            obj 要匹配的值
         * @return 删除后的List中的记录数
         * */
        public long remove(String key, long i, Object obj) {
            return redisTemplate.opsForList().remove(key, i, obj);
        }
        
        /**
         * 缓存Set
         * @param key        缓存键值
         * @param dataSet    缓存的数据
         * @return            缓存数据的对象
         */
        public BoundSetOperations<String, Object> setCacheSet(String key,Set<Object> dataSet)
        {
            BoundSetOperations<String, Object> setOperation = redisTemplate.boundSetOps(key);    
            /*T[] t = (T[]) dataSet.toArray();
                 setOperation.add(t);*/
            
            Iterator<Object> it = dataSet.iterator();
            while(it.hasNext())
            {
                setOperation.add(it.next());
            }
            return setOperation;
        }
        
        /**
         * 获得缓存的set
         * @param key
         * @param operation
         * @return
         */
        public Set<Object> getCacheSet(String key/*,BoundSetOperations<String,T> operation*/)
        {
            Set<Object> dataSet = new HashSet<Object>();
            BoundSetOperations<String,Object> operation = redisTemplate.boundSetOps(key);    
            
            Long size = operation.size();
            for(int i = 0 ; i < size ; i++)
            {
                dataSet.add(operation.pop());
            }
            return dataSet;
        }
        
        /**
         * 缓存Map
         * @param key
         * @param dataMap
         * @return
         */
        public int setCacheMap(String key,Map<String, Object> dataMap)
        {
            if(null != dataMap)
            {
                HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
                for (Map.Entry<String, Object> entry : dataMap.entrySet()) {  
                    /*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  */
                    if(hashOperations != null){
                        hashOperations.put(key,entry.getKey(),entry.getValue());
                    } else{
                        return 0;
                    }
                } 
            } else{
                return 0;
            }
            return dataMap.size();
        }
        
        /**
         * 获得缓存的Map
         * @param key
         * @param hashOperation
         * @return
         */
        public Map<Object, Object> getCacheMap(String key/*,HashOperations<String,String,T> hashOperation*/)
        {
            Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
            /*Map<String, T> map = hashOperation.entries(key);*/
            return map;
        }
        
        /**
         * 缓存Map
         * @param key
         * @param dataMap
         * @return
         */
        public void setCacheIntegerMap(String key,Map<Integer, Object> dataMap)
        {
            HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
            if(null != dataMap)
            {
                for (Map.Entry<Integer, Object> entry : dataMap.entrySet()) {  
                    /*System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  */
                    hashOperations.put(key,entry.getKey(),entry.getValue());
                } 
                
            }
        }
        
        /**
         * 获得缓存的Map
         * @param key
         * @param hashOperation
         * @return
         */
        public Map<Object, Object> getCacheIntegerMap(String key/*,HashOperations<String,String,T> hashOperation*/)
        {
            Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
            /*Map<String, T> map = hashOperation.entries(key);*/
            return map;
        }
        
        /**
         * 从hash中删除指定的存储
         * 
         * @param String
         * @return 状态码,1成功,0失败
         * */
        public long deleteMap(String key) {
            redisTemplate.setEnableTransactionSupport(true);
            return redisTemplate.opsForHash().delete(key);
        }
        
         /**
          * 设置过期时间
          * @param key
          * @param time
          * @param unit
          * @return
          */
        public boolean expire(String key, long time, TimeUnit unit) {
            return redisTemplate.expire(key, time, unit);
        }
        
        /**
         * increment
         * @param key
         * @param step
         * @return
         */
        public long increment(String key, long step) {
            return redisTemplate.opsForValue().increment(key, step);
        }
    
        
        //redisTemplateSerializable
        
        /**
         * 删除redis的所有数据
         */
        /*@SuppressWarnings({"unchecked", "rawtypes"})
        public String flushDB() {
            return redisTemplateSerializable.execute(new RedisCallback() {
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    connection.flushDb();
                    return "ok";
                }
            });
        }*/
        
        public long del(final byte[] key) {  
            return (long) redisTemplateSerializable.execute(new RedisCallback<Object>() {  
                public Long doInRedis(RedisConnection connection) {  
                    return connection.del(key);  
                }  
            });  
        }  
    
        @SuppressWarnings({"unchecked", "rawtypes"})
        public byte[] get(final byte[] key) {
            return (byte[]) redisTemplateSerializable.execute(new RedisCallback() {
                public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.get(key);
                }
            });
        }
        
        /**
         * @param key
         * @param value
         * @param liveTime
         */
        public void set(final byte[] key, final byte[] value, final long liveTime) {
            redisTemplateSerializable.execute(new RedisCallback<Object>() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    connection.set(key, value);
                    if (liveTime > 0) {
                        connection.expire(key, liveTime);
                    }
                    return 1L;
                }
            });
        }
    
    }
    package cn.zsmy.palmdoctor.redis;
    
    import java.util.HashSet;
    import java.util.Properties;
    import java.util.Set;
    import java.util.regex.Pattern;
    
    import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.core.io.Resource;
    
    import redis.clients.jedis.HostAndPort;
    import redis.clients.jedis.JedisCluster;
    
    public class JedisClusterFactory implements FactoryBean<JedisCluster>, InitializingBean {
    
        private Resource addressConfig;
        private String addressKeyPrefix ;
        private String password;
    
        private JedisCluster jedisCluster;
        private Integer timeout;
        private Integer maxRedirections;
        private GenericObjectPoolConfig genericObjectPoolConfig;
        
        private Pattern p = Pattern.compile("^.+[:]\d{1,5}\s*$");
    
        @Override
        public JedisCluster getObject() throws Exception {
            return jedisCluster;
        }
    
        @Override
        public Class<? extends JedisCluster> getObjectType() {
            return (this.jedisCluster != null ? this.jedisCluster.getClass() : JedisCluster.class);
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    
    
    
        private Set<HostAndPort> parseHostAndPort() throws Exception {
            try {
                Properties prop = new Properties();
                prop.load(this.addressConfig.getInputStream());
    
                Set<HostAndPort> haps = new HashSet<HostAndPort>();
                for (Object key : prop.keySet()) {
    
                    if (!((String) key).startsWith(addressKeyPrefix)) {
                        continue;
                    }
    
                    String val = (String) prop.get(key);
    
                    boolean isIpPort = p.matcher(val).matches();
    
                    if (!isIpPort) {
                        throw new IllegalArgumentException("ip 或 port 不合法");
                    }
                    String[] ipAndPort = val.split(":");
    
                    HostAndPort hap = new HostAndPort(ipAndPort[0], Integer.parseInt(ipAndPort[1]));
                    haps.add(hap);
                }
    
                return haps;
            } catch (IllegalArgumentException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new Exception("解析 jedis 配置文件失败", ex);
            }
        }
        
        @Override
        public void afterPropertiesSet() throws Exception {
            Set<HostAndPort> haps = this.parseHostAndPort();
            
            //jedisCluster = new JedisCluster(haps, timeout, maxRedirections,genericObjectPoolConfig);
            jedisCluster = new JedisCluster(haps, timeout, 2000, maxRedirections, password, genericObjectPoolConfig);
        }
        public void setAddressConfig(Resource addressConfig) {
            this.addressConfig = addressConfig;
        }
    
        public void setTimeout(int timeout) {
            this.timeout = timeout;
        }
    
        public void setMaxRedirections(int maxRedirections) {
            this.maxRedirections = maxRedirections;
        }
    
        public void setAddressKeyPrefix(String addressKeyPrefix) {
            this.addressKeyPrefix = addressKeyPrefix;
        }
    
        public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) {
            this.genericObjectPoolConfig = genericObjectPoolConfig;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
    }
    package cn.zsmy.palmdoctor.redis;
    
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.Closeable;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    import cn.zsmy.constant.Constant;
    
    /**
     * @author shm
     */
    public class SerializeUtil {
    
        public static byte[] serialize(Object value) {
            if (value == null) {
                throw new NullPointerException("Can't serialize null");
            }
            byte[] rv = null;
            ByteArrayOutputStream bos = null;
            ObjectOutputStream os = null;
            try {
                bos = new ByteArrayOutputStream();
                os = new ObjectOutputStream(bos);
                os.writeObject(value);
                os.close();
                bos.close();
                rv = bos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
                Constant.MY_LOG.info("serialize error");
            } finally {
                close(os);
                close(bos);
            }
            return rv;
        }
    
        public static Object deserialize(byte[] in) {
            return deserialize(in, Object.class);
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T deserialize(byte[] in, Class<T> requiredType) {
            Object rv = null;
            ByteArrayInputStream bis = null;
            ObjectInputStream is = null;
            try {
                if (in != null) {
                    bis = new ByteArrayInputStream(in);
                    is = new ObjectInputStream(bis);
                    rv = is.readObject();
                }
            } catch (Exception e) {
                e.printStackTrace();
                Constant.MY_LOG.info("deserialize error");
            } finally {
                close(is);
                close(bis);
            }
            return (T) rv;
        }
    
        private static void close(Closeable closeable) {
            if (closeable != null)
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Constant.MY_LOG.info("close stream error");
                }
        }
    
    }
  • 相关阅读:
    代码检查工具介绍
    Eclipse利用代理快速安装插件
    toString结果
    Eclipse查看jdk源码
    java语言基础特性
    TODO、FIXME和XXX转载
    java泛型
    不良代码总结
    mockServer学习
    akka
  • 原文地址:https://www.cnblogs.com/shihaiming/p/6050777.html
Copyright © 2020-2023  润新知