Home

利用mybatis-generator自动生成代码

1、mybatis-generator 概述

MyBatis官方提供了逆向工程 mybatis-generator,可以针对数据库表自动生成MyBatis执行所需要的代码(如Mapper.java、Mapper.xml、POJO)。mybatis-generator 有三种用法:命令行、eclipse插件、maven插件。而maven插件的方式比较通用,本文也将概述maven插件的使用方式。

2、pom.xml中配置plugin

(官方文档:Running MyBatis Generator With Maven

<build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>
                        mybatis-generator/generatorConfig.xml
                    </configurationFile>
                    <overwrite>true</overwrite>
                    <verbose>true</verbose>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.47</version>
                    </dependency>
                    <dependency>
                        <groupId>com.itfsw</groupId>
                        <artifactId>mybatis-generator-plugin</artifactId>
                        <version>1.3.8</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

3、generatorConfig.xml配置文件

(官方文档:MyBatis GeneratorXML Configuration File Reference

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <context id="mysqlgenerator" targetRuntime="MyBatis3">
      <property name="autoDelimitKeywords" value="true"/>
      <!--可以使用``包括字段名,避免字段名与sql保留字冲突报错-->
      <property name="beginningDelimiter" value="`"/>
      <property name="endingDelimiter" value="`"/>

      <!-- 自动生成toString方法 -->
      <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
      <!-- 自动生成equals方法和hashcode方法 -->
      <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>

      <!-- 非官方插件 https://github.com/itfsw/mybatis-generator-plugin -->
      <!-- 查询单条数据插件 -->
      <plugin type="com.itfsw.mybatis.generator.plugins.SelectOneByExamplePlugin"/>
      <!-- MySQL分页插件 -->
      <plugin type="com.itfsw.mybatis.generator.plugins.LimitPlugin"/>
      <!-- 查询结果选择性返回插件 -->
      <plugin type="com.itfsw.mybatis.generator.plugins.SelectSelectivePlugin"/>
      <!-- Example Criteria 增强插件 -->
      <plugin type="com.itfsw.mybatis.generator.plugins.ExampleEnhancedPlugin"/>
      <!-- 数据Model属性对应Column获取插件 -->
      <plugin type="com.itfsw.mybatis.generator.plugins.ModelColumnPlugin"/>
      <!-- 逻辑删除插件 需配合数据库有对应的字段-->
      <plugin type="com.itfsw.mybatis.generator.plugins.LogicalDeletePlugin">
          <!-- 这里配置的是全局逻辑删除列和逻辑删除值,当然在table中配置的值会覆盖该全局配置 -->
          <!-- 逻辑删除列类型只能为数字、字符串或者布尔类型 -->
          <property name="logicalDeleteColumn" value="deleted"/>
          <!-- 逻辑删除-已删除值 -->
          <property name="logicalDeleteValue" value="1"/>
          <!-- 逻辑删除-未删除值 -->
          <property name="logicalUnDeleteValue" value="0"/>
      </plugin>

        <!-- Example 目标包修改插件 -->
        <plugin type="com.itfsw.mybatis.generator.plugins.ExampleTargetPlugin">
            <!-- 修改Example类生成到目标包下 -->
            <property name="targetPackage" value="org.linlinjava.litemall.db.domain.example"/>
        </plugin>

        <!-- 是否去除自动生成的注释 true:是 : false:否 -->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--数据库连接信息-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/litemall?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC&amp;verifyServerCertificate=false&amp;useSSL=false"
                        userId="litemall"
                        password="litemall123456"/>

        <javaTypeResolver>
            <property name="useJSR310Types" value="true"/>
        </javaTypeResolver>

        <javaModelGenerator targetPackage="org.linlinjava.litemall.db.domain" targetProject="src/main/java"/>
        <sqlMapGenerator targetPackage="org.linlinjava.litemall.db.mapper" targetProject="src/main/resources"/>
        <javaClientGenerator type="XMLMAPPER" targetPackage="org.linlinjava.litemall.db.mapper"
                             targetProject="src/main/java"/>
        <!--表名-->
        <table tableName="litemall_ad">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
        <table tableName="litemall_address">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
        <table tableName="litemall_admin">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="role_ids" javaType="java.lang.Integer[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler"/>
        </table>
        <table tableName="litemall_goods">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="gallery" javaType="java.lang.String[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonStringArrayTypeHandler"/>
        </table>
        <table tableName="litemall_goods_product">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="specifications" javaType="java.lang.String[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonStringArrayTypeHandler"/>
        </table>
        <table tableName="litemall_issue">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>

        <table tableName="litemall_order_goods">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="specifications" javaType="java.lang.String[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonStringArrayTypeHandler"/>
            <columnOverride column="comments" javaType="java.lang.Integer[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler"/>

        </table>
        <table tableName="litemall_topic">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="goods" javaType="java.lang.Integer[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler"/>
        <table tableName="litemall_coupon">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="goods_value" javaType="java.lang.Integer[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonIntegerArrayTypeHandler"/>
        </table>
        <table tableName="litemall_notice_admin">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
        </table>
        <table tableName="litemall_aftersale">
            <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            <columnOverride column="pictures" javaType="java.lang.String[]"
                            typeHandler="org.linlinjava.litemall.db.mybatis.JsonStringArrayTypeHandler"/>
        </table>
    </context>
</generatorConfiguration>

参考文档

Read more

1160. 拼写单词

  • 难度简单
  • 本题涉及算法哈希表记数
  • 思路哈希表记数
  • 类似题型:

题目 1160. 拼写单词

给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars。 假如你可以用 chars 中的『字母』(字符)拼写出 words 中的某个『单词』(字符串),那么我们就认为你掌握了这个单词。
注意:每次拼写时,chars 中的每个字母都只能用一次。
返回词汇表 words 中你掌握的所有单词的 长度之和

示例

示例 1:

输入:words = ["cat","bt","hat","tree"], chars = "atach"
输出:6
解释:
可以形成字符串 "cat" 和 "hat",所以答案是 3 + 3 = 6。

示例 2:

输入:words = ["hello","world","leetcode"], chars = "welldonehoneyr"
输出:10
解释:
可以形成字符串 "hello" 和 "world",所以答案是 5 + 5 = 10。

提示:

1 <= words.length <= 1000
1 <= words[i].length, chars.length <= 100
所有字符串中都仅包含小写英文字母

解题思路

  • 统计字母出现次数
  • 两个哈希表的键值对逐一进行比较即可

友情提示:

遇到有提示字符串仅包含小写(或者大写)英文字母的题,
都可以试着考虑能不能构造长度为26的每个元素分别代表一个字母的数组,来简化计算
对于这道题,用数组c来保存字母表里每个字母出现的次数
如法炮制,再对词汇表中的每个词汇都做一数组w,比较数组w与数组c的对应位置

  • 如果w中的都不大于c,就说明该词可以被拼写出,长度计入结果
  • 如果w其中有一个超过了c,则说明不可以被拼写,直接跳至下一个(这里用到了带label的continue语法)

代码

python

class Solution(object):
    def countCharacters(self, words, chars):
        """
        :type words: List[str]
        :type chars: str
        :rtype: int
        """
        res = 0
        for word in words:
            # for i in word:
            #     if word.count(i)<=chars.count(i):
            #         flag = 1
            #         continue
            #     else:
            #         flag = 0
            #         break
            # if flag == 1:
            #     res += len(word)
            if all(word.count(i)<=chars.count(i) for i in word):
                res += len(word)
        return res

java一

public class Solution {
    public int countCharacters(String[] words, String chars) {
        int[] c = new int[26];
        for(char cc:chars.toCharArray()) {
            c[(int)cc-'a']+=1;
        }
        int res = 0;
        a: for(String word:words) {
            int[] w = new int[26];
            for(char ww:word.toCharArray()) {
                w[(int)ww-'a']+=1;
            }
            for(int i=0;i<26;i++) {
                if(w[i]>c[i]) {
                    continue a;
                }
            }
            res += word.length();
        }
        return res;
    }
}

java二

class Solution {
    public int countCharacters(String[] words, String chars) {
        int[] abc = new int[26];
        for (char ch : chars.toCharArray()) {
            int index = ch - 'a';
            abc[index] += 1;
        }
        int sumlen = 0;
        a:
        for (String word : words) {
            int[] sub = abc.clone();// 注意这里 需要用拷贝,如果int[] sub = abc 指定的是同一个对象,
            int len = 0;
            for (char cha : word.toCharArray()) {
                int c = cha - 'a';
                if (sub[c] == 0) {
                    len = 0;
                    continue a;
                } else {
                    len += 1;
                    sub[c] -= 1;
                }
            }
            sumlen += len;
        }

        return sumlen;
    }
}

Read more

shiro源码篇 - shiro的session共享

github

前情回顾

shiro的session创建与session的查询、更新、过期、删除中,shiro对session的操作基本都讲到了,但还缺一个session共享没有讲解;session共享的原理其实在自定义session管理一文已经讲过了,本文不讲原理,只看看shiro的session共享的实现。

为何需要session共享

如果是单机应用,那么谈不上session共享,session放哪都无所谓,不在乎放到默认的servlet容器中,还是抽出来放到单独的地方;

也就是说session共享是针对集群(或分布式、或分布式集群)的;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。

如何实现session共享

实现方式其实有很多,甚至可以不做session共享,具体有哪些,大家自行去查资料。本文提供一种方式:redis实现session共享,就是将session从servlet容器抽出来,放到redis中存储,所有集群节点都从redis中对session进行操作。

SessionDAO

SessionDAO其实是用于session持久化的,但里面有缓存部分,具体细节我们往下看

shiro已有SessionDAO的实现如下

pic1

SessionDAO接口提供的方法如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;

import java.io.Serializable;
import java.util.Collection;


/**
 * 从EIS操作session的规范(EIS:例如关系型数据库, 文件系统, 持久化缓存等等, 具体依赖DAO实现)
 * 提供了典型的CRUD的方法:create, readSession, update, delete
 */
public interface SessionDAO {

    /**
     * 插入一个新的sesion记录到EIS
     */
    Serializable create(Session session);

    /**
     * 根据会话ID获取会话
     */
    Session readSession(Serializable sessionId) throws UnknownSessionException;

    /**
     * 更新session; 如更新session最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
     */
    void update(Session session) throws UnknownSessionException;

    /**
     * 删除session; 当会话过期/会话停止(如用户退出时)会调用
     */
    void delete(Session session);

    /**
     * 获取当前所有活跃session, 所有状态不是stopped/expired的session
     * 如果用户量多此方法影响性能
     */
    Collection<Session> getActiveSessions();
}

SessionDAO给出了从持久层(一般而言是关系型数据库)操作session的标准。

AbstractSessionDAO提供了SessionDAO的基本实现,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;


/**
 * SessionDAO的抽象实现, 在会话创建和读取时做一些健全性检查,并在需要时允许可插入的会话ID生成策略.
 * SessionDAO的update和delete则留给子类来实现
 * EIS需要子类自己实现
 */
public abstract class AbstractSessionDAO implements SessionDAO {

    /**
     * sessionId生成器
     */
    private SessionIdGenerator sessionIdGenerator;

    public AbstractSessionDAO() {
        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();    // 指定JavaUuidSessionIdGenerator为默认sessionId生成器
    }

    /**
     * 获取sessionId生成器
     */
    public SessionIdGenerator getSessionIdGenerator() {
        return sessionIdGenerator;
    }

    /**
     * 设置sessionId生成器
     */
    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
        this.sessionIdGenerator = sessionIdGenerator;
    }

    /**
     * 生成一个新的sessionId, 并将它应用到session实例
     */
    protected Serializable generateSessionId(Session session) {
        if (this.sessionIdGenerator == null) {
            String msg = "sessionIdGenerator attribute has not been configured.";
            throw new IllegalStateException(msg);
        }
        return this.sessionIdGenerator.generateId(session);
    }

    /**
     * SessionDAO中create实现; 将创建的sesion保存到EIS.
     * 子类doCreate方法的代理,具体的细节委托给了子类的doCreate方法
     */
    public Serializable create(Session session) {
        Serializable sessionId = doCreate(session);
        verifySessionId(sessionId);
        return sessionId;
    }

    /**
     * 保证从doCreate返回的sessionId不是null,并且不是已经存在的.
     * 目前只实现了null校验,是否已存在是没有校验的,可能shiro的开发者会在后续补上吧.
     */
    private void verifySessionId(Serializable sessionId) {
        if (sessionId == null) {
            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
            throw new IllegalStateException(msg);
        }
    }

    /**
     * 分配sessionId给session实例
     */
    protected void assignSessionId(Session session, Serializable sessionId) {
        ((SimpleSession) session).setId(sessionId);
    }

    /**
     * 子类通过实现此方法来持久化Session实例到EIS.
     */
    protected abstract Serializable doCreate(Session session);

    /**
     * SessionDAO中readSession实现; 通过sessionId从EIS获取session对象.
     * 子类doReadSession方法的代理,具体的获取细节委托给了子类的doReadSession方法.
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = doReadSession(sessionId);
        if (s == null) {
            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
        }
        return s;
    }

    /**
     * 子类通过实现此方法从EIS获取session实例
     */
    protected abstract Session doReadSession(Serializable sessionId);

}

SessionDao的基本实现,实现了SessionDao的create、readSession(具体还是依赖AbstractSessionDAO子类的doCreate、doReadSession实现);同时加入了自己的sessionId生成器,负责sessionId的操作。

CachingSessionDAO提供了session缓存的功能,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.CacheManagerAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;

/**
 * 应用层与持久层(EIS,如关系型数据库、文件系统、NOSQL)之间的缓存层实现
 * 缓存着所有激活状态的session
 * 实现了CacheManagerAware,会在shiro加载的过程中调用此对象的setCacheManager方法
 */
public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {

    /**
     * 激活状态的sesion的默认缓存名
     */
    public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";

    /**
     * 缓存管理器,用来获取session缓存
     */
    private CacheManager cacheManager;

    /**
     * 用来缓存session的缓存实例
     */
    private Cache<Serializable, Session> activeSessions;

    /**
     * session缓存名, 默认是ACTIVE_SESSION_CACHE_NAME.
     */
    private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;

    public CachingSessionDAO() {
    }

    /**
     * 设置缓存管理器
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    /**
     * 获取缓存管理器
     */
    public CacheManager getCacheManager() {
        return cacheManager;
    }

    /**
     * 获取缓存实例的名称,也就是获取activeSessionsCacheName的值
     */
    public String getActiveSessionsCacheName() {
        return activeSessionsCacheName;
    }

    /**
     * 设置缓存实例的名称,也就是设置activeSessionsCacheName的值
     */
    public void setActiveSessionsCacheName(String activeSessionsCacheName) {
        this.activeSessionsCacheName = activeSessionsCacheName;
    }

    /**
     * 获取缓存实例
     */
    public Cache<Serializable, Session> getActiveSessionsCache() {
        return this.activeSessions;
    }

    /**
     * 设置缓存实例
     */
    public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
        this.activeSessions = cache;
    }

    /**
     * 获取缓存实例
     * 注意:不会返回non-null值
     *
     * @return the active sessions cache instance.
     */
    private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
        if (this.activeSessions == null) {
            this.activeSessions = createActiveSessionsCache();
        }
        return activeSessions;
    }

    /**
     * 创建缓存实例
     */
    protected Cache<Serializable, Session> createActiveSessionsCache() {
        Cache<Serializable, Session> cache = null;
        CacheManager mgr = getCacheManager();
        if (mgr != null) {
            String name = getActiveSessionsCacheName();
            cache = mgr.getCache(name);
        }
        return cache;
    }

    /**
     * AbstractSessionDAO中create的重写
     * 调用父类(AbstractSessionDAO)的create方法, 然后将session缓存起来
     * 返回sessionId
     */
    public Serializable create(Session session) {
        Serializable sessionId = super.create(session);    // 调用父类的create方法
        cache(session, sessionId);                        // 以sessionId作为key缓存session
        return sessionId;
    }

    /**
     * 从缓存中获取session; 若sessionId为null,则返回null
     */
    protected Session getCachedSession(Serializable sessionId) {
        Session cached = null;
        if (sessionId != null) {
            Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
            if (cache != null) {
                cached = getCachedSession(sessionId, cache);
            }
        }
        return cached;
    }

    /**
     * 从缓存中获取session
     */
    protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
        return cache.get(sessionId);
    }

    /**
     * 缓存session,以sessionId作为key
     */
    protected void cache(Session session, Serializable sessionId) {
        if (session == null || sessionId == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache == null) {
            return;
        }
        cache(session, sessionId, cache);
    }

    protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
        cache.put(sessionId, session);
    }

    /**
     * AbstractSessionDAO中readSession的重写
     * 先从缓存中获取,若没有则调用父类的readSession方法获取session
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = getCachedSession(sessionId);        // 从缓存中获取
        if (s == null) {
            s = super.readSession(sessionId);           // 调用父类readSession方法获取
        }
        return s;
    }

    /**
     * SessionDAO中update的实现
     * 更新session的状态
     */
    public void update(Session session) throws UnknownSessionException {
        doUpdate(session);                               // 更新EIS中的session
        if (session instanceof ValidatingSession) {
            if (((ValidatingSession) session).isValid()) {
                cache(session, session.getId());         // 更新缓存中的session
            } else {
                uncache(session);                        // 移除缓存中的sesson
            }
        } else {
            cache(session, session.getId());
        }
    }

    /**
     * 由子类去实现,持久化session到EIS
     */
    protected abstract void doUpdate(Session session);

    /**
     * SessionDAO中delete的实现
     * 删除session
     */
    public void delete(Session session) {
        uncache(session);                                // 从缓存中移除
        doDelete(session);                               // 从EIS中删除
    }

    /**
     * 由子类去实现,从EIS中删除session
     */
    protected abstract void doDelete(Session session);

    /**
     * 从缓存中移除指定的session
     */
    protected void uncache(Session session) {
        if (session == null) {
            return;
        }
        Serializable id = session.getId();
        if (id == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache != null) {
            cache.remove(id);
        }
    }

    /**
     * SessionDAO中getActiveSessions的实现
     * 获取所有的存活的session
     */
    public Collection<Session> getActiveSessions() {
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache != null) {
            return cache.values();
        } else {
            return Collections.emptySet();
        }
    }
}

