2017年4月12日 星期三

Spring Web (Spring3.x 二十五)


import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
    
@WebListener
public class SpringListener implements ServletContextListener {
    private ApplicationContext context;
    
    @Override
    public void contextInitialized(ServletContextEvent e) {
        String ooo = e.getServletContext().getInitParameter("ooo");
        context = new ClassPathXmlApplicationContext(ooo);
        e.getServletContext().setAttribute("xxx", context);
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        ((ClassPathXmlApplicationContext) context).close();
    }
}

※自己模擬一支啟動時的 Spring


※web.xml

<context-param>
    <param-name>ooo</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
    
<!-- <listener> -->
<!-- <listener-class>package.className</listener-class> -->
<!-- </listener> -->

※尤於上面已經有Annotation,所以此時的 listener 可以不要

※Spring API裡的 org.springframework.web.context.ContextLoaderListener
建構子有說明使用 contextClass 或 contextConfigLocation
意思就是我上面的ooo,Spring 取名為 contextConfigLocation,對應的是xml設定檔
但如果用的不是設定檔,是Class,那就寫成 contextClass
寫 ooo 是無效的,不要被誤導了

而ApplicationContext,也就是如上的xxx,被存成一個常數,叫
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

ContextLoaderListener 的父類別 ContextLoader 還有說
預設會去 /WEB-INF/applicationContext.xml 抓設定檔
所以如果你的設定檔放在這個路徑,而且也叫 applicationContext.xml
那 context-param 就可以不用寫



※使用Spring 的Web

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

※ContextLoaderListener 是 ServletContextListener 的子類



※測試

※pom.xml

<properties>
    <jdk.version>1.8</jdk.version>
    <spring.version>3.2.15.RELEASE</spring.version>
</properties>
    
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>
    
<build>
    <finalName>SpringWeb</finalName>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.3</version>
            <configuration>
                <source>${jdk.version}</source>
                <target>${jdk.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>



※applicationContext.xml

<bean id="b" class="Book">
    <property name="bookName" value="DragonBall" />
    <property name="bookMoney" value="70" />
</bean>



※bean

public class Book {
    private String bookName;
    private int bookMoney;
    // setter/getter...
}



※jsp頁面

<form action="xxx" method="post">
    <input type="submit" value="送出" />
</form>



※Servlet

@WebServlet(urlPatterns = { "/xxx" })
public class ServletTest extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String app = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
        ApplicationContext ctx = (ApplicationContext) getServletContext().getAttribute(app);
    
        System.out.println(ctx.getBeanDefinitionCount());
        for (String s : ctx.getBeanDefinitionNames()) {
            System.out.println(s);
        }
    
        Book b = ctx.getBean("b", Book.class);
        System.out.println(b);
    }
}

※使用 Annotation 時,在啟動時出現 @EnableAsync annotation metadata was not injected 的錯誤,我將 Book.java 增加 package 就解決了,也有可能是 component-Scan 寫錯



※使用 annotation 的設定


<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>bean.Animal</param-value>
</context-param>
    
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

※並不一定要使用常數的方式取得 applicationContxt,還可以用 ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

※多個 class 可用「,」隔開,或者用 @ComponentScan



※取得 @Autowired

super.init();
// super.init(config);
WebApplicationContextUtils.getWebApplicationContext(getServletContext())
    .getAutowireCapableBeanFactory()
    .autowireBean(this);
    
// WebApplicationContextUtils.getWebApplicationContext(getServletContext())
//     .getAutowireCapableBeanFactory()
//     .configureBean(this, "animal");

※覆寫 init 方法,有兩個,隨便一個都可以,有參數的可用 config.getServletContext() 取得 ServletContext

※兩者選其一即可

※animal 可以寫 @ComponentScan,這樣就可以抓到很多了


2017年3月22日 星期三

聚合、依賴、繼承、插件 (Maven 4)

※聚合


上面的 A、B 專案,都要先 install 才能讓 C 抓到,這如果不只這兩個專案,那每一個都要先 install,太沒效率了,所以可用繼承的方式,統一用一個 pom.xml 就好了,操作如下:

1.新增 maven 專案後,勾下面的選項



 2.package 要選 pom



※D的pom


<project ...>
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>maven_test</groupId>
    <artifactId>D</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>../A</module>
        <module>../B</module>
        <module>../C</module>
    </modules>
</project>

※因為 4 個專案同一層,所以要到上一層才可以抓到其他專案

※packaging 裡的 pom 一定要小寫

※此時只要操作 D 專案即可一次統一操作 3 個專案

※但有些人不想要新增一個專案,也可以將 D 的 pom.xml 複製到 ABC 三個專案的同層,而 D 專案也刪了,最後用命令提示字元下 mvn clean install,當然 Eclipse 還是能 import



※依賴


※左圖

