为了节约网络带宽,一般在发布项目时对资源(js/css)文件进行压缩(去掉空行、精简代码等)。但是要做到兼容开发与生产还是的下一番功夫才行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ls -l src/main/webapp/static/assets/js/ | head
total 3120
-rwxrwxr--+ 1 winse None 24804 Aug 10 17:40 bootbox.js
-rwxrwxr--+ 1 winse None 71315 Aug 10 17:40 bootstrap.js
-rwxrwxr--+ 1 winse None 13905 Aug 10 17:40 bootstrap-colorpicker.js
-rwxrwxr--+ 1 winse None 49319 Aug 10 17:40 bootstrap-multiselect.js
...
$ ls -l target/dist/js/ | head
total 1368
-rwxrwx---+ 1 winse None 8943 Aug 19 16:53 bootbox-min.js
-rwxrwx---+ 1 winse None 8057 Aug 19 16:53 bootstrap-colorpicker-min.js
-rwxrwx---+ 1 winse None 38061 Aug 19 16:53 bootstrap-min.js
-rwxrwx---+ 1 winse None 18232 Aug 19 16:53 bootstrap-multiselect-min.js
...
项目中原本使用dist(压缩)、assets目录放置js/css等资源,在部署的时刻替换dist为assets,有点麻烦。首先想到的用nginx进行url重写 ,但是需要增加一个服务有点麻烦,能不能直接用spring来实现呢?
查看Spring的 mvc:resources
实现,相当于注册了一个 location -> ResourceHttpRequestHandler
的映射。
第一种尝试自动化的方式就是自定义handler类来进行资源的定位。增加 StaticRequestHandler 的处理类,增加配置 location 和 compressLocation 的配置:首先去查找压缩文件([NAME]-min.js),找不到然后再找源文件([NAME].js)位置。
主要修改 getResource 方法,具体完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
## java
public class StaticRequestHandler extends ResourceHttpRequestHandler {
private final static Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private String location;
private String compressLocation;
private Resource locationResource;
private Resource compressLocationResource;
public void setLocation(String location) {
this.location = location;
}
public void setCompressLocation(String compressLocation) {
this.compressLocation = compressLocation;
}
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
this.locationResource = getWebApplicationContext().getResource(location);
super.setLocations(Collections.singletonList(this.locationResource));
this.compressLocationResource = getWebApplicationContext().getResource(compressLocation);
}
@Override
protected Resource getResource(HttpServletRequest request) {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) {
throw new IllegalStateException("Required request attribute '"
+ HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
}
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid resource path [" + path + "]");
}
return null;
}
Resource res = null;
if (path.endsWith(".css")) {
res = findResource(compressLocationResource, path.substring(0, path.length() - 4) + ".min.css");
} else if (path.endsWith(".js")) {
res = findResource(compressLocationResource, path.substring(0, path.length() - 3) + ".min.js");
}
if (res == null) {
res = findResource(locationResource, path);
}
return res;
}
private Resource findResource(Resource location, String path) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Trying relative path [" + path + "] against base location: " + location);
}
Resource resource = location.createRelative(path);
if (resource.exists() && resource.isReadable()) {
if (logger.isDebugEnabled()) {
logger.debug("Found matching resource: " + resource);
}
return resource;
} else if (logger.isTraceEnabled()) {
logger.trace("Relative resource doesn't exist or isn't readable: " + resource);
}
} catch (IOException ex) {
logger.debug("Failed to create relative resource - trying next resource location", ex);
}
return null;
}
}
## spring config
<!-- 静态资源 -->
<!-- <mvc:resources mapping="/static/**" location="/static/" /> -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/static/assets/**=staticRequestHandler
</value>
</property>
</bean>
<bean id="staticRequestHandler" class="com.hotel.servlet.resource.StaticRequestHandler">
<property name="location" value="/static/assets/" />
<property name="compressLocation" value="/static/dist/" />
</bean>
这种方式实现了自动定位压缩资源 min.js
的功能,但是压缩还是不能自动化而且不能实时的更新(min要单独压缩产生),并且调试和生产环境还是需要手动的修改配置来切换。
有没有更好的自动化的实现开发环境和生产环境分开呢?
使用 yuicompressor-maven-plugin 插件压缩资源,然后把压缩资源先 打包放置到assets目录下。
注意: yuicomressor 插件的 nosuffix 配置为 true ! 这样压缩后的文件名和源文件名称才一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
## spring config
<!-- 静态资源 -->
<mvc:resources mapping="/static/**" location="/static/" />
## maven pom.xml
<profile>
<id>release</id>
<build>
<plugins>
<!-- http://alchim.sourceforge.net/yuicompressor-maven-plugin/compress-mojo.html -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>yuicompressor-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>compress_js_css</id>
<phase>process-resources</phase>
<goals>
<goal>compress</goal>
</goals>
</execution>
</executions>
<configuration>
<encoding>UTF-8</encoding>
<nosuffix>true</nosuffix>
<skip>false</skip>
<jswarn>false</jswarn>
<nomunge>false</nomunge>
<preserveAllSemiColons>false</preserveAllSemiColons>
<sourceDirectory>src/main/webapp/static/assets</sourceDirectory>
<outputDirectory>${project.build.directory}/dist</outputDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<webResources>
<resource>
<directory>${project.build.directory}/dist</directory>
<targetPath>static/assets</targetPath>
<filtering>false</filtering>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
war插件添加了自定义webResources资源,首先把压缩的文件拷贝到对应目录,maven发现文件已经存在就不会再拷贝同名的文件。这样源文件就相当于被替换成压缩的资源了。
总结
使用maven插件压缩打包,完美的解决js/css压缩导致的开发和生产不兼容问题。
后记
jsp使用了tag的地方总是会产生很多的空行,看着挺烦的。其实可以通过在jsp开头添加 trimDirectiveWhitespaces 属性来去掉空行:
1
<%@ page language="java" trimDirectiveWhitespaces="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
–END