是应用层与持久化层之间的缓存层,不用频繁请求持久化层以提升效率。重写了AbstractSessionDAO中的create、readSession方法,实现了SessionDAO中的update、delete、getActiveSessions方法,预留doUpdate和doDelele给子类去实现(doXXX方法操作的是持久层)

MemorySessionDAO,SessionDAO的简单内存实现,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


/**
 * 基于内存的SessionDao的简单实现,所有的session存在ConcurrentMap中
 * DefaultSessionManager默认用的MemorySessionDAO
 */
public class MemorySessionDAO extends AbstractSessionDAO {

    private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);

    private ConcurrentMap<Serializable, Session> sessions;                // 存放session的容器

    public MemorySessionDAO() {
        this.sessions = new ConcurrentHashMap<Serializable, Session>();
    }

    // AbstractSessionDAO 中doCreate的重写; 将session存入sessions
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);        // 生成sessionId
        assignSessionId(session, sessionId);                        // 将sessionId赋值到session
        storeSession(sessionId, session);                            // 存储session到sessions
        return sessionId;
    }

    // 存储session到sessions
    protected Session storeSession(Serializable id, Session session) {
        if (id == null) {
            throw new NullPointerException("id argument cannot be null.");
        }
        return sessions.putIfAbsent(id, session);
    }

    // AbstractSessionDAO 中doReadSession的重写; 从sessions中获取session
    protected Session doReadSession(Serializable sessionId) {
        return sessions.get(sessionId);
    }

    // SessionDAO中update的实现; 更新sessions中指定的session
    public void update(Session session) throws UnknownSessionException {
        storeSession(session.getId(), session);
    }

    // SessionDAO中delete的实现; 从sessions中移除指定的session
    public void delete(Session session) {
        if (session == null) {
            throw new NullPointerException("session argument cannot be null.");
        }
        Serializable id = session.getId();
        if (id != null) {
            sessions.remove(id);
        }
    }

    // SessionDAO中SessionDAO中delete的实现的实现; 获取sessions中全部session
    public Collection<Session> SessionDAO中delete的实现() {
        Collection<Session> values = sessions.values();
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptySet();
        } else {
            return Collections.unmodifiableCollection(values);
        }
    }

}