C 專案依賴 A 和 B 專案,只要 scope 是 compile(預設) ,就會將 A、B 兩個專案的 jar 檔,複製到 C 專案
如果有同樣的 jar 檔,會以依賴最短的優先,但路徑都是 1,所以就會以 pom 檔的順序為主,誰先寫誰就贏了

※右圖

C 專案依賴 B 和 A1 專案,只要 scope 是 compile(預設) ,就會將 B、A1 兩個專案的 jar 檔,複製到 C 專案,但 A1 又依賴 A 專案,所以 A 也會複製給 C 專案
如果有同樣的 jar 檔,會以依賴最短的優先,所以會以 B 為主

如果不想依賴 maven 規定好的 jar,那就可以在不想依賴的 <dependency>增加<exclusions>來排除
<dependency>
    <groupId>maven_test</groupId>
    <artifactId>C</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>


※注意 C 專案要抓到 A、B 專案,必需先 mvn install ,這樣 .m2 才會有 jar 檔供 C 下載



※繼承

※父類

<groupId>bruce.chen</groupId>
<artifactId>D</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
    
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.2.2.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

※packaging 一定是 pom,否則子類會報錯

※使用 dependencyManagement 標籤將 dependencies 裝起來

※子類

<artifactId>B</artifactId>
<packaging>jar</packaging>
    
<parent>
    <groupId>bruce.chen</groupId>
    <artifactId>D</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>


※寫個 parent 標籤,表示它還有父 pom

※自己坐標的 groupId 和 version 如果和父類一樣,也可以刪除

※dependency 標籤的 version 也可以刪除

※parent 有個子標籤 relativePath,預設是 ../pom.xml,如果不是就要打出來了


※插件

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<phase>generate-test-resources</phase>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

phase 要打上生命週期的名字,也就是哪時候執行這個插件
goal 可以多個,此例是打原碼包和打測試包,至於裡面要打什麼,可參考下圖


左邊選擇 Maven Plugins 會自動跳動上圖的 Plugins,然後選擇 source,表示要打源碼包,點進去就會看到可以用的 goals

此例在 compile phase 並不會打上面設定的 jar 包,可參考下圖



可以看出 compile 還沒到我們指定的 phase,但 package 有包括,加上原本的 jar,總共有三個
demo-0.0.1-SNAPSHOT.jar:原本的 jar 包
demo-0.0.1-SNAPSHOT-sources.jar:goal 為 jar 生出來的,內容和原本的一模一樣,不會有測試的類別
demo-0.0.1-SNAPSHOT-test-sources.jar:goal 為 test-jar 生出來的, 內容只有測試類別
.可以用解壓工具看裡面的內容
.如果專案是 springboot,還會有第四包,springboot 是可以執行的 jar 包,但要打上如下的標籤
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
通常 IDE 內鍵都會幫忙生成

2017年3月21日 星期二

The import XXX conflicts with a type defined in the same file

The import XXX conflicts with a type defined in the same file
假設新增一個 java 檔,檔名叫 List,那麼使用到 java 內鍵的List時,假設是 java.util.List 時,import 時就會出現這個錯
解決方法是使用package.class名稱,就不要 import 了,如java.util.List<String> list = new ArrayList<>();

假設檔名叫 Test.java,那麼使用 junit 的 @Test 時也要全部打,不要 import 了,如 @org.junit.Test

以上就算自己新建的檔案有 package 名稱,一樣會出這個錯

2017年3月15日 星期三

增加ORCL

做完此篇的動作,即可使用PL/SQL Developer

安裝完 Oracle 後,ORCL 的路徑在 app\BAU\product\11.2.0\dbhome_1\NETWORK\ADMIN\tnsnames.ora
想增加可用Net Manager


1.在程式集可看到如下的畫面
 
2.選到Service Naming 後,按旁邊綠色的加號,名字是待會在PL/SQL Developer 登入時的 Database 欄位會出現的 (做完後Service Naming下也會出現)


3.選預設的即可


4.這IP要和專案的人要


5.Service Name 也是要和專案的人要,如 192.168.11.22:1521:xxx 裡的xxx


6.不放心可按 Test... 測試一下,有自信就直接按 Finish 了


7.如果第 6 步是選 Test...,就會看到這個畫面,按 Change Login...,可用帳號密碼測試


按下完成後,會在app\BAU\product\11.2.0\dbhome_1\NETWORK\ADMIN\tnsnames.ora 裡的內容發現已經增加了

Linux 的連結和 Windows 的 mklink

不管是Linux 還 Windows ,連結檔的容量都是很小的

Linux

ln [-s] 已有的檔案或目錄名稱 檔案或目錄名稱

