Spring 使用经验谈
Posted sp42a
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 使用经验谈相关的知识,希望对你有一定的参考价值。
首先说说大潮流的 Spring Boot。因为项目强制要求的缘故,我不得不将 Spring 升级到 Spring Boot,但我个人觉得,这不但没有必要(不是做微服务),而且有以下几个问题:
- Boot 采用新的的打包机制,默认生成一个体积非常大的 jar 包。这样导致更新很麻烦,每次上次那么大的 jar 耗时间久(lib 下面的依赖 jar)。以前我更新项目有个技巧,也不是用 war 包(第一次的时候可以是),而是同步本地的到远程的即可,因为本地事实的就是最终编译结果,通过 SFTP 同步两边即可。或者不同步,你知道更新哪个 class/jsp 上传文件即可,名副其实的“增量更新”。现在可好了——上传完整的 jar 包就是“全量更新” 。
- 同样也正是这个原因,更加网页的静态文件非常不方便。——难道我改 html 里面的一个字符就要重新打包 jar 并上传?太不科学了吧!?
- 由于 Boot 整合了 Tomcat 服务器,那些日志我都不知道哪里看,有肯定是有——那就要重新学习、改变习惯呗。以前的话,用 Tomcat 自带的脚本:
catalina.sh run
就可以观察日志,非常方便。
当然,鼎鼎大名的 Boot 不是没有对应的解决方法,百度一下就有了。但鄙人就觉得不太优雅去处理咯。首先 lib/*.jar
可外置游离于 WEB-INF/lib
下面,依靠 Java 层面去指定某个目录,一个项目没啥事,多个项目呢?——这个就花费了人们去管理这个目录的“心智”;其次,前台页面的,也可以游离出去,不是放在 webapps
下面,所以管理维护成本又上升了——我何必那样子呢——老的方法不是很好么。最后更新的问题,Boot 项目允许你打包为 war 包而不是 jar 包,你解压一下不就可以上传了么。
还有个不痛不痒的问题,Boot 不能设置全局的 Welcome 默认页,例如(index.htm/index.jsp 等)。尽管可以单独设置某个 path 下面的,但全局不行。看来 Boot 不打算为传统页面模式提供支持。
说了那么多不好的话,但此时此刻,得说说好话。不过的确也是实事求是地说,Boot 使用起来很方便,简化了很多,但是无非仍是 Spring + Spring MVC 的高层封装,对于搞微服务的 Spring Cloud,那真是刚需。但话又说回来,你不是搞微服务,就是普通的 Web 开发,是不是 Boot 真的差异不大。
话归正题,让我们重新学习 Spring & MVC 吧!
Spring 最小引用
比起我初学 Spring 那时候,Spring 5.x 对依赖简化了不少,我那时候还需要 Common-lang 和 Common Log 之类的,现在不用了。下面是使用 Spring MVC 的最小引用
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
webmvc 自动依赖 web、context 等的包。
各种配置
为传统的模式的 Spring MVC 和 Boot 的都提供配置支持。
启动 Spring
<!-- 启用 Spring -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- JSP 页面编码 -->
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<page-encoding>UTF-8</page-encoding>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
<!-- // -->
在 /WEB-INF
下配置 applicationContext.xml
,指定扫描的包:
<!-- 扫描的包 -->
<context:component-scan base-package="net.bingosoft" />
<!--方便 JSP 调用注入的组件-->
<bean class="com.ajaxjs.spring.DiContextUtil" />
Spring MVC 配置
在指定的包之中新建一个 Spring MVC 配置类,继承 BaseWebInitializer
。
import com.ajaxjs.spring.BaseWebInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
public class WebInitializer extends BaseWebInitializer {
}
于是要为 MVC 设一个配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<mvc:default-servlet-handler />
</beans>
对 Request、Response 的扩展
在 web.xml
加入一个过滤器。
<!-- 对 Request、Response 的扩展 -->
<filter>
<filter-name>InitMvcRequest</filter-name>
<filter-class>com.ajaxjs.web.InitMvcRequest</filter-class>
</filter>
<filter-mapping>
<filter-name>InitMvcRequest</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Boot 下使用:继承 InitMvcRequest
并声明为组件即可。
@Component
@Order(1)
InitMvcRequestFilter extends InitMvcRequest {
}
如果不自动扫描包,那么加入下面 bean:
<!--对 Request、Response 的扩展-->
<bean class="com.ajaxjs.web.InitMvcRequest" />
Spring 工具类
就地取材,Spring 本身包含不少优秀的工具类,既然 Spring 为底层必定依赖的库,那么请大胆使用其工具栏吧~我知道的是,有些团队约束(或禁止)成员去写自己的工具类,例如 StringUtil.isEmpty()
之类的。
// 最常用的,判断是否空字符串
StringUtils.hasText();
// 将第一个字符改大写
StringUtils.capitalize(Str);
ObjectUtils.isEmpty([]);
CollectionUtils.isEmpty(List/Map);
Base64Utils.encodeToString();
Base64Utils.decodeFromString();
StreamUtils.copyToByteArray();
UriUtils.encode(v.toString(), "utf-8");
/**
* 将字符进行 URL 编码,默认 UTF-8 编码
*
* jdk自带的URL编码工具类 URLEncoder 在对字符串进行URI编码的时候,会把空格编码为 + 号。
* 空格的URI编码其实是:%20 搜素引擎上不少人都遇到这个问题,哀声一片。
* 解决办法大都是对编码后的字符串,进行 + 号替换为 %20。总感觉这种方式不优雅
*
* @param str 正常的 Java 字符串
*
* @return 已 URL 编码的字符串
*/
public static String urlEncode(String str) {
try {
return URLEncoder.encode(str, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
LOGGER.warning(e);
return null;
}
}
/**
* 将 URL 编码的字符还原,默认 UTF-8 编码
*
* @param str 已 URL 编码的字符串
* @return 正常的 Java 字符串
*/
public static String urlDecode(String str) {
try {
return URLDecoder.decode(str, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
LOGGER.warning(e);
return null;
}
}
以前写的
/**
* 将一个字符串分隔为字符串数组,分隔符 可以是,、/、-、\\(注意转义)、|、; 作为分隔符。 常在读取配置的时候使用。
*
* @param str 输入的字符串
* @return 分隔后的数组
*/
public static String[] split(String str) {
return str.split(",|/|-|\\\\\\\\|\\\\||;");
}
@Test
public void testSplit() {
assertEquals(2, split("a,b").length);
assertEquals(3, split("a/b/c").length);
assertEquals(4, split("a\\\\b\\\\c\\\\d").length);
assertEquals(5, split("a|b|c|d|e").length);
assertEquals(5, split("a;b;c;d;e").length);
}
/**
* 重复字符串 repeat 次并以 div 分隔
*
* @param str 要重复的字符串
* @param div 字符串之间的分隔符
* @param repeat 重复次数
* @return 结果
*/
public static String repeatStr(String str, String div, int repeat) {
StringBuilder s = new StringBuilder();
int i = 0;
while (i++ < repeat) {
s.append(str);
if (i != repeat)
s.append(div);
}
return s.toString();
}
@Test
public void testRepeatStr() {
assertEquals(repeatStr("Hi", ",", 3), "Hi,Hi,Hi");
}
/**
* 输入 a,看 a 里面是否包含另一个字符串 b,忽略大小写。
*
* @param a 输入字符串 a
* @param b 另一个字符串 b
* @return true 表示包含
*/
public static boolean containsIgnoreCase(String a, String b) {
return a.toLowerCase().contains(b.toLowerCase());
}
@Test
public void testContainsIgnoreCase() {
assertTrue(containsIgnoreCase("abc", "A"));
}
/**
* 随机字符串
*/
private static final String STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/**
* 生成指定长度的随机字符,可能包含数字
*
* @param length 户要求产生字符串的长度
* @return 随机字符
*/
public static String getRandomString(int length) {
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(STR.charAt(number));
}
return sb.toString();
}
/**
* 是否空字符串
*
* @param str 要判断的字符串
* @return true 表示为为空字符串,否则不为空
*/
public static boolean isEmptyString(String str) {
return str == null || str.isEmpty() || str.trim().isEmpty();
}
/**
* 将第一个字母大写
*
* @param str 字符串
* @return 字符串
*/
public static String firstLetterUpper(String str) {
// return str.substring(0, 1).toUpperCase() + str.substring(1); // 另外一种写法
return Character.toString(str.charAt(0)).toUpperCase() + str.substring(1);
}
/**
* 判断数组是否为空
*
* @param arr 输入的数组
* @return true 表示为素组不是为空,是有内容的,false 表示为数组为空数组,length = 0
*/
public static boolean isNull(Object[] arr) {
return arr == null || arr.length == 0;
}
assertTrue(isNull(new Object[] {}));
assertFalse(isNull(new Object[] { null }));
/**
* 判断 collection 是否为空
*
* @param collection Map输入的集合
* @return true 表示为集合不是为空,是有内容的,false 表示为空集合
*/
public static boolean isNull(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
/**
* 判断 map 是否有意义
*
* @param map 输入的
* @return true 表示为 map 不是为空,是有内容的,false 表示为空 map
*/
public static boolean isNull(Map<?, ?> map) {
return map == null || map.isEmpty();
}
@Test
public void testCollection() {
assertTrue(isNull(new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
}));
assertTrue(isNull(new ArrayList<String>() {
private static final long serialVersionUID = 1L;
}));
List<Object> list = null;
assertTrue(isNull(list));
}
// ------------BASE 64
/**
* BASE64 编码
*
* @param bytes 输入的字节数组
* @return 已编码的字符串
*/
public static String base64Encode(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
/**
* BASE64 编码
*
* @param str 待编码的字符串
* @return 已编码的字符串
*/
public static String base64Encode(String str) {
return base64Encode(str.getBytes(StandardCharsets.UTF_8));
}
/**
* BASE64 解码,但以 Byte 形式返回
*
* @param str 待解码的字符串
* @return 已解码的 Byte
*/
public static byte[] base64DecodeAsByte(String str) {
try {
return Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
LOGGER.warning("BASE64 解码失败", e);
return null;
}
}
/**
* BASE64 解码 这里需要强制捕获异常。
* 中文乱码:http://s.yanghao.org/program/viewdetail.php?i=54806
*
* @param str 待解码的字符串
* @return 已解码的字符串
*/
public static String base64Decode(String str) {
byte[] b = base64DecodeAsByte(str);
return b == null ? null : byte2String(b);
}
Stream
/**
* 使用内存操作流,读取二进制,也就是将流转换为内存的数据。 InputStream 转换到 byte[]. 从输入流中获取数据, 转换到 byte[]
* 也就是 in 转到内存。虽然大家可能都在内存里面了但还不能直接使用,要转换
*
* @param in 输入流
* @return 返回本实例供链式调用
*/
public static byte[] inputStream2Byte(InputStream in) {
// 使用内存操作流,读取二进制
try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
write(in, out, true);
return out.toByteArray();
} catch (IOException e) {
LOGGER.warning(e);
return null;
}
}
以上是关于Spring 使用经验谈的主要内容,如果未能解决你的问题,请参考以下文章
spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段
Spring boot:thymeleaf 没有正确渲染片段
Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段