将session保存在内存中,存储结构是ConcurrentHashMap;项目中基本不用,即使我们不实现自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。

EnterpriseCacheSessionDAO,提供了缓存功能的session维护,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.cache.AbstractCacheManager;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.MapCache;
import org.apache.shiro.session.Session;

import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;

public class EnterpriseCacheSessionDAO extends CachingSessionDAO {

    public EnterpriseCacheSessionDAO() {

        // 设置默认缓存器,并实例化MapCache作为cache实例
        setCacheManager(new AbstractCacheManager() {
            @Override
            protected Cache<Serializable, Session> createCache(String name) throws CacheException {
                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
            }
        });
    }

    // AbstractSessionDAO中doCreate的重写;
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        return sessionId;
    }

    // AbstractSessionDAO中doReadSession的重写
    protected Session doReadSession(Serializable sessionId) {
        return null; //should never execute because this implementation relies on parent class to access cache, which
        //is where all sessions reside - it is the cache implementation that determines if the
        //cache is memory only or disk-persistent, etc.
    }

    // CachingSessionDAO中doUpdate的重写
    protected void doUpdate(Session session) {
        //does nothing - parent class persists to cache.
    }

    // CachingSessionDAO中doDelete的重写
    protected void doDelete(Session session) {
        //does nothing - parent class removes from cache.
    }
}