硬連結

    語法:ln <目的> <來源>
    1.將一個檔案硬連結和多個檔案以後,假設是5個,刪除其中1~4個不會影響本來的檔案
    2.只能是同一個檔案系統
    3.inode 一樣
    4.只能操作檔案
    5.ls -l 查看時,第一個並不是L,是-

軟連結

    語法:ln -s <目的> <來源>
    1.目的檔案若被刪除,則來源會出現錯誤
    2.可跨越不同的檔案系統
    3.inode 不同
    4.ls -l 查看時,是 L
    5.-s = --symbolic

還有一個參數 f,意思是如果目的檔已經有了,會先刪除再新增



Windows

以下是用Windows 7 的實驗,必需有管理的權限來操作,而且來源的檔案或目錄不可存在
mklink [/H /S /J] 檔案或目錄名稱 已有的檔案或目錄名稱

先切換到想連結的地方,然後如下操作

硬連結(Hard)

    語法:mklink -h <來源> <目的>
    1.只對檔案有效,如果目的為目錄,會出現「存取被拒」
    2.也只能在同個槽才行,如 C 槽不能到 D 槽

軟連結(Symbolic/Soft)

    語法:
        針對檔案:mklink <來源> <目的>,如果目的為目錄會成功,但沒作用,無意義
        針對目錄:mklink -d <來源> <目的>,如果目的為檔案會成功,但點下去會報錯,無意義

NTFS 連結(Junction)

    故名思義,檔案系統是NTFS才可使用,在X槽按右鍵-->屬性的一般活頁標籤可看到
    但只能使用在目錄
    語法:mklink -j <來源> <目的>,目的為檔案會成功,但點下去會報錯,無意義

軟連結的目錄和 NTFS 連結的差異

1.右鍵屬性時,軟連結會多一個 Shortcut 的活頁標籤

2.使用 dir 指令時,軟連結是<SYMLINK>,NTFS 連結是<JUNCTION>

3.複製時不同,將這兩個連結複製到其他的目錄後
    .軟連結只有原本的目錄才可以連
    .NTFS 連結無法複製,複製完後到目的資料夾貼上時,沒有作用

4.移動時不同,將這兩個連結剪下到其他的目錄貼上後
    .軟連結真的會移動,也可連結
    .NTFS 連結會複製一份到新的目錄,原本的還可以連結,但複製後的不行
    ※注意 NTFS 連結還會將原本目錄的所有檔案、目錄全部移動到新的目錄,造成原本的目錄 是空的情形,此時所有連結裡面都是空的



2017年3月8日 星期三

ExecutorService (Thread 六)

連線到資料庫有個連線池,Thread 也有 Thread pool ,ExecutorService 就是個 Thread pool


※取得ExecutorService

以下是overloading,以下只列出同名的其中一個方法


ExecutorService s1 = Executors.newCachedThreadPool();// 可快取的 Thread,預設idle 60 秒
ExecutorService s2 = Executors.newFixedThreadPool(3);// 固定數量的 Thread
ExecutorService s3 = Executors.newSingleThreadExecutor();// 單一的 Thread
    
ScheduledExecutorService sch1 = Executors.newScheduledThreadPool(3);// 可排程的 Thread
ScheduledExecutorService sch2 = Executors.newSingleThreadScheduledExecutor();// 單一可排程的

※最後兩個可排程,但要用 ScheduledExecutorService 去接

※Executors.newFixedThreadPool(1) 等於 Executors.newSingleThreadExecutor(),看原始碼就知道,排程的也是一樣



※ScheduledExecutorService

ScheduledExecutorService 有4個方法
delay配合TimeUnit的時間間隔,是設定時間用的

delay:多少時間後開始執行
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

以下兩個不容易分
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
initialDelay:多少時間後開始執行,只有第一次
period、delay:第二次(含)之後看的是這個時間
period、delay不同的是
period:command 的開始時間開始算,算到 period 設定的時間為一個間隔,如果設定5秒就每5秒執行一次
delay:command 的結束時間開始算,算到 delay 設定的時間為一個間隔,如果設定5秒就每5秒加上 command 執行的時間執行一次

※排程範例

ScheduledExecutorService sch1 = Executors.newScheduledThreadPool(1);
ScheduledExecutorService sch2 = Executors.newSingleThreadScheduledExecutor();
    
ScheduledFuture<String> future1 = sch1.schedule(new Callable<String>() {
    public String call() throws Exception {
        return "xxx";
    }
}, 3, TimeUnit.SECONDS);
    
try {
    System.out.println(future1.get());
    sch1.shutdown();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}
    
ScheduledFuture<?> future2 = sch2.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("哇哈哈");
    }
}, 1, 1, TimeUnit.SECONDS);
    
