15 七月 2018
先建立 Java Config 檔案
@Configuration
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(false)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
...
}
1.1 若使用了 @EnableWebMvc
,就不能在 XML 中使用 <annotation-driven />
(參考)
1.2 WebMvcConfigurerAdapter 只能在 @EnableWebMvc
中使用 (參考同上)
在 JSP 中改用以下方式來指定靜態檔案路徑
<script type="text/javascript" src="<c:url value="/resources/js/cache.js" />"></script>
2.1 其中 /resources/js/cache.js
必須以 /
作為開頭,才能正確轉換成 /${contextPath}/resources/js/cache-d347bec25f38177ca4e18844b11f1d42.js
2.2 推測這麼做的可能原因是同一個 .jsp 檔案有可能被不同的 @RequestMapping 所使用,因此 requestURI() 也會有所不同,故需以 /
確定其參考到的靜態檔案的路徑是正確的
在 web.xml 加入以下設定:
<filter>
<filter-name>resourceUrlEncodingFilter</filter-name>
<filter-class>
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>resourceUrlEncodingFilter</filter-name>
<servlet-name>appServlet</servlet-name>
</filter-mapping>
3.1 其中 <servlet-name>
應設為 org.springframework.web.servlet.DispatcherServlet 所使用的 servlet name (參考)
在 Spring 的 xml 設定檔中加入:
<beans xmlns="http://www.springframework.org/schema/beans" ... >
...
<context:component-scan base-package="idv.shunyi.config" /> <!-- MvcConfig.java 所在的 package -->
...
</beans>
若要在 CSS 中 import 其他 CSS
@import "cache.css";
5.1 則需改寫 addResourceHandler()
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
...
registry.addResourceHandler("/resources/css/**")
.addResourceLocations("/resources/css/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(false)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
.addTransformer(new CssLinkResourceTransformer());
}
17 六月 2018
在 pom.xml 中加入 pitest (目前最新版本是 v1.4.0)
<plugins>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.4.0</version>
</plugin>
</plugins>
1.1 若出現以下錯誤訊息:
上午 11:31:24 PIT >> SEVERE : Error generating coverage. Please check that your classpath contains JUnit 4.6 or above.
1.2 請在 pom.xml 中加入以下設定:(pitest 需要使用到 JUnit v4.6+)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
執行 mvn org.pitest:pitest-maven:mutationCoverage
測試的結果會產生到 target/pit-reports/ 目錄中
25 二月 2018
系統環境
在 pom.xml 加入以下設定
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.11</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.11</version>
</dependency>
在 web-context.xml 加入以下設定
<bean id="messageAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</list>
</property>
</bean>
加入一個 JSON Object
package idv.shunyi.entity;
public class JarFile {
private long id;
private String fileName;
private String fileType;
private String mainClass;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public String getMainClass() {
return mainClass;
}
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
}
Controller 寫法如下:
@RequestMapping(value = "/testJsonObject.do", produces = "text/html; charset=UTF-8")
@ResponseBody
public String param(@RequestBody JarFile jarFile) {
String value = jarFile.getFileName();
System.out.println(value);
return value;
}
Client 端寫法如下 (e.g. JQuery ajax):
function test() {
$.ajax({
method : 'post',
url: 'testJsonObject.do',
data: JSON.stringify({ id: '0', fileName: 'test-中文檔名.jar', fileType: 'jar', mainClass: 'idv.shunyi.entity.JarFile'}),
contentType:'application/json; charset=UTF-8',
success: function(data) {
alert(data);
}
});
};
24 十二月 2017
若要在 Spring 中使用 Quartz v1.x 則使用以下設定 (JobDetailBean & SimpleTriggerBean)
<bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="idv.shunyi.mvc.QuartzTest" />
</bean>
<bean id="simpleTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="jobDetailBean" />
<property name="repeatInterval" value="5000" />
<property name="startDelay" value="1000" />
</bean>
若要在 Spring (v3.1+) 中使用 Quartz v2.x 則使用以下設定 (JobDetail**Factory**Bean & SimpleTrigger**Factory**Bean)
<bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="idv.shunyi.mvc.QuartzTest" />
</bean>
<bean id="simpleTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="jobDetail" ref="jobDetailBean" />
<property name="repeatInterval" value="5000" />
<property name="startDelay" value="1000" />
</bean>
若設定錯誤可能會看到以下 Exception (quartz-2.0.0.jar,卻使用 項目 1.
的設定)
Caused by: java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.JobDetailBean has interface org.quartz.JobDetail as super class
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:2474)
at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:855)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1304)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1163)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:258)
at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:417)
at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1283)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1254)
... 18 more
參考資料
Spring should support Quartz 2.0 CronTrigger interface
IncompatibleClassChangeError : JobDetailBean has interface org.quartz.JobDetail as super class
Spring 4 + Quartz Scheduler Integration Example
JobDetailFactoryBean
SimpleTriggerFactoryBean
10 十二月 2017
簡單的解釋: 在多執行緒的環境下,volatile
用於讓變數的讀和寫都在記憶體中執行。
因為每個執行緒都需將變數放在 CPU cache 才能做運算,所以要從記憶體中把所有可存取的變數複製一份副本到 CPU cache 以供運算時讀寫操作使用,此時就有可能因為某些運算造成 CPU cache 存的變數值和記憶體存的變數值不一致。volatile
可以保證變數的讀寫操作都在記憶體中執行,以確保在多個執行緒都可以取到最新的變數值。
換個說法,每個執行緒都有自己的堆疊,所以它可以存取自己的變數副本。當執行緒被建立時,它將所有可存取的變數複製到自己的記憶體中。volatile
關鍵字用於告訴 JVM 說:「注意,這個變數可以在其他執行緒中修改」。若沒有這個關鍵字,JVM 可以自由地進行一些最佳化,像是在某些執行緒中從不更新這些本地副本。volatile
會強制執行緒更新每個變數的原始值。volatile
關鍵字可以用在任何類型的變數,可以是 primitive 或 objects!
對於非 volatile
變數而言,JVM 並不能保證什麼時候把記憶體的變數值讀取到 CPU cache 中,以及何時把 CPU cache 的變數值寫入到記憶體中。
因此在多執行緒的環境下,有可能造成 Thread 1 將 counter 變數設為 7,但沒有馬上寫入到記憶體中,造成 Thread 2 取到 counter 之值仍為 0。
圖片來源
由於 volatile
變數的讀寫都是在記憶體中執行,所以比直接在 CPU cache 做變數讀寫來得慢。
不過比 synchronized
快,因為 synchronized
要等到寫資料的執行緒做完才能夠讀到資料。
除了效能比較慢之外,用 synchronized
有一個風險是,若正在讀取變數的執行緒因為某些原因臨時被暫停 (suspended) 的話,其他正在等待寫入同一個變數的執行緒會 陷入無限的等待 或 直到讀取變數的執行緒終於做完。
Java Volatile Keyword Explained by Example
Java Volatile Keyword
[译] Java Volatile 关键字详解
09 十二月 2017
安裝環境
Debain Jessie
先安裝 openntpd
apt-get install openntpd
若要使用國內的 NTP 伺服器,可修改 openntpd 的設定檔,並加入以下設定
#將預設的國外 NTP 伺服器註解掉
#servers 0.debian.pool.ntp.org
#servers 1.debian.pool.ntp.org
#servers 2.debian.pool.ntp.org
#servers 3.debian.pool.ntp.org
servers tock.stdtime.gov.tw
servers time.stdtime.gov.tw
servers clock.stdtime.gov.tw
servers tick.stdtime.gov.tw
servers watch.stdtime.gov.tw
安裝完後 openntpd 就會自動於 daemon 執行了,若要確認 openntpd 有正確運作,可使用以下指令
systemctl status openntpd
#執行結果如下,Active 為 running 代表有正確執行
● openntpd.service - OpenNTPd Network Time Protocol
Loaded: loaded (/lib/systemd/system/openntpd.service; enabled)
Active: active (running) since Sat 2017-12-09 12:05:17 CST; 2h 41min ago
Process: 3715 ExecStart=/usr/sbin/ntpd $DAEMON_OPTS (code=exited, status=0/SUCCESS)
Process: 3712 ExecStartPre=/usr/sbin/ntpd -n $DAEMON_OPTS (code=exited, status=0/SUCCESS)
Main PID: 3717 (ntpd)
CGroup: /system.slice/openntpd.service
├─3717 /usr/sbin/ntpd -f /etc/openntpd/ntpd.conf
├─3718 ntpd: ntp engine
└─3719 ntpd: dns engine
若要手動執行 openntpd,可使用以下指令
ntpd -dsv
若是出現錯誤訊息:debian ntpd[29354]: adjtime failed: Invalid argument
,則要在 /etc/default/openntpd 中加入 DAEMON_OPTS="-s"
參考資料
NTP 的網路資源
NTP 新增頻寬
NTP 伺服器
openntpd: "adjtime failed: Invalid argument"
19 十一月 2017
可利用 Tika 來取得檔案的內文、Metadata 以及 MIME type
先準備 pom.xml,在 <dependency>
中加入:
<dependencies>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>1.16</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers</artifactId>
<version>1.16</version>
<!-- 不知為何 tika-parsers 會依賴於 cxf-rt-rs-client 和 quartz,在這個範例中我先把它排除掉 -->
<exclusions>
<exclusion>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
執行 mvn eclipse:eclipse
打開 Eclipse,並將剛剛建立的 project 匯入
建立 TikaTest.java
package idv.shunyi.tika;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Reader;
import org.apache.tika.Tika;
import org.apache.tika.metadata.Metadata;
import org.junit.Before;
import org.junit.Test;
public class TikaTest {
private Tika tika;
private File file;
@Before
public void setUp() throws Exception {
tika = new Tika();
file = new File(TikaTest.class.getResource("/test.html").toURI());
}
@Test
public void testParse() throws Exception {
Metadata metadata = new Metadata();
try (InputStream in = new FileInputStream(file)) {
Reader reader = tika.parse(in, metadata);
try (BufferedReader br = new BufferedReader(reader)) {
// Print extracted text content
System.out.println("### extracted text content");
for (String text; (text = br.readLine()) != null;) {
System.out.println(text);
}
System.out.println();
// Print meta data
System.out.println("### metadata");
for (String name : metadata.names()) {
System.out.println(name + " = " + metadata.get(name));
}
System.out.println();
}
}
}
@Test
public void testParseToString() throws Exception {
System.out.println("### parse to string");
try (InputStream stream = new FileInputStream(file)) {
String text = tika.parseToString(stream);
System.out.println(text);
}
System.out.println();
}
@Test
public void testDetect() throws Exception {
System.out.println("### detect media type");
String mimeType = tika.detect(file);
System.out.println(mimeType);
}
}
執行結果如下:
### extracted text content
這是內文
### metadata
X-Parsed-By = org.apache.tika.parser.DefaultParser
dc:title = 這是標題
Content-Encoding = UTF-8
title = 這是標題
Content-Type = text/html; charset=UTF-8
### parse to string
這是內文
### detect media type
text/html
04 十一月 2017
Tika core library. Contains the core interfaces and classes of Tika, but none of the parser implementations. Depends only on Java 6.
Tika 的核心函式庫,不包含任何 Parser
Tika parsers. Collection of classes that implement the Tika Parser interface based on various external parser libraries.
Tika Parser 函式庫的彙集
Tika application. Combines the above components and all the external parser libraries into a single runnable jar with a GUI and a command line interface.
命令列模式的 Tika,相關說明請參考連結
Tika JAX-RS REST application. This is a Jetty web server running Tika REST services.
在 Jetty 上提供 Tika REST 服務,相關說明請參考連結
Tika bundle. An OSGi bundle that combines tika-parsers with non-OSGified parser libraries to make them easy to deploy in an OSGi environment.
Tika 的 OSGi bundle
沒有包含任何 Parser 的版本
Build artifacts
Download Apache Tika
29 十月 2017
想把 Debian Jessie 的 Iceweasel 升級成 Firefox ESR
按照 Debian Mozilla Team 網頁上提供的訊息,應該是有 Firefox ESR 52 可以安裝
但是照網頁上的指示,更新 /etc/apt/sources.list 並執行 apt-get 後,卻顯示錯誤訊息:
Package firefox-esr is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package 'firefox-esr' has no installation candidate
寫信去 pkg-mozilla-maintainers@lists.alioth.debian.org 問也沒有回應 ...
所以 Firefox ESR 應該已經不支援 Jessie 了...
14 十月 2017
下載 JBake, 並解壓縮
unzip jbake-2.5.1-bin.zip
建立 project 目錄,並初始化
mkdir project
cd project/
../bin/jbake -i
產生 output 目錄
../bin/jbake -b
執行預覽 (若 content 目錄 的資料有改動會自動 update)
../bin/jbake -s
23 二月 2014
先建立要透過 Web Service 呼叫的 method
package tw.shunyi.ws.server;
import javax.jws.WebService;
@WebService
public class CircleFunctions {
public double getArea(double r) {
return java.lang.Math.PI * (r * r);
}
public double getCircumference(double r) {
return 2 * java.lang.Math.PI * r;
}
}
建立 server 端程式
package tw.shunyi.ws.server;
import javax.xml.ws.Endpoint;
public class Server {
public static void main(String[] args) {
Endpoint.publish(
"http://localhost:8080/SimpleWebService/circlefunctions",
new CircleFunctions());
}
}
啟動 web server
3.1. 程式會動態產生 wrapper class
2014/2/23 上午 09:11:19 com.sun.xml.internal.ws.model.RuntimeModeler getRequestWrapperClass
資訊: Dynamically creating request wrapper Class tw.shunyi.ws.server.jaxws.GetArea
2014/2/23 上午 09:11:19 com.sun.xml.internal.ws.model.RuntimeModeler getResponseWrapperClass
資訊: Dynamically creating response wrapper bean Class tw.shunyi.ws.server.jaxws.GetAreaResponse
2014/2/23 上午 09:11:19 com.sun.xml.internal.ws.model.RuntimeModeler getRequestWrapperClass
資訊: Dynamically creating request wrapper Class tw.shunyi.ws.server.jaxws.GetCircumference
2014/2/23 上午 09:11:19 com.sun.xml.internal.ws.model.RuntimeModeler getResponseWrapperClass
資訊: Dynamically creating response wrapper bean Class tw.shunyi.ws.server.jaxws.GetCircumferenceResponse
3.2. 可以執行以下指令來產生 wrapper class, 避免每次重啟 server 都要動態產生
wsgen -verbose -cp . tw.shunyi.ws.server.CircleFunctions
執行以下指令產生給 client site 用的檔案
wsimport -p tw.shunyi.ws.client -s src -d bin -verbose http://localhost:8080/SimpleWebService/circlefunctions?WSDL
建立 client 端程式
package tw.shunyi.ws.client;
public class Client {
private static CircleFunctionsService service;
public static void main(String[] args) {
service = new CircleFunctionsService();
CircleFunctions port = service.getCircleFunctionsPort();
System.out.println(port.getArea(1.0));
}
}
執行結果
3.141592653589793
Older posts are available in the archive.