设置了默认的缓存管理器(AbstractCacheManager)和默认的缓存实例(MapCache),实现了缓存效果。从父类继承的持久化操作方法(doXXX)都是空实现,也就说EnterpriseCacheSessionDAO是没有实现持久化操作的,仅仅只是简单的提供了缓存实现。当然我们可以继承EnterpriseCacheSessionDAO,重写doXXX方法来实现持久化操作。

总结下:SessionDAO定义了从持久层操作session的标准;AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的session缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行session维护;而EnterpriseCacheSessionDAO提供了缓存功能的session维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。因为shiro不知道我们需要将session持久化到哪里(关系型数据库,还是文件系统),所以只提供了MemorySessionDAO持久化到内存(听起来怪怪的,内存中能说成持久层吗)

shiro session共享

共享实现

shiro的session共享其实是比较简单的,重写CacheManager,将其操作指向我们的redis,然后实现我们自己的CachingSessionDAO定制缓存操作和缓存持久化。

自定义CacheManager

ShiroRedisCacheManager

package com.lee.shiro.config;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ShiroRedisCacheManager implements CacheManager {

    @Autowired
    private Cache shiroRedisCache;

    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return shiroRedisCache;
    }
}

ShiroRedisCache

package com.lee.shiro.config;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class ShiroRedisCache<K,V> implements Cache<K,V>{

    @Autowired
    private RedisTemplate<K,V> redisTemplate;

    @Value("${spring.redis.expireTime}")
    private long expireTime;

    @Override
    public V get(K k) throws CacheException {
        return redisTemplate.opsForValue().get(k);
    }

    @Override
    public V put(K k, V v) throws CacheException {
        redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
        V v = redisTemplate.opsForValue().get(k);
        redisTemplate.opsForValue().getOperations().delete(k);
        return v;
    }

    @Override
    public void clear() throws CacheException {
    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public Set<K> keys() {
        return null;
    }

    @Override
    public Collection<V> values() {
        return null;
    }
}

自定义CachingSessionDAO

继承EnterpriseCacheSessionDAO,然后重新设置其CacheManager(替换掉默认的内存缓存器),这样也可以实现我们的自定义CachingSessionDAO,但是这是优选吗;如若我们实现持久化,继承EnterpriseCacheSessionDAO是优选,但如果只是实现session缓存,那么CachingSessionDAO是优选,自定义更灵活。那么我们还是继承CachingSessionDAO来实现我们的自定义CachingSessionDAO

ShiroSessionDAO

package com.lee.shiro.config;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
public class ShiroSessionDAO extends CachingSessionDAO {

    @Override
    protected void doUpdate(Session session) {
    }

    @Override
    protected void doDelete(Session session) {
    }

    @Override
    protected Serializable doCreate(Session session) {
        // 这里绑定sessionId到session,必须要有
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        return null;
    }
}

最后将ShiroSessionDAO实例赋值给SessionManager实例,再讲SessionManager实例赋值给SecurityManager实例即可

具体代码请参考spring-boot-shiro

源码解析

底层还是利用Filter + HttpServletRequestWrapper将对session的操作接入到自己的实现中来,而不走默认的servlet容器,这样对session的操作完全由我们自己掌握。

shiro的session创建中其实讲到了shiro中对session操作的基本流程,这里不再赘述,没看的朋友可以先去看看再回过头来看这篇。本文只讲shiro中,如何将一个请求的session接入到自己的实现中来的;shiro中有很多默认的filter,我会单独开一篇来讲shiro的filter,这篇我们先不纠结这些filter。

OncePerRequestFilter中doFilter方法如下

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {    // 当前filter已经执行过了,进行下一个filter
        log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
        filterChain.doFilter(request, response);
    } else //noinspection deprecation
        if (/* added in 1.2: */ !isEnabled(request, response) ||
            /* retain backwards compatibility: */ shouldNotFilter(request) ) {    // 当前filter未被启用或忽略此filter,则进行下一个filter;shouldNotFilter已经被废弃了
        log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                getName());
        filterChain.doFilter(request, response);
    } else {
        // Do invoke this filter...
        log.trace("Filter '{}' not yet executed.  Executing now.", getName());
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

        try {
            // 执行当前filter
            doFilterInternal(request, response, filterChain);
        } finally {
            // 一旦请求完成,我们清除当前filter的"已经过滤"的状态
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }
}