try {
    future2.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

※注意 future2 的順序,以上面的例子是先執行3秒後的xxx,然後再執行哇哈哈
如果將 future2 放到 future1 的下面,然後在 future1.get() 下增加 future2.get(),最下面的 future2.get() 刪除,此時結果會是1秒的哇哈哈先執行



※Future 的 cancel 方法

參數是布林值,表示如果任務正在執行,是否要取消
回傳是否成功取消,如果為false,通常已經正常完成


ScheduledExecutorService sch = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> future = sch.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("哇哈哈");
    }
}, 1, 1, TimeUnit.SECONDS);
    
ScheduledFuture<?> f = sch.schedule(new Callable<Boolean>() {
    public Boolean call() throws Exception {
        return future.cancel(true);
    }
}, 5, TimeUnit.SECONDS);
    
try {
    // System.out.println(future.get());
    System.out.println("是否取消=" + f.get());
    sch.shutdown();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

※註解打開會出「java.util.concurrent.CancellationException」的錯


2017年3月6日 星期一

集合、ThreadLocal (Thread 五)

※集合

System.out.println("synchronized list");
List<Integer> l = new ArrayList<>();
synchronized (l) {
    l.add(1);
    l.add(2);
    l.add(3);
}
for (Integer i : l) {
    System.out.println(i);
}
    
System.out.println();
System.out.println("synchronizedCollection");
Collection<String> c = Collections.synchronizedCollection(new ArrayList<>());
c.add("A");
c.add("B");
c.add("C");
synchronized (c) {
    for (String s : c) {
        System.out.println(s);
    }
}
    
System.out.println();
System.out.println("synchronizedList");
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("D");
list.add("E");
list.add("F");
    
synchronized (list) {
    for (String s : list) {
        System.out.println(s);
    }
}
    
System.out.println();
System.out.println("synchronizedSet");
Set<String> set = Collections.synchronizedSet(new HashSet<>());
set.add("G");
set.add("H");
set.add("G");
synchronized (set) {
    for (String s : set) {
        System.out.println(s);
    }
}
    
System.out.println();
System.out.println("synchronizedMap");
Map<Integer, String> map = Collections.synchronizedMap(new HashMap<>());
map.put(0, "A");
map.put(1, "B");
map.put(2, "C");
for (int i = 0; i < map.size(); i++) {
    System.out.println(map.get(i));
}

※這些是 java 1.5 之前的寫法,for 迴圈有用 synchronized ,是因為使用到 Iterator ,它並不是 Thread-safe,而 for each 會用到 Iterator 物件,但最原始的 for 迴圈不會用到



※ java 1.5 集合

System.out.println("CopyOnWriteArrayList");
List<String> list = new CopyOnWriteArrayList<>();
list.add("D");
list.add("E");
list.add("F");
for (String s : list) {
    System.out.println(s);
}
    
System.out.println();
System.out.println("CopyOnWriteArraySet");
Set<String> set = new CopyOnWriteArraySet<>();
set.add("G");
set.add("H");
set.add("G");
for (String s : set) {
    System.out.println(s);
}
    
System.out.println();
System.out.println("ConcurrentHashMap");
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(0, "A");
map.put(1, "B");
map.put(2, "C");
for (int i = 0; i < map.size(); i++) {
    System.out.println(map.get(i));
}

※java 1.5 新增了 java.util.concurrent 這個套件



※ThreadLocal

ThreadLocal 是 Thread 變數,在寫 Thread-safe 是很困難的,要讓 Thread 共用,又要同步,又有效能的問題,還要小心 Dead Lock,所以有人想到不要共用了,直接創造 Thread 的複本,在 java 1.2 就產生了 ThreadLocal 類別了,但此方法會需要較多的記憶體


public class ThreadTest extends Thread {
    private static int number;
    
    public int getNumber() {
        number = number + 1;
        return number;
    }
    
    // private static ThreadLocal<Integer> number = new ThreadLocal<Integer>() {
    // @Override
    // protected Integer initialValue() {
    // return 0;
    // }
    // };
    
    // public int getNumber() {
    // number.set(number.get() + 1);
    // return number.get();
    // }
    
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + "=" + getNumber());
        }
    }
    
    public static void main(String... a) {
        ThreadTest t1 = new ThreadTest();
        ThreadTest t2 = new ThreadTest();
        ThreadTest t3 = new ThreadTest();
    
        t1.start();
        t2.start();
        t3.start();
    }
}

※結果:
Thread-0=1
Thread-0=4
Thread-0=5
Thread-1=3
Thread-1=6
Thread-1=7
Thread-2=2
Thread-2=8
Thread-2=9

可以看得出來 Thread 之間被影響了,應該三個 Thread 都是 123 才是正確的


※打開註解並註解原來的 number 和 getNumber(),結果如下:
Thread-0=1
Thread-0=2
Thread-0=3
Thread-2=1
Thread-2=2
Thread-2=3
Thread-1=1
Thread-1=2
Thread-1=3