前言
在Spring Framework里的spring-core核心包里面,有个org.springframework.util里面有不少非常实用的工具类。
该工具包里面的工具类虽然是被定义在Spring下面的,但是由于Spring框架目前几乎成了JavaEE实际的标准了,因此我们直接使用也是无妨的,很多时候能够大大的提高我们的生产力。本文主要介绍一些个人认为还非常实用的工具类,仅仅代表个人意见哦~
此处提到的工具类为纯工具类。与Spring的Bean没有关系,是最为共用的工具类
IdGenerator 唯一键生成器 UUID
UUID除了生成的字符串比较长以外,几乎没有缺点(当然用字符串做主键,也算一个小缺点吧)
Spring给我提供了接口:IdGenerator 来生成id代表唯一箭,它内置提供了三个实现:
JdkIdGenerator
JDK的工具类包util包中就为我们提供了一个很好的工具类,即UUID。UUID(Universally Unique Identifier)通用唯一识别码。
@Override public UUID generateId() { return UUID.randomUUID(); }
底层字节调用JDK的UUID方法,因此不做过多介绍了
AlternativeJdkIdGenerator
这是Spring提供给我们的重头戏,用它来取代JDK的UUID的生成。从它的javadoc说明:
* An {@link IdGenerator} that uses {@link SecureRandom} for the initial seed and * {@link Random} thereafter, instead of calling {@link UUID#randomUUID()} every * time as {@link org.springframework.util.JdkIdGenerator JdkIdGenerator} does. * This provides a better balance between securely random ids and performance.
大意是:它使用了SecureRandom作为种子,来替换调用UUID#randomUUID()。它提供了一个更好、更高性能的表现(关于性能比较,下面会给出一个例子)
SimpleIdGenerator
类似于自增的Id生成器。每调用一次,自增1(一般比较少使用 指导就行了)
三者性能比较:
public static void main(String[] args) { JdkIdGenerator jdkIdGenerator = new JdkIdGenerator(); AlternativeJdkIdGenerator alternativeJdkIdGenerator = new AlternativeJdkIdGenerator(); SimpleIdGenerator simpleIdGenerator = new SimpleIdGenerator(); Instant start; Instant end; int count = 1000000; //jdkIdGenerator start = Instant.now(); for (int i = 0; i < count; i++) { jdkIdGenerator.generateId(); } end = Instant.now(); System.out.println("jdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms"); //alternativeJdkIdGenerator start = Instant.now(); for (int i = 0; i < count; i++) { alternativeJdkIdGenerator.generateId(); } end = Instant.now(); System.out.println("alternativeJdkIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms"); //simpleIdGenerator start = Instant.now(); for (int i = 0; i < count; i++) { simpleIdGenerator.generateId(); } end = Instant.now(); System.out.println("simpleIdGenerator循环" + count + "次耗时:" + Duration.between(start, end).toMillis() + "ms"); }
发现仅仅循环生成100万次,Spring提供的算法性能远远高于JDK的。因此建议大家以后使用AlternativeJdkIdGenerator去生成UUID,性能会更好一点
缺点是:还需要new对象才能使用,不能通过类名直接调用静态方法,当然我们可以二次封装。另外,一般输出串我们都会进一步这么处理:.toString().replace("-", "")
Assert 断言工具类
Assert断言工具类,通常用于数据合法性检查。Assert断言工具类,通常用于数据合法性检查.
if (message== null || message.equls("")) { throw new IllegalArgumentException("输入信息错误!"); }
用Assert工具类上面的代码可以简化为:Assert.hasText((message, "输入信息错误!");
下面介绍常用的断言方法的使用:
Assert.notNull(Object object, "object is required") - 对象非空 Assert.isTrue(Object object, "object must be true") - 对象必须为true Assert.notEmpty(Collection collection, "collection must not be empty") - 集合非空 Assert.hasLength(String text, "text must be specified") - 字符不为null且字符长度不为0 Assert.hasText(String text, "text must not be empty") - text 不为null且必须至少包含一个非空格的字符 Assert.isInstanceOf(Class clazz, Object obj, "clazz must be of type [clazz]") - obj必须能被正确造型成为clazz 指定的类
junit也提供断言工具类,但是我们只能在单元测试中使用,而Spring提供的这个,哪儿都能使用,还是比较方便的
PathMatcher 路径匹配器
Spring提供的实现:AntPathMatcher Ant路径匹配规则
(1)SpringMVC的路径匹配规则是依照Ant的来的,实际上不只是SpringMVC,整个Spring框架的路径解析都是按照Ant的风格来的;
(2)AntPathMatcher不仅可以匹配Spring的@RequestMapping路径,也可以用来匹配各种字符串,包括文件路径等。
你是否曾今在你们的Filter里看过类似下面的代码?
这种所谓的白名单URL这样来匹配,可谓非常的不优雅,而且通过穷举法的扩展性不可为不差。因此下面举几个例子来介绍此匹配器的用法,以后建议使用它吧~
PathMatcher pathMatcher = new AntPathMatcher(); //这是我们的请求路径 需要被匹配(理解成匹配controller吧 就很容易理解了) String requestPath = "/user/list.htm?username=aaa&departmentid=2&pageNumber=1&pageSize=20";//请求路径 //路径匹配模版 String patternPath = "/user/list.htm**"; assertTrue(pathMatcher.match(patternPath, requestPath));
NT方式的通配符有三种:
?(匹配任何单字符),*(匹配0或者任意数量的字符),**(匹配0或者更多的目录)
url路径匹配规则说明:
举一些常用案例:
@Test public void fun1() { PathMatcher pathMatcher = new AntPathMatcher(); // 精确匹配 assertTrue(pathMatcher.match("/test", "/test")); assertFalse(pathMatcher.match("test", "/test")); //测试通配符? assertTrue(pathMatcher.match("t?st", "test")); assertTrue(pathMatcher.match("te??", "test")); assertFalse(pathMatcher.match("tes?", "tes")); assertFalse(pathMatcher.match("tes?", "testt")); //测试通配符* assertTrue(pathMatcher.match("*", "test")); assertTrue(pathMatcher.match("test*", "test")); assertTrue(pathMatcher.match("test/*", "test/Test")); assertTrue(pathMatcher.match("*.*", "test.")); assertTrue(pathMatcher.match("*.*", "test.test.test")); assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 assertFalse(pathMatcher.match("test*", "test/t")); //这同理 assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 //测试通配符** 匹配多级URL assertTrue(pathMatcher.match("/*/**", "/testing/testing")); assertTrue(pathMatcher.match("/**/*", "/testing/testing")); assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); assertFalse(pathMatcher.match("/????", "/bala/bla")); assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 }
注意事项:
1、AntPathMatcher不仅可以匹配URL路径,也可以匹配文件路径。但是需要注意AntPathMatcher也有有参构造,传递路径分隔符参数pathSeparator(若不传,默认值为/),对于文件路径的匹配来说,则需要根据不同的操作系统来传递各自的文件分隔符,以此防止匹配文件路径错误。
AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式:
AntPathMatcher matcher = new AntPathMatcher(File.separator); AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator"));
2、最长匹配规则(has more characters),即越精确的模式越会被优先匹配到。例如,URL请求/app/dir/file.jsp,现在存在两个路径匹配模式/**/.jsp和/app/dir/.jsp,那么会根据模式/app/dir/*.jsp来匹配。
ConcurrentReferenceHashMap
ConcurrentReferenceHashMap是自spring3.2后增加的一个同步的软(虚)引用Map。
这个工具类厉害了。我们知道java的引用类型一共分四种【小家java】引用类型(强引用、软引用、弱引用、虚引用),JDK也为我们提供了
WeakHashMap来使用。但是,但是它并不是线程安全的,因此刚好Spring给我们功提供这个工具类:ConcurrentReferenceHashMap满足了我们对线程安全的弱、软引用的需求。线面通过一个示例体验一把:
@Test public void fun1() throws InterruptedException { String key = new String("key"); String value = new String("val"); Map<String, String> map = new ConcurrentReferenceHashMap<>(8, ConcurrentReferenceHashMap.ReferenceType.WEAK); map.put(key, value); System.out.println(map); //{key=val} key = null; System.gc(); //等待一会 确保GC能够过来 TimeUnit.SECONDS.sleep(5); System.out.println(map); //{} }
我们发现当我们吧key置为null后,垃圾回收器会回收掉这部分内存。这就是弱、虚引用的作用,主要用来防止OOM。
查看ConcurrentReferenceHashMap源码发现起底层实现依赖的是RefrenceQueue完成自动移除操作。时间有限就写到这里。有时间再进行完善。 当然还有保证线程安全的代码,也是很重要的
DefaultPropertiesPersister
这个类本身没有什么特别的,就是代理了JDK的Properties类而已。但写到此处是觉得Spring优秀就优秀在它强大的对扩展开放的原则体现。
比如如果我们需要对配置文件进行解密(比如数据库连接密码不能明文),这些操作通过复写这些扩展类的某些方法来做,将特别的优雅。
spring启动时,解密配置文件的密文
DigestUtils
可以对字节数组、InputStream流生成摘要。10禁止或者16进制都行
FastByteArrayOutputStream
可以说是Spring实现的加强版的ByteArrayOutputStream
。为什么加强了呢?其实底层原理是Spring采用了一个LinkedList来作为缓冲区:
// The buffers used to store the content bytes private final LinkedList<byte[]> buffers = new LinkedList<>();
而ByteArrayOutputStream
直接使用的字节数组。
protected byte buf[];
这样每一次扩容中分配一个数组的空间,并当该数据放入到List中。相当于批量的操作,而ByteArrayOutputStream内部实现为一个数组每一次扩容需要重新分配空间并将数据复制到新数组中。效率高下立判了~因此推荐使用