pic2

上图中,我可以看到AbstractShiroFilter的doFilterInternal放中将request封装成了shiro自定义的ShiroHttpServletRequest,将response也封装成了shiro自定义的ShiroHttpServletResponse。既然Filter中将request封装了ShiroHttpServletRequest,那么到我们应用的request就是ShiroHttpServletRequest类型,也就是说我们对session的操作最终都是由shiro完成的,而不是默认的servlet容器。

另外补充一点,shiro的session创建不是懒创建的。servlet容器中的session创建是第一次请求session(第一调用request.getSession())时才创建。shiro的session创建如下图

pic3

此时,还没登录,但是subject、session已经创建了,只是subject的认证状态为false,说明还没进行登录认证的。至于session创建过程已经保存到redis的流程需要大家自行去跟,或者阅读我之前的博文

总结

  1. 当以集群方式对外提供服务的时候,不做session共享也是可以的

可以通过ip_hash的机制将同个ip的请求定向到同一台后端,这样保证用户的请求始终是同一台服务处理,与单机应用基本一致了;但这有很多方面的缺陷(具体就不详说了),不推荐使用。

  1. servlet容器之间做session同步也是可以实现session共享的

一个servlet容器生成session,其他节点的servlet容器从此servlet容器进行session同步,以达到session信息一致。这个也不推荐,某个时间点会有session不一致的问题,毕竟同步过程受到各方面的影响,不能保证session实时一致。

  1. session共享实现的原理其实都是一样的,都是filter + HttpServletRequestWrapper,只是实现细节会有所区别;有兴趣的可以看下spring-session的实现细节。
  2. 如果我们采用的spring集成shiro,其实可以将缓存管理器交由spring管理,相当于由spring统一管理缓存。
  3. shiro的CacheManager不只是管理session缓存,还管理着身份认证缓存、授权缓存,shiro的缓存都是CacheManager管理。但是身份认证缓存默认是关闭的,个人也不推荐开启。
  4. shiro的session创建时机是在登录认证之前,而不是第一次调用getSession()时。

Read more

if中的判断条件

  • equals( ) 比较字符串
  • equalsIgnoreCase() 忽略大小写比较字符串
  • contains() 集合中判断某个对象是否含有这个元素
  • match 用于正则表达式匹配

Read more

shiro的工具类-webUtils

这个类提供的很多方法对于我们的平时开发都很有帮助,并不仅仅是shiro内部的应用。

  1. getPathWithinApplication(HttpServletRequest),取得不包含应用路径的路径。

  2. normalize(String path)将路径修改之后正常显示,去掉或者是替换比如”/”“/.”“/../”“\”等。

  3. issueRedirect(ServletRequest, ServletResponse, String),重定向到指定的url。

  4. saveRequest(ServletRequest request),将访问的request保存起来,但是注意仅仅保存了url,method和queryString(这个值在post方式提交时是空,所以如果是post时没用,不要保存,因为没法保存参数)

  5. redirectToSavedRequest重定向到以前保存的request的路径,但是注意这个方法只对get方式的有效,post方式无效。如果是get的方法会将参数传过去。他是用一个SavedRequest类封装的,里面有三个属性:

private String method;

private String queryString;

private String requestURI;

举个例子:xxx?a=b

其中queryString记录了a=b,

requestURI表示xxx,

在这个类的getRequestUrl可以发现是将queryString和requestURI组合起来了,源码如下:

public String getRequestUrl() {
    StringBuilder requestUrl = new StringBuilder(getRequestURI());
    if (getQueryString() != null) {
        requestUrl.append("?").append(getQueryString());//将所有的参数都带上,但是只对get方法有效。
    }
    returnrequestUrl.toString();
}

Read more

一句话概念

  1. 事务使用的地方很少,其使用场景主要包括,1.设计到金融或金钱,2.多表操作 什么时候使用mysql事务
  2. 当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了 ArrayList和LinkedList的区别(含代码)
  3. 在做查询时 ArrayList 比LinkedList 好些
  4. select distinct m.perms distinct用于去重,方法解析
  5. Set: 去重,独一无二,无序 Java Set集合的详解
  6. BeanFactoryPostProcessor 实现该接口,可以对 bean 操作 Spring的BeanFactoryPostProcessor和BeanPostProcessor
  7. Server层中方法间调用需要使用 AopContext.currentProxy()获取代理类 如方法A调用方法BAOP切入同类调用方法-AopContext.currentProxy()
    ((Service)AopContext.currentProxy()).B()
    
  8. WebDataBinder 用于把参数绑定到javaBean ,和 @RequestBody @ReauestPram 类似 最全面阐述WebDataBinder理解Spring的数据绑定
  9. @InitBinder注解 被此注解的方法可以对 WebDataBinder 初始化 和WebDataBinder 一起使用 SpringMVC学习笔记15—–@InitBinder注解
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setDisallowedFields("name");
    }
    
  10. public int indexOf(int ch): 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
    String accept = request.getHeader("accept");
    if (accept != null && accept.indexOf("application/json") != -1)
    
  11. shiro提供的filter-AccessControlFilter 过滤器
  12. AtomicInteger 保证了并发时候的原子性 原子操作类AtomicInteger详解
  13. @AfterReturning(pointcut = “logPointCut()”, returning = “jsonResult”)//jsonResult 被注解方法的返回值
  14. ordinal():用于获取某个枚举对象的位置索引值
  15. HttpServletRequestWrapper类 类似于filter,可用于对用户输入的敏感字眼进行过滤用一个实例来说明HttpServletRequestWrapper类的使用
  16. AopContext.currentProxy 原理是创建代理类,当service间调用是需要用到 AOP切入同类调用方法-AopContext.currentProxy()
  17. 被final 修饰的属性,需要设置默认值,或者在构造方法中赋值,否则会编译错误
    public class ExpressAutoConfiguration {
    private final ExpressProperties properties;
    
    public ExpressAutoConfiguration(ExpressProperties properties) {
        this.properties = properties;
    }
    }
    
  18. @JsonProperty注解的使用 1.命名别名,序列化和反序列化
  19. ObjectMapper使用 对象与json字符串、byte数组间转化
  20. 遍历删除map集合中元素时,必须使用迭代iterator map遍历删除异常:ConcurrentModificationException
  21. springboot使用hibernate validator校验
  22. 为什么要设置链表头结点?
  23. springboot自定义参数解析HandlerMethodArgumentResolver 我们可以通过实现HandlerMethodArgumentResolver接口来实现对自定义的参数进行解析。 比如可以解析自定义的时间格式、自定义解析Map对象等这些spring原本不支持的对象格式
  24. 设计模式
  25. 对数组排序 Arrays.sort(a1)
  26. java.lang.NumberFormatException: For input string: “”
  27. 在spring boot中使用@WebFilter配置filter(包括排除URL) Spring Boot Filter不过滤指定资源,绕过不需要过滤的资源

Read more

MediaType源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.http;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.MimeType.SpecificityComparator;
import org.springframework.util.comparator.CompoundComparator;

public class MediaType extends MimeType implements Serializable {
    private static final long serialVersionUID = 2069937152339670231L;
    public static final MediaType ALL = valueOf("*/*");
    public static final String ALL_VALUE = "*/*";
    public static final MediaType APPLICATION_ATOM_XML = valueOf("application/atom+xml");
    public static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
    public static final MediaType APPLICATION_FORM_URLENCODED = valueOf("application/x-www-form-urlencoded");
    public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
    public static final MediaType APPLICATION_JSON = valueOf("application/json");
    public static final String APPLICATION_JSON_VALUE = "application/json";
    public static final MediaType APPLICATION_OCTET_STREAM = valueOf("application/octet-stream");
    public static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
    public static final MediaType APPLICATION_XHTML_XML = valueOf("application/xhtml+xml");
    public static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
    public static final MediaType APPLICATION_XML = valueOf("application/xml");
    public static final String APPLICATION_XML_VALUE = "application/xml";
    public static final MediaType IMAGE_GIF = valueOf("image/gif");
    public static final String IMAGE_GIF_VALUE = "image/gif";
    public static final MediaType IMAGE_JPEG = valueOf("image/jpeg");
    public static final String IMAGE_JPEG_VALUE = "image/jpeg";
    public static final MediaType IMAGE_PNG = valueOf("image/png");
    public static final String IMAGE_PNG_VALUE = "image/png";
    public static final MediaType MULTIPART_FORM_DATA = valueOf("multipart/form-data");
    public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
    public static final MediaType TEXT_HTML = valueOf("text/html");
    public static final String TEXT_HTML_VALUE = "text/html";
    public static final MediaType TEXT_PLAIN = valueOf("text/plain");
    public static final String TEXT_PLAIN_VALUE = "text/plain";
    public static final MediaType TEXT_XML = valueOf("text/xml");
    public static final String TEXT_XML_VALUE = "text/xml";
    private static final String PARAM_QUALITY_FACTOR = "q";
    public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
        public int compare(MediaType mediaType1, MediaType mediaType2) {
            double quality1 = mediaType1.getQualityValue();
            double quality2 = mediaType2.getQualityValue();
            int qualityComparison = Double.compare(quality2, quality1);
            if (qualityComparison != 0) {
                return qualityComparison;
            } else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) {
                return 1;
            } else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) {
                return -1;
            } else if (!mediaType1.getType().equals(mediaType2.getType())) {
                return 0;
            } else if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) {
                return 1;
            } else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) {
                return -1;
            } else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) {
                return 0;
            } else {
                int paramsSize1 = mediaType1.getParameters().size();
                int paramsSize2 = mediaType2.getParameters().size();
                return paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1);
            }
        }
    };
    public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MediaType>() {
        protected int compareParameters(MediaType mediaType1, MediaType mediaType2) {
            double quality1 = mediaType1.getQualityValue();
            double quality2 = mediaType2.getQualityValue();
            int qualityComparison = Double.compare(quality2, quality1);
            return qualityComparison != 0 ? qualityComparison : super.compareParameters(mediaType1, mediaType2);
        }
    };

    public MediaType(String type) {
        super(type);
    }

    public MediaType(String type, String subtype) {
        super(type, subtype, Collections.emptyMap());
    }

    public MediaType(String type, String subtype, Charset charset) {
        super(type, subtype, charset);
    }

    public MediaType(String type, String subtype, double qualityValue) {
        this(type, subtype, Collections.singletonMap("q", Double.toString(qualityValue)));
    }

    public MediaType(MediaType other, Map<String, String> parameters) {
        super(other.getType(), other.getSubtype(), parameters);
    }

    public MediaType(String type, String subtype, Map<String, String> parameters) {
        super(type, subtype, parameters);
    }

    protected void checkParameters(String attribute, String value) {
        super.checkParameters(attribute, value);
        if ("q".equals(attribute)) {
            value = this.unquote(value);
            double d = Double.parseDouble(value);
            Assert.isTrue(d >= 0.0D && d <= 1.0D, "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0");
        }

    }

    public double getQualityValue() {
        String qualityFactory = this.getParameter("q");
        return qualityFactory != null ? Double.parseDouble(this.unquote(qualityFactory)) : 1.0D;
    }

    public boolean includes(MediaType other) {
        return super.includes(other);
    }

    public boolean isCompatibleWith(MediaType other) {
        return super.isCompatibleWith(other);
    }

    public MediaType copyQualityValue(MediaType mediaType) {
        if (!mediaType.getParameters().containsKey("q")) {
            return this;
        } else {
            Map<String, String> params = new LinkedHashMap(this.getParameters());
            params.put("q", mediaType.getParameters().get("q"));
            return new MediaType(this, params);
        }
    }

    public MediaType removeQualityValue() {
        if (!this.getParameters().containsKey("q")) {
            return this;
        } else {
            Map<String, String> params = new LinkedHashMap(this.getParameters());
            params.remove("q");
            return new MediaType(this, params);
        }
    }

    public static MediaType valueOf(String value) {
        return parseMediaType(value);
    }

    public static MediaType parseMediaType(String mediaType) {
        MimeType type;
        try {
            type = MimeTypeUtils.parseMimeType(mediaType);
        } catch (InvalidMimeTypeException var4) {
            throw new InvalidMediaTypeException(var4);
        }

        try {
            return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
        } catch (IllegalArgumentException var3) {
            throw new InvalidMediaTypeException(mediaType, var3.getMessage());
        }
    }

    public static List<MediaType> parseMediaTypes(String mediaTypes) {
        if (!StringUtils.hasLength(mediaTypes)) {
            return Collections.emptyList();
        } else {
            String[] tokens = mediaTypes.split(",\\s*");
            List<MediaType> result = new ArrayList(tokens.length);
            String[] var3 = tokens;
            int var4 = tokens.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String token = var3[var5];
                result.add(parseMediaType(token));
            }

            return result;
        }
    }

    public static String toString(Collection<MediaType> mediaTypes) {
        return MimeTypeUtils.toString(mediaTypes);
    }

    public static void sortBySpecificity(List<MediaType> mediaTypes) {
        Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
        if (mediaTypes.size() > 1) {
            Collections.sort(mediaTypes, SPECIFICITY_COMPARATOR);
        }

    }

    public static void sortByQualityValue(List<MediaType> mediaTypes) {
        Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
        if (mediaTypes.size() > 1) {
            Collections.sort(mediaTypes, QUALITY_VALUE_COMPARATOR);
        }

    }

    public static void sortBySpecificityAndQuality(List<MediaType> mediaTypes) {
        Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
        if (mediaTypes.size() > 1) {
            Collections.sort(mediaTypes, new CompoundComparator(new Comparator[]{SPECIFICITY_COMPARATOR, QUALITY_VALUE_COMPARATOR}));
        }

    }
}

Read more

HttpStatus源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.http;

public enum HttpStatus {
    CONTINUE(100, "Continue"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols"),
    PROCESSING(102, "Processing"),
    CHECKPOINT(103, "Checkpoint"),
    OK(200, "OK"),
    CREATED(201, "Created"),
    ACCEPTED(202, "Accepted"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
    NO_CONTENT(204, "No Content"),
    RESET_CONTENT(205, "Reset Content"),
    PARTIAL_CONTENT(206, "Partial Content"),
    MULTI_STATUS(207, "Multi-Status"),
    ALREADY_REPORTED(208, "Already Reported"),
    IM_USED(226, "IM Used"),
    MULTIPLE_CHOICES(300, "Multiple Choices"),
    MOVED_PERMANENTLY(301, "Moved Permanently"),
    FOUND(302, "Found"),
    /** @deprecated */
    @Deprecated
    MOVED_TEMPORARILY(302, "Moved Temporarily"),
    SEE_OTHER(303, "See Other"),
    NOT_MODIFIED(304, "Not Modified"),
    /** @deprecated */
    @Deprecated
    USE_PROXY(305, "Use Proxy"),
    TEMPORARY_REDIRECT(307, "Temporary Redirect"),
    PERMANENT_REDIRECT(308, "Permanent Redirect"),
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "Unauthorized"),
    PAYMENT_REQUIRED(402, "Payment Required"),
    FORBIDDEN(403, "Forbidden"),
    NOT_FOUND(404, "Not Found"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
    NOT_ACCEPTABLE(406, "Not Acceptable"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
    REQUEST_TIMEOUT(408, "Request Timeout"),
    CONFLICT(409, "Conflict"),
    GONE(410, "Gone"),
    LENGTH_REQUIRED(411, "Length Required"),
    PRECONDITION_FAILED(412, "Precondition Failed"),
    PAYLOAD_TOO_LARGE(413, "Payload Too Large"),
    /** @deprecated */
    @Deprecated
    REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
    URI_TOO_LONG(414, "URI Too Long"),
    /** @deprecated */
    @Deprecated
    REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
    EXPECTATION_FAILED(417, "Expectation Failed"),
    I_AM_A_TEAPOT(418, "I'm a teapot"),
    /** @deprecated */
    @Deprecated
    INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"),
    /** @deprecated */
    @Deprecated
    METHOD_FAILURE(420, "Method Failure"),
    /** @deprecated */
    @Deprecated
    DESTINATION_LOCKED(421, "Destination Locked"),
    UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
    LOCKED(423, "Locked"),
    FAILED_DEPENDENCY(424, "Failed Dependency"),
    UPGRADE_REQUIRED(426, "Upgrade Required"),
    PRECONDITION_REQUIRED(428, "Precondition Required"),
    TOO_MANY_REQUESTS(429, "Too Many Requests"),
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
    NOT_IMPLEMENTED(501, "Not Implemented"),
    BAD_GATEWAY(502, "Bad Gateway"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
    VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
    INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
    LOOP_DETECTED(508, "Loop Detected"),
    BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
    NOT_EXTENDED(510, "Not Extended"),
    NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");

    private final int value;
    private final String reasonPhrase;

    private HttpStatus(int value, String reasonPhrase) {
        this.value = value;
        this.reasonPhrase = reasonPhrase;
    }

    public int value() {
        return this.value;
    }

    public String getReasonPhrase() {
        return this.reasonPhrase;
    }

    public boolean is1xxInformational() {
        return HttpStatus.Series.INFORMATIONAL.equals(this.series());
    }

    public boolean is2xxSuccessful() {
        return HttpStatus.Series.SUCCESSFUL.equals(this.series());
    }

    public boolean is3xxRedirection() {
        return HttpStatus.Series.REDIRECTION.equals(this.series());
    }

    public boolean is4xxClientError() {
        return HttpStatus.Series.CLIENT_ERROR.equals(this.series());
    }

    public boolean is5xxServerError() {
        return HttpStatus.Series.SERVER_ERROR.equals(this.series());
    }

    public HttpStatus.Series series() {
        return HttpStatus.Series.valueOf(this);
    }

    public String toString() {
        return Integer.toString(this.value);
    }

    public static HttpStatus valueOf(int statusCode) {
        HttpStatus[] var1 = values();
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            HttpStatus status = var1[var3];
            if (status.value == statusCode) {
                return status;
            }
        }

        throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
    }

    public static enum Series {
        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);

        private final int value;

        private Series(int value) {
            this.value = value;
        }

        public int value() {
            return this.value;
        }

        public static HttpStatus.Series valueOf(int status) {
            int seriesCode = status / 100;
            HttpStatus.Series[] var2 = values();
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                HttpStatus.Series series = var2[var4];
                if (series.value == seriesCode) {
                    return series;
                }
            }

            throw new IllegalArgumentException("No matching constant for [" + status + "]");
        }

        public static HttpStatus.Series valueOf(HttpStatus status) {
            return valueOf(status.value);
        }
    }
}

Read more