2016年1月31日 星期日

Struts 1.x 整合 Spring 3.x

參考文件

官方說有兩種方式,第一種方式是代理,又分成兩種方法;而第二種方式是繼承,只有一種方法,所以總共有三種方法,以Struts Hello World(Struts一)為例,先可以runStruts1,然後再做整合

要用到的jar檔

pom.xml

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts-core</artifactId>
    <version>1.3.10</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-struts</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
    
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>

※jstl和standard有在Struts Hello World(Struts一)說過,因為太舊了



web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

※要啟動Spring,一律都要加


applicationContext.xml

<bean name="/xxx" class="controller.LoginAction" />

※先設定好Action路徑




※第一種方法:DelegatingRequestProcessor

struts-config.xml

<form-beans>
    <form-bean name="ooo" type="controller.LoginActionForm">
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>
    
<action-mappings>
    <action path="/xxx" name="ooo" scope="request" validate="true" input="/index.jsp">
        <forward name="success" path="/s.jsp" />
        <forward name="fail" path="/f.jsp" />
    </action>
</action-mappings>
    
<controller>
    <set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor" />
</controller>
    
<message-resources parameter="MessageResources" />
    
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml" />
</plug-in>

※plug-in是為了讓系統知道applicationContext.xml放在哪

※主要是controller,這時action的type就沒用了,所以拿掉了。
當JSP傳來xxx.do時,action的path會收到,這是controller在把它丟到applicationContext.xml,然後找到對應的Action



※第二種方法:DelegatingActionProxy

struts-config.xml

<form-beans>
    <form-bean name="ooo" type="controller.LoginActionForm">
        <form-property name="username" type="java.lang.String" />
        <form-property name="password" type="java.lang.String" />
    </form-bean>
</form-beans>
    
<action-mappings>
    <action path="/xxx" type="org.springframework.web.struts.DelegatingActionProxy" name="ooo" scope="request" validate="true" input="/index.jsp">
        <forward name="success" path="/s.jsp" />
        <forward name="fail" path="/f.jsp" />
    </action>
</action-mappings>
    
<message-resources parameter="MessageResources" />
    
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
    <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml" />
</plug-in>

※action的type變成Spring的代理機制了,整個controller都不要了,很方便,是三種方法最好的方式




※第三種方法:ActionSupport

LoginAction.java

public class LoginAction extends ActionSupport {
    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        WebApplicationContext ctx = this.getWebApplicationContext();
        LoginAction l = (LoginAction) ctx.getBean("/xxx");
    
        LoginActionForm laf = (LoginActionForm) form;
    
        if (laf.getUsername().trim().equals("aaa") && laf.getPassword().trim().equals("111")) {
            return mapping.findForward("success");
        } else {
            return mapping.findForward("fail");
        }
    }
}

※本來是繼承org.apache.struts.action.Action,要改繼承org.springframework.web.struts.ActionSupport,不是Struts2的ActionSupport哦!

※然後就通過WebApplicationContext再getBean就能取的applicationContext.xml的實體了,看是要取得Dao對資料庫做事或什麼的,這種方法是最破的,因為產生了耦合



※取得applicationContext.xml

web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- <param-value>classpath*:applicationContext.xml</param-value> -->
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

※前面三種方法都是使用plug-in取得,其實寫在web.xml也可以,兩者擇一,但註解那行不知為什麼會抓不到,以繼承ActionSupport為例,run的時候都很順,但一執行到getBean時就會報找不到xxx的錯,也就是找不到applicationContext.xml

2016年1月30日 星期六

Java Bean、參數、返回值 (Spring3.x MVC 二)

※Java Bean


public class Book {
    private int id;
    private String name;
    private ComicBook comicBook;
    
    // setter/getter...
}
------------------------------
public class ComicBook {
    private String name;
    
    // setter/getter...
}




@RequestMapping("book")
public String pojo(Book book) {
    System.out.println(book.getId());
    System.out.println(book.getName());
    System.out.println(book.getComicBook().getName());
    return "hello";
}

※傳到 controller 後,只要名稱符合 java bean 裡面的名字,就會抓的到


<a href="ooo/xxx/book.mvc?id=99&name=漫畫&comicBook.name=七龍珠">pojo</a>





※接受的參數

官方文件有說明,有一些上一篇也說過了,

Servlet API 

有 9 個:
.ServletRequest
.ServletResponse
.HttpSession
.Principal
.Locale
.InputStream
.Reader
.OutputStream
.Writer

@RequestMapping("api")
public String api(
    ServletRequest req, 
    ServletResponse res, 
    HttpSession session, 
    Principal principal, 
    Locale locale,
    InputStream is, 
    // Reader reader, 
    // OutputStream out,
    Writer writer) {
    
    System.out.println(req);
    System.out.println(res);
    System.out.println(session);
    System.out.println(principal);
    System.out.println(locale);
    System.out.println(is);
    // System.out.println(reader);
    // System.out.println(out);
    System.out.println(writer);
    return null;
}

※注意 XXX has already been called for this response,IO 的 InputStream 和 Reader 只能選其中一個; OutputStream 和 Writer 也只能選其中一個

※在 controller 第一行下繼點後,下面第 2 個 Annotation

※第二個 for  迴圈裡 resolveHandlerArguments 會看到判斷 annotation 的程式碼
RequestParam
RequestHeader
RequestBody
CookieValue
PathVariable
ModelAttribute
Value
Valid 開頭的
所以在參數裡,可以用以上這幾個 annotation

再往下看有一個 if,裡面有個方法 resolveCommonArgument
進去後,再找到 resolveStandardArgument,按 Open Implementation 就能看到這 9 個判斷

※測試
<a href="ooo/xxx/api.mvc">servlet api</a>


※Model

@RequestMapping("model")
public String model(Model model) {
    System.out.println(model.getClass().getName());
    model.addAttribute("m", Stream.of("ddd", "eee", "fff").collect(Collectors.toList()));
    return "hello";
}



※測試
---------- index.jsp ----------
<a href="ooo/xxx/model.mvc">model</a>
    
    
---------- hello.jsp ----------
m:${requestScope.m}



※Map

@RequestMapping("map")
public String map(Map<String, Object> map) {
    map.put("ooo", Stream.of("aaa", "bbb", "ccc").collect(Collectors.toList()));
    return "hello";
}



※測試
---------- index.jsp ----------
<a href="ooo/xxx/map.mvc">map</a>
    
    
---------- hello.jsp ----------
ooo:${requestScope.ooo}

※ModelMap 當然也可以,因為它是 Map 的子類

※從ExtendedModelMap 分開來的



※接受的返回值


官方文件


※ModelAndView

@RequestMapping("modelView")
public ModelAndView mv() {
    ModelAndView mav = new ModelAndView();
    // mav.setViewName("/WEB-INF/hello.jsp");
    mav.setViewName("hello");
    mav.addObject("xxx", "yeah");
    return mav;
}

※setViewName 的內容和 上一篇說的 InternalResourceViewResolver (Spring 設定檔裡) 有關

※也可使用建構子 new ModelAndView("hello");

※測試
---------- index.jsp ----------
<a href="ooo/xxx/modelView.mvc">modelAndView</a>
    
    
---------- hello.jsp ----------
xxx:${requestScope.xxx}



2016年1月29日 星期五

讀取國際化資源 (Spring3.x 二十四)

基本的java國際化資源可看這篇,現在講的是Spring的國際化資源

applicationContext.xml
<bean id="messageSource" ="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>aaa</value>
            <value>bbb</value>
        </list>
    </property>
</bean>

※從官網複製

※用list、array、set都可以,這樣的設定表示還要有aaa.properties、bbb.properties,如下三隻



aaa.properties
aaa.bbb=111

bbb_en_US.properties
ccc.ddd=english {0},{1}

bbb_zh_TW.properties
ccc.ddd=\u7E41\u4E2D {0},{1}

測試類
MessageSource ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
String m1 = ctx.getMessage("aaa.bbb", null, "Default", null);
String m2 = ctx.getMessage("ccc.ddd", new Object[] { "p1", "p2" }, "default Message", Locale.TAIWAN);
System.out.println(m1);
System.out.println(m2);
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
111
繁中 p1,p2

※getMessage是overloading,第三個參數是找不到第一個參數的key時顯示的訊息,如果給null,就是null;因為是overloading,所以只給三個參數(第三個參數不設定),又找不到key的話,就會出「org.springframework.context.NoSuchMessageException」的錯

※我把最後的Locale的參數給null,還是能找到,是zh_TW的,不曉得是不是覆蓋的原因


2016年1月28日 星期四

HelloWorld (Spring3.x MVC 一)

此專案使用 xml 加 annotation 架設 SpringMVC
這次使用 gradle,創建好 gradle 後,預設不是 Web 的,使用以下方式變成 Web


※變 Web 專案

專案按右鍵 Properties


產生 Web.xml



增加 jar 檔

※build.gradle

apply plugin: 'java-library'
    
repositories {
    jcenter()
}
    
def springVer = '3.2.13.RELEASE'
    
dependencies {
    api 'org.apache.commons:commons-math3:3.6.1'
    implementation 'com.google.guava:guava:21.0'
    
    testImplementation 'junit:junit:4.12'
    compile 'org.springframework:spring-context:"${springVer}"'
    compile group: 'org.springframework', name: 'spring-webmvc', version: "${springVer}"
    // compile group: 'javax.servlet.jsp.jstl', name: 'jstl', version: '1.2'
    compile group: 'javax.servlet', name: 'jstl', version: '1.2'
}

※之後只要有改,就要在專案按右鍵做如下的設定,才會更新 build.gradle

※web.xml

<servlet>
    <servlet-name>testServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/testServlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>testServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

※根據官方文件的第二張圖 (Context hierarchy in Spring Web MVC) 下面,第一段有說明 [servlet-name]-servlet.xml in the WEB-INF,表示預設會在 WEB-INF 下找 [servlet-name]-servlet.xml,以此例來說就是 testServlet-servlet.xml,如果是叫這個名字且放在這個目錄下,那就可以不寫 init-param

※如果想用 annotation,本篇最下面有


※testServlet-servlet.xml

<context:component-scan base-package="controller" />




※XxxAction.java

@Controller
public class XxxAction {
    @RequestMapping("/ooo/xxx/*.mvc")
    public String hello() {
        System.out.println("hello mvc");
        return "/WEB-INF/hello.jsp";
    }
}

※只要是/ooo/xxx/開頭且是 mvc 結尾就會進來這個方法,進來後導向到另一支 jsp


※index.jsp

<a href="ooo/xxx/hello.mvc">Hello World</a>

※也可用 form,但網址是 GET,所以也可以用這個方式連後端


※@RequestMapping 

可寫在類和方法上,兩個都寫可以合併,如上例可改成下面二種方式

@Controller
@RequestMapping("/ooo")
public class XxxAction {
    @RequestMapping("/xxx/*.mvc")
    public String hello() {
        System.out.println("hello mvc");
        return "/WEB-INF/hello.jsp";
    }
}

@Controller
@RequestMapping("/ooo/xxx/")
public class XxxAction {
    @RequestMapping("*.mvc")
    public String hello() {
        System.out.println("hello mvc");
        return "/WEB-INF/hello.jsp";
    }
}


※處理 view

<bean id="jspViewResolver"
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- <property name="viewClass" -->
    <!-- value="org.springframework.web.servlet.view.JstlView" /> -->
    <property name="prefix" value="/WEB-INF/" />
    <property name="suffix" value=".jsp" />
</bean>

※可在 testServlet-servlet.xml 增加這段,可參考官網

※JstlView 需要 JSTL 的 jar 包,但此例可以不加這個屬性,只要一加上,也不用寫上面那個 viewClass屬性,DispatcherServlet.resolveViewName,裡面的迴圈有個 View,會回傳 JstlView,原本是 InternalResourceView
因為 InternalResourceViewResolver 的預設建構子有寫


※上面的 gradle.build 有寫兩個,要小心註解那一個是有問題的,會出找不到 jstl 的 class

※設定好後,controller 只要回傳 hello 即可


※編譯及執行問題

編譯時找不到 Servlet,可在專案按右鍵 Build Path >> Configure Build Path >> Libraries 活頁標籤裡的 Add Library,然後如下操作



運行時出現找不到 DispatcherServlet 的 class,可如下操作

※tomcat 還要打包到指定的路徑上,這裡不選 ,jar 包就不會包到指定的地方,此時就找不到jar 檔而報錯,所以只要將 gradle 下載的 jar 選起來即可


※@RequestMapping

※value 支援三種通配符:?、*、**

※@RequestMapping(value = "*.mvc", method = RequestMethod.POST, params = { "xxx", "ooo=9" }, headers="Host=localhost:9080")

※params 表示一定要有屬性 xxx 和 ooo,且屬性 ooo一定要是 9 的,且 request header 的 Host 一定要是 localhost:9080 才可以請求,否則 404

※param 裡的都是字串,只能用「=」、「!=」,沒有正則表達式

※注意如果 params 過了,但 headers 沒通過,錯誤訊息還是顯示 params 沒通過

※瀏覽器可看到 header 的訊息,以 Chrome 為例



※@RequestParam、@RequestHeader、@CookieValue

@RequestMapping(value = "abc/*.mvc")
public String hello(
    @RequestParam(value = "id", defaultValue = "0") int i,
    @RequestParam(value = "name", required = false) String n,
    @RequestHeader(value = "Accept-Language") String alang, 
    @CookieValue("JSESSIONID") String cookie) {
    
    System.out.println(i + ":" + n + ":" + alang + ":" + cookie);
    return "hello";
}

※這三個 annotation 裡的屬性一樣,用法也差不多,也都只能寫在參數裡

※假設 hello(@RequestParam(value="id") int id),因為都叫 id,所以 annotation 可以不寫,主要是用在 defaultValue 和 required 這兩個屬性要設定時

※注意如果是基本型態,沒有 null,required 設定 false,還是會 400,可以改成 Wrapper 型態或者使用 defaultValue

※@RequestHeader 的 value 一樣是看上一張圖

※@CookieValue 的 value 看上一張圖的 Cookie

※測試
<a href="ooo/xxx/abc/bruce.mvc?id=99&name=bruce">param</a>




※@PathVariable

※只能寫在參數裡


@RequestMapping("abc/{name}")
public String pathVar(@PathVariable("name") String n) {
    System.out.println(n);
    return "hello";
}

※{name} 必需和 @PathVariable 裡的字對應



<a href="ooo/xxx/abc/bruce">param</a>
<a href="ooo/xxx/abc/bruce.mvc">param</a>

※如果 web.xml 設定附檔名對應,就寫第二種;如果全部都給 spring 處理,就寫第一種,抓到的都是 bruce



※其他方式

※mvc:view-controller

假設 controller 沒做什麼事,只是重導而已,可以用這個,就不用寫 controller 了
官網連結

※testServlet-servlet.xml

<mvc:view-controller path="ooo/xxx/view.mvc" view-name="hello" />
<mvc:annotation-driven />

※path 為請求的網址,和 @RequestMapping 不同,如果 web.xml 設定 *.mvc,在這裡一定要打才行,但 @RequestMapping 可打可不打

※view-name 會配合 InternalResourceViewResolver 設定的路徑

※annotation-driven 不寫時,ooo/xxx/view.mvc 這個請求沒有問題,但其他的,如上面有很多的其他請求會 404,加上這個才可以正常訪問

※如果這個設定和 controller 都寫了,會以 controller 為主


※重導

@RequestMapping("view")
public String takeView() {
    System.out.println("yeah");
    return "forward:/WEB-INF/hello.jsp";
    //return "redirect:/success.html";
}
------------------------------
<a href="ooo/xxx/view.mvc">view</a>

※使用重導的方式,InternalResourceViewResolver 是沒有作用的

.直接請求重導 forward
A想拿東西給B,一進門看到C,C會轉交給B,有沒有轉交成功,A都會知道
伺服器端很忙,因為何服器會去找,所以可以找到WEB-INF之下的資源,所以網址不變
使用 RequestDispatcher


.間接請求重導 redirect
A想拿東西給B,一進門看到C,C叫他自己去找B
用戶端很忙,因為是用戶端,所以找不到WEB-INF之下的資源,所以網址會變
使用 HttpServletRequest,很明顯是用戶端


※不寫 web.xml 


@WebServlet(
    urlPatterns = "*.mvc", 
    initParams = @WebInitParam(
        name = "contextConfigLocation", 
        value = "/WEB-INF/applicationContext.xml"
    )
)
public class TestDispatcherServlet extends DispatcherServlet {}

※隨便寫個類實作 DispatcherServlet,然後如上代碼,注意 @WebInitParam 寫在 @WebServlet 裡面才有用

※如果不寫  @WebInitParam,那預設的 spring 設定檔名是「套件名.類名-servlet.xml」,此例為 init.TestDispatcherServlet-servlet.xml

※又如果不想用 applicationContext,直接使用 annotation,可如下設定
@WebServlet(
    urlPatterns = "/", 
    initParams = {
        @WebInitParam(name = "contextConfigLocation", value = "controller.XxxAction"),
        @WebInitParam(name = "contextClass", value = "org.springframework.web.context.support.AnnotationConfigWebApplicationContext") })
public class TestDispatcherServlet extends DispatcherServlet {}

2016年1月24日 星期日

HelloWorld 和 HibernateUtil (Hibernate4.x一)

Hibernate3.x的HibernateUtil裡面的buildSessionFactory()在第4版已經過時了,雖然還是可以run,如果不喜歡看到錯誤,稍加修改就沒問題了,從官網可以複製,但執行時出現「Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set」的錯,要修改成以下的樣子才OK
HibernateUtil.java
private static final SessionFactory sessionFactory = buildSessionFactory();
    
private static SessionFactory buildSessionFactory() {
    try {
        Configuration configuration = new Configuration();
        configuration.configure();
    
        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
    
        SessionFactory sessionFactory = configuration
                .buildSessionFactory(serviceRegistry);
        return sessionFactory;
        // return new Configuration().configure().buildSessionFactory(
        // new StandardServiceRegistryBuilder().build() );
    } catch (Throwable ex) {
        // Make sure you log the exception, as it might be swallowed
        System.err.println("Initial SessionFactory creation failed." + ex);
        throw new ExceptionInInitializerError(ex);
    }
}
    
public static SessionFactory getSessionFactory() {
    return sessionFactory;
}

※註解的部分是官網的,就是會錯

※使用時和第3版一樣Session s = HibernateUtil.getSessionFactory().getCurrentSession();



<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.3.11.Final</version>
</dependency>

※只下載hibernate-core,3.x版在執行時,會出現找不到javassist的錯誤訊息,所以還得下載org.javassist,但第4版我一樣用maven,就直接有了,所以下載這個jar即可



其他測試程式如下:

hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:orcl</property>
        <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
        <property name="hibernate.connection.username">username</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <!-- <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property> -->
        <property name="hibernate.current_session_context_class">thread</property>
        <!-- <mapping resource="hbm/Dept.hbm.xml" /> -->
        <mapping class="vo.Dept" />
    </session-factory>
</hibernate-configuration>



Dept.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 2016/1/24 ?U?? 07:30:42 by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
    <class name="vo.Dept" table="DEPT">
        <id name="deptno" type="java.lang.Integer">
            <column name="DEPTNO" />
            <generator class="assigned" />
        </id>
        <property name="dname" type="java.lang.String">
            <column name="DNAME" />
        </property>
        <property name="loc" type="java.lang.String">
            <column name="LOC" />
        </property>
    </class>
</hibernate-mapping>



Dept.java

@Entity
@Table
public class Dept {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "DEPT_SEQ")
    private Integer deptno;
    
    private String dname;
    
    private String loc;
    // setter/getter...
}



測試類

Session s = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction tx = s.beginTransaction();
try {
    Query q = s.createQuery("from Dept");
    @SuppressWarnings("unchecked")
    List<Dept> e = q.list();
    for (Dept d : e) {
        System.out.println(d.getDeptno());
        System.out.println(d.getDname());
        System.out.println(d.getLoc());
        System.out.println();
    }

} catch (Exception e) {
    tx.rollback();
    System.err.println("例外錯誤!");
    e.printStackTrace();
} finally {
    if (s.isOpen()) {
        s.close();
    }
}

※我有將開發工具Hibernate3.x的Hello World (使用Hibernate tools)(Hibernate3.x一)設定成4.3版了,但cfg.xml和hbm.xml的dtd還是3,都和Hibernate3.x一樣

※cfg的mapping resource對應hbm.xml; cfg的mapping class對應annotation都和Hibernate3.x一樣,擇一使用即可

2016年1月23日 星期六

Hibernate 3.x 整合 Spring 3.x

Hibernate3.x 整合 Spring3.x可以不需要hibernate.cfg.xml,就先以這為例



※XML設定

pom.xml


<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
    
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>3.6.10.Final</version>
</dependency>
    
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
</dependency>

※雖然不用hibernate.cfg.xml,但jar檔當然還是要,而hibernate-core會用到javassist,所以也要下載

※如果不喜歡用DBCP,可以用C3P0,可參考Spring 管理 JdbcTemplate、DBCP、C3P0、JdbcDaoSupport (Spring3.x 二十三)的最後一項



applicationContext.xml

<context:annotation-config />
<context:component-scan base-package="dao.impl" />
    
<bean id="ds" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
    <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl" />
    <property name="username" value="username" />
    <property name="password" value="password" />
</bean>
    
<bean id="sf"
    class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="ds" />
    <property name="mappingResources">
        <list>
            <value>hbm/Dept.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.use_sql_comments">true</prop>
        </props>
    </property>
</bean>
    
<bean id="ht" class="org.springframework.orm.hibernate3.HibernateTemplate">
    <property name="sessionFactory" ref="sf" />
</bean>

※可以從這裡複製,然後再修改,比較不會錯,最後的bean沒得複製,這行是因為Spring有提供一個叫HibernateTemplate的東西,相當於Hibernate的org.hibernate.Session

※props也可用如下的寫法:
<value>
    hibernate.dialect=org.hibernate.dialect.OracleDialect
    hibernate.show_sql=true
    hibernate.format_sql=true
    hibernate.use_sql_comments=true
</value>



IDept.java

public interface IDept {
    public boolean insert(Dept dept);
    public List<Dept> QueryAll();
}



DeptImpl.java

@Repository
public class DeptImpl extends HibernateDaoSupport implements IDept {
    @Autowired
    public DeptImpl(HibernateTemplate ht) {
        super.setHibernateTemplate(ht);
    }
    
    // 不用HibernateTemplate
    @Autowired
    private SessionFactory sf;
    
    @Override
    public boolean insert(Dept dept) {
        return super.getHibernateTemplate().save(dept) != null;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public List<Dept> QueryAll() {
        // 不用HibernateTemplate
        Session session = sf.openSession();
        Query q = session.createQuery("from Dept");
        return q.list();

        // return super.getHibernateTemplate().find("from Dept");
    }
}



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IDept dao = ctx.getBean("deptImpl", IDept.class);
Dept vo = new Dept();
vo.setDname("業務部");
vo.setLoc("舊金山");
System.out.println(dao.insert(vo));
    
for (Dept d : dao.QueryAll()) {
    System.out.println(d.getDname());
}
    
((ClassPathXmlApplicationContext) ctx).close();


※此次專案圖



※Annotation設定

可參考API,package是org.springframework.orm.hibernate3.annotation,裡面只有一隻class,叫AnnotationSessionFactoryBean

將XML設定的applicationContext.xml的sf改成以下的樣子,注意class不同了

applicationContext.xml

<bean id="sf"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="ds" />
    <property name="annotatedClasses">
        <list>
            <value>vo.Dept</value>
        </list>
    </property>    
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.use_sql_comments">true</prop>
        </props>
    </property>
</bean>

※屬性名稱annotatedClasses下面放class就搞定了


Dept.java

@Entity
@Table
public class Dept {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "XXX")
    @SequenceGenerator(name = "XXX", sequenceName = "DEPT_SEQ")
    private Integer deptno;
    
    private String dname;
    
    private String loc;
    // setter/getter...
}





※getCurrentSession

如果想使用CurrentSession還要設定


applicationContext.xml

<tx:annotation-driven transaction-manager="txManagerHibernate" />
    
<bean id="txManagerHibernate"
    class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sf" />
</bean>
    
<bean id="txManagerJDBC"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds" />
</bean>
    
    
<!-- LocalSessionFactoryBean的hibernateProperties,這兩個prop不用設定 -->
<prop key="hibernate.current_session_context_class">
    org.springframework.orm.hibernate3.SpringSessionContext
</prop>
    
<prop key="hibernate.transaction.factory_class">
    org.springframework.orm.hibernate3.SpringTransactionFactory
    org.hibernate.transaction.JTATransactionFactory
</prop>

※txManagerHibernate和txManagerJDBC擇其一,其實還有JTA,看官網

※hibernate.current_session_context_class要設就設這個,執行LocalSessionFactoryBean時,裡面其實就會幫我們加了(底層的589行),但整合就不能使用thread了

※hibernate.transaction.factory_class,執行LocalSessionFactoryBean就會幫我們判斷加其中之一了,不要設定



DeptImpl.java

@Transactional
public boolean insert(Dept dept) {
    Session session = sf.getCurrentSession();
    
    Dept dept1 = new Dept();
    dept1.setDeptno(160);
    dept1.setDname("業務部");
    dept1.setLoc("舊金山");
    
    session.save(dept);
    session.save(dept1);
    
    return true;
}
    
@Transactional(readOnly = true)
public List<Dept> QueryAll() {
    //...
}

※applicationContext.xml設定完了還不夠,還得在方法上加@Transactional,這樣子不用顯式的呼叫commit、rollback,就可以自動幫我們完成commit、rollback了

※雖然是select,還是要加@transactional,不然一樣會報錯;加上readOnly = true效能會比較好


※還可以applicationContext.xml和hibernate.cfg.xml都並存,還有針對特定的方法名執行transaction,看Hibernate4.x 整合 Spring3.x

Spring 管理 JdbcTemplate、DBCP、C3P0、JdbcDaoSupport (Spring3.x 二十三)

將上一篇的程式碼交給Spring管理


applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    
    <context:annotation-config />
    <context:component-scan base-package="dao.impl" />
    
    <bean id="ds"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource"
        p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@127.0.0.1:1521:orcl"
        p:username="username" p:password="password" />
    
    <bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="ds" />
</beans>



IDepDAO.java

public interface IDeptDAO {
    public static final String PK_SQL = "SELECT DEPT_SEQ.NEXTVAL FROM DUAL";
    public Long get();
    public int save(Long deptno, String dname, String loc);
}



DeptDAOImpl.java

@Repository
public class DeptDAOImpl implements IDeptDAO {
    @Autowired
    private JdbcTemplate jt;
    
    public DeptDAOImpl() {
    }
    
    public DeptDAOImpl(JdbcTemplate jt) {
        this.jt = jt;
    }
    
    @Override
    public Long get() {
        return jt.queryForObject(PK_SQL, Long.class);
    }
    
    @Override
    public int save(Long deptno, String dname, String loc) {
        String sql = "INSERT INTO DEPT VALUES (?, ?, ?)";
        return jt.update(sql, deptno, dname, loc);
    }
}



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IDeptDAO dao = ctx.getBean("deptDAOImpl", IDeptDAO.class);
int success = dao.save(dao.get(), "業務部", "芝加哥");
System.out.println("成功新增" + success + "筆!");
    
((ClassPathXmlApplicationContext) ctx).close();



這次的專案圖


這邊有個小技巧,注意DeptDAOImpl.java的圖示,右上角有個「S」,表示使用getBean可以抓的到


官方的${}是在外面新增一個檔案,然後抓進去用的,如下:

xxx.properties

jdbc.d=oracle.jdbc.driver.OracleDriver
jdbc.u1=jdbc:oracle:thin:@127.0.0.1:1521:orcl
jdbc.u2=username
jdbc.p=password



applicationContext.xml

<context:property-placeholder location="classpath:xxx.properties" />
    
<bean id="ds"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="${jdbc.d}" p:url="${jdbc.u1}" p:username="${jdbc.u2}"
    p:password="${jdbc.p}" />




※DBCP和C3P0的支援

官網可複製,快到14.3.2的上面兩張表

applicationContext.xml

<context:property-placeholder location="classpath:xxx.properties" />
    
<bean id="ds" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.d}" />
    <property name="url" value="${jdbc.u1}" />
    <property name="username" value="${jdbc.u2}" />
    <property name="password" value="${jdbc.p}" />
</bean>
    
<bean id="ds2" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass" value="${jdbc.d}" />
    <property name="jdbcUrl" value="${jdbc.u1}" />
    <property name="user" value="${jdbc.u2}" />
    <property name="password" value="${jdbc.p}" />
</bean>



pom.xml

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
    
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

※以上是基本設定,還有很多設定:
DBCP的文件可參考這裡,而API可參考這裡
C3P0的文件可參考這裡,而API可參考這裡,package是com.mchange.v2.c3p0,class是ComboPooledDataSource



※JdbcDaoSupport

JdbcDaoSupport可以幫我們取得JdbcTemplate,所以原來的程式碼還可以再改成如下的寫法


@Repository
public class DeptDAOImpl extends JdbcDaoSupport implements IDeptDAO {
    public DeptDAOImpl() {}
    
    @Autowired
    public DeptDAOImpl(JdbcTemplate jt) {
        super.setJdbcTemplate(jt);
    }
    
    @Override
    public Long get() {
        return super.getJdbcTemplate().queryForObject(PK_SQL, Long.class);
    }
    
    @Override
    public int save(Long deptno, String dname, String loc) {
        String sql = "INSERT INTO DEPT VALUES (?, ?, ?)";
        return super.getJdbcTemplate().update(sql, deptno, dname, loc);
    }
}

※繼承了JdbcDaoSupport以後,原本的JdbcTemplate統統都用super取得了

2016年1月22日 星期五

使用JdbcTemplate (Spring3.x 二十二)

JdbcTemplate就是將傳統的JDBC封裝起來,讓程式員只需管自行撰寫SQL語句和處理ResultSet,不用管什麼關閉連線、關閉ResultSet、Transaction…等

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>

※除了context一定要下載外,還要有jdbc的jar,當然資料庫的Driver也要



取得Connection

import java.sql.SQLException;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
public class TestJDBCTemplate {
    private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
    private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";
    
    public static void main(String[] args) {
        // DriverManagerDataSource ds = new DriverManagerDataSource(DRIVER,
        // URL,
        // USERNAME, PASSWORD);
    
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(DRIVER);
        ds.setUrl(URL);
        // ds.setUsername(USERNAME);
        // ds.setPassword(PASSWORD);
    
        System.out.println(ds.getUrl());
        System.out.println(ds.getUsername());
        System.out.println(ds.getPassword());
    
        try {
            System.out.println(ds.getConnection(USERNAME, PASSWORD)
                    .isClosed());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    
        // try {
        // System.out.println(ds.getConnection().isClosed());
        // } catch (SQLException e) {
        // e.printStackTrace();
        // }
    }
}

※DriverManagerDataSource是overloading,下面的程式碼還會寫其他的方式



查詢一筆欄位

DriverManagerDataSource ds = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
ds.setDriverClassName(DRIVER);
    
JdbcTemplate jt = new JdbcTemplate(ds);
String sql = "SELECT LOC FROM DEPT WHERE DEPTNO = ? AND DNAME = ?";
String loc = jt.queryForObject(sql, new Object[] { 10, "ACCOUNTING" }, String.class);
System.out.println(loc);
System.out.println(jt.queryForInt("SELECT COUNT(*) FROM DEPT"));



查詢多筆

@SuppressWarnings("deprecation")
DriverManagerDataSource ds = new DriverManagerDataSource(DRIVER, URL, USERNAME, PASSWORD);
JdbcTemplate jt = new JdbcTemplate(ds);
    
String sql = "SELECT * FROM DEPT WHERE DEPTNO < ?";
List<Map<String, Object>> rtn = jt.queryForList(sql, 50);
    
for (Map<String, Object> map : rtn) {
    System.out.println(map.get("DEPTNO"));
    System.out.println(map.get("DNAME"));
    System.out.println(map.get("LOC") + "\n");
}



查詢VO 

Dept.java

public class Dept {
    private Integer deptno;
    private String dname;
    private String loc;
    // setter/getter...
}



測試

DriverManagerDataSource ds = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
ds.setDriverClassName(DRIVER);
JdbcTemplate jt = new JdbcTemplate(ds);
    
String sql = "SELECT * FROM DEPT WHERE DEPTNO >= ?";
List<Dept> list = jt.query(sql, new Object[] { 20 },
    new RowMapper<Dept>() {
        @Override
        public Dept mapRow(ResultSet rs, int rowNum)
                throws SQLException {
            System.out.println("目前行數" + rowNum);
            Dept vo = new Dept();
            vo.setDeptno(rs.getInt(1));
            vo.setDname(rs.getString(2));
            vo.setLoc(rs.getString(3));
            return vo;
        }
    });
    
for (Iterator<Dept> it = list.iterator(); it.hasNext();) {
    Dept dept = it.next();
    System.out.println(dept.getDname());
}

※主要是RowMapper較複雜

新增

@SuppressWarnings("deprecation")
DriverManagerDataSource ds = new DriverManagerDataSource(DRIVER, URL, USERNAME, PASSWORD);
JdbcTemplate jt = new JdbcTemplate(ds);
    
String sql = "INSERT INTO DEPT VALUES (?, ?, ?)";
int success = jt.update(sql, 50, "業務部", "芝加哥");
System.out.println("成功新增" + success + "筆!");

※增刪改都是用update方法



PK為流水號

DriverManagerDataSource ds = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
ds.setDriverClassName(DRIVER);
    
JdbcTemplate jt = new JdbcTemplate(ds);
KeyHolder pk = new GeneratedKeyHolder();
    
int success = jt.update(new PreparedStatementCreator() {
    @Override
    public PreparedStatement createPreparedStatement(Connection con)
            throws SQLException {
        String sql = "INSERT INTO DEPT(DNAME, LOC) VALUES (?, ?)";
        PreparedStatement pstmt = con.prepareStatement(sql);
        pstmt.setString(1, "業務部");
        pstmt.setString(2, "芝加哥");
        return pstmt;
    }
}, pk);
System.out.println("成功新增" + success + "筆!");
System.out.println("PK是" + pk.getKey());

※此方法不適用Oracle,我目前沒其他家的資料庫,所以沒試過成不成功



Oracle的PK為流水號

DriverManagerDataSource ds = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
ds.setDriverClassName(DRIVER);
JdbcTemplate jt = new JdbcTemplate(ds);
    
String pkSql = "SELECT DEPT_SEQ.NEXTVAL FROM DUAL";
Long pk = jt.queryForObject(pkSql, Long.class);
    
String insSql = "INSERT INTO DEPT VALUES (?, ?, ?)";
int success = jt.update(insSql, pk, "業務部", "芝加哥");
System.out.println("成功新增" + success + "筆!");
System.out.println("PK是" + pk);

※Oracle先從資料庫把Sequence查詢出來存在一個變數,然後再做新增

2016年1月20日 星期三

AOP4-Introduction、Advice (Spring3.x 二十一)

Introduction用declare-parents的標籤實現,它的功能是有兩個interface,彼此之間沒有關係,透過這個功能可以讓它們產生父子關係,而它們兩彼此沒有耦合


※XML設定



IAOP.java和AOP.java

public interface IAOP {
    public String C();
}

@Named
public class AOP implements IAOP {
    @Override
    public String C() {
        System.out.println("無參數C");
        return "c";
    }
}



IChess.java和ChessImpl

public interface IChess {
    public void chessName();
    public String C();
}
    
@Component
public class ChessImpl implements IChess {
    @Override
    public void chessName() {
        System.out.println("象棋!");
    }
    
    @Override
    public String C() {
        System.out.println("ChessImpl");
        return "ChessImpl";
    }
}



applicationContext.xml

<context:component-scan base-package="\" />
    
<aop:config>
    <aop:aspect ref="beforeAfter">
        <aop:declare-parents types-matching="aop.AOP+" implement-interface="service.IChess" default-impl="impl.ChessImpl" />
    </aop:aspect>
</aop:config>

※aop.AOP+表示AOP的兒子,也可以用「*」等,和之前的pointcut一樣

※整行的意思是AOP的兒子可以轉成IChess


測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
// IAOP aop = (IAOP) ctx.getBean("AOP");
IChess aop = (IChess) ctx.getBean("AOP");
// IChess aop = (IChess) ctx.getBean("AOP", IAOP.class);
// IChess aop = (IChess) ctx.getBean("AOP", IChess.class);
aop.chessName();
aop.C();
// 失敗
// AOP aop = (AOP) ctx.getBean("AOP");
// IAOP aop = (IAOP) ctx.getBean("chessImpl");
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
象棋!
無參數C

※要注意方法一樣並沒有轉成設定的方法

※上面4行都是ok的,也就是AOP可以轉成自己的IAOP和設定的IChess

※這種方式只有支援interface,所以AOP轉AOP會錯

※xml的設定是AOP的兒子可以轉IChess,相反當然是不行



※Annotation設定

Annotation用的是@DeclareParents,只能寫在Field,我不太會用





※Advice

前幾篇用的before、after等方法,這是較早期用的方式

BeforeAfter.java

@Named
@Aspect
public class BeforeAfter implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) {
        System.out.println("方法=" + method);
        System.out.println("參數=" + Arrays.toString(args));
        System.out.println("物件=" + target);
    }
}

※要實作MethodBeforeAdvice的before方法


applicationContext.xml

<context:component-scan base-package="\" />
    
<aop:config>
    <aop:advisor advice-ref="beforeAfter" pointcut="execution(public void aop.AOP.A())" />
</aop:config>



測試類

    
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
IAOP aop = (IAOP) ctx.getBean("AOP");
aop.A();
    
((ClassPathXmlApplicationContext) ctx).close();

※以前的方法每一個class都要實作MethodBeforeAdvice,所以現在很少人用了


AOP3-Returning、Throwing、Around (Spring3.x 二十)

※AOP傳參數、Returning

※之前的before和after都沒有傳參數,現在傳個參數,還有回傳值的接收
使用B方法傳參數; D方法接收回傳值


※XML設定

IAOP.java和AOP.java

public interface IAOP {
    public void B(String s, Book b);
    public String D(String d);
}
    
@Named
public class AOP implements IAOP {
    @Override
    public void B(String s, Book b) {
        System.out.println("有參數B");
    }
    
    @Override
    public String D(String d) {
        System.out.println("有參數D");
        return d;
    }
}



Book.java

@Component("bo")
public class Book {
    private String bookName;
    private int bookPrice;
    // setter/getter...
}



BeforeAfter.java

@Named
public class BeforeAfter {
    public void beforeParam(Object o, Book b) {
        System.out.println("之前+參數,參數是:" + o + "和" + b.getBookName());
    }
    
    public void afterReturn(String s) {
        System.out.println("回傳值" + s);
    }
}



applicationContext.xml

<context:component-scan base-package="\" />
    <aop:config>
        <aop:aspect ref="beforeAfter">
    
        <aop:before method="beforeParam" pointcut="execution(public void aop.AOP.B(String, vo.Book)) and args(p1, p2)" arg-names="p1, p2" />
    
        <aop:after-returning method="afterReturn" pointcut="execution(public String aop.AOP.D(String))" returning="r1" arg-names="r1"/>
    </aop:aspect>
</aop:config>

※p1、p2, r1要對應好,Book用包.類,一般的可省略,如:String、int等


測試類
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
Book book = new Book();
book.setBookName("七龍珠");
book.setBookPrice(50);
    
IAOP aop = ctx.getBean("AOP", IAOP.class);
aop.B("ooo", book);
aop.D("ddd");
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
之前+參數,參數是:ooo和七龍珠
有參數B
有參數D
回傳值ddd


※Annotation設定

@Named
@Aspect
public class BeforeAfter {
    @Before(value="execution(public void aop.AOP.B(String, vo.Book)) and args(p1, p2)", argNames="p1, p2")
    public void beforeParam(Object o, Book b) {
        System.out.println("之前+參數,參數是:" + o + "和" + b.getBookName());
    }
    
    @AfterReturning(pointcut = "execution(public String aop.AOP.D(String))", returning = "r1", argNames = "r1")
    public void afterReturn(String s) {
        System.out.println("回傳值" + s);
    }
}



※Throwing 

如果有例外,可以設定抓例外的資訊



※XML設定 


IAOP.java和AOP.java

public interface IAOP {
    public void D(String d) throws ArithmeticException;
}
    
@Named
public class AOP implements IAOP {
    @Override
    public void D(String d) {
        System.out.println("有參數D");
        try {
            int a = 1 / 0;
        } catch (ArithmeticException e) {
            throw new ArithmeticException();
        }
    }
}



BeforeAfter.java

@Named
public class BeforeAfter {
    public void afterThrow(Exception e) {
        System.out.println("AOP截取例外:" + e);
    }
}



applicationContext.xml

<context:component-scan base-package="\" />
    
<aop:config>
    <aop:aspect ref="beforeAfter">
        <aop:after-throwing method="afterThrow" pointcut="execution(public void aop.AOP.D(String))" throwing="t1" arg-names="t1" />
    </aop:aspect>
</aop:config>

※和回傳值一樣,只不過屬性名稱變成throwing,要和arg-names對應


測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
IAOP aop = ctx.getBean("AOP", IAOP.class);
try {
    aop.D("ddd");
} catch (ArithmeticException e) {}
    
((ClassPathXmlApplicationContext) ctx).close();

※結果:
有參數D
AOP截取例外:java.lang.ArithmeticException

※注意這個D方法沒有回傳值,但還是可以抓到喔!


※Annotation設定

@Named
@Aspect
public class BeforeAfter {
    @AfterThrowing(pointcut = "execution(public void aop.AOP.D(String))", throwing = "t1", argNames = "t1")
    public void afterThrow(Exception e) {
        System.out.println("AOP截取例外:" + e);
    }
}



※Around

IAOP.java和AOP.java

public interface IAOP {
    public String B(String s, Book b);
}
    
@Named
public class AOP implements IAOP {
    @Override
    public String B(String s, Book b) {
        System.out.println("有參數B");
        // try {
        // int a = 1 / 0;
        // } catch (Exception e) {
        // System.out.println("我錯了B");
        // }
        return "參數B回傳";
    }
}



Book.java

@Component("bo")
public class Book {
    private String bookName;
    private int bookPrice;
    // setter/getter...
}



BeforeAfter.java

// @Named
// @Aspect
public class BeforeAfter {
    // @Around("execution(public String aop.AOP.B(String, vo.Book))")
    // ProceedingJoinPoint可取得方法資訊
    public void around(ProceedingJoinPoint pjp) {
        System.out.println("around start" + Arrays.toString(pjp.getArgs()));
    
        try {
            // Object rtn = pjp.proceed();
            Object rtn = pjp.proceed(new Object[] { "hello String", new Book() });
            System.out.println("rtn=" + rtn);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}



<context:component-scan base-package="\" />
<!--     <aop:aspectj-autoproxy/> -->
    
<aop:config>
    <aop:aspect ref="beforeAfter">
        <aop:around method="around" pointcut="execution(public String aop.AOP.B(String, vo.Book))" />
    </aop:aspect>
</aop:config>



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IAOP aop = ctx.getBean("AOP", IAOP.class);
Book book = new Book();
book.setBookName("七龍珠");
book.setBookPrice(50);
aop.B("ooo", book);
    
((ClassPathXmlApplicationContext) ctx).close();


※結果:
around start[ooo, vo.Book@3cc2931c]
有參數B
rtn=參數B回傳

※在BeforeAfter.java的proceed()是overloading,就這兩個方法,一開始會先像before一樣先執行,然後要繼續就要使用proceed(),沒加參數會自動幫我們執行,但要加型態和順序就要和B方法的參數一樣,否則會出錯

2016年1月18日 星期一

AOP2-五種AOP方法的正常和例外是否執行 (Spring3.x 十九)

上一篇的AOP只有after和before,其實它還有after-returning、after-throwing、around這三個較重要的功能,一樣也是測例外和正常的情形

※XML設定


applicationContext.xml
<aop:config>
    <aop:pointcut expression="execution(public void aop.AOP.A())" id="aaa" />
    <aop:pointcut expression="execution(public void aop.AOP.B(String))" id="bbb" />
    <aop:pointcut expression="execution(public String aop.AOP.C())" id="ccc" />
    <aop:pointcut expression="execution(public String aop.AOP.D(String))" id="ddd" />
    
    <aop:aspect ref="beforeAfter">
        <!-- <aop:after-returning method="after" pointcut-ref="aaa" /> -->
        <!-- <aop:after-returning method="after" pointcut-ref="bbb" /> -->
        <!-- <aop:after-returning method="after" pointcut-ref="ccc" /> -->
        <!-- <aop:after-returning method="after" pointcut-ref="ddd" /> -->
    
        <!-- <aop:after-throwing method="after" pointcut-ref="aaa" /> -->
        <!-- <aop:after-throwing method="after" pointcut-ref="bbb" /> -->
        <!-- <aop:after-throwing method="after" pointcut-ref="ccc" /> -->
        <!-- <aop:after-throwing method="after" pointcut-ref="ddd" /> -->
    
        <aop:around method="after" pointcut-ref="aaa" />
        <aop:around method="after" pointcut-ref="bbb" />
        <aop:around method="after" pointcut-ref="ccc" />
        <aop:around method="after" pointcut-ref="ddd" />
    </aop:aspect>
</aop:config>

※整理成一張表

before/after 程式碼正常 throws try/catch 無throws和try/catch
會執行AOP
會執行Advice
繼續往下執行  
after-returning 程式碼正常 throws try/catch 無throws和try/catch
會執行AOP    
會執行Advice
繼續往下執行  
after-throwing 程式碼正常 throws try/catch 無throws和try/catch
會執行AOP    
會執行Advice
繼續往下執行  
around 程式碼正常 throws try/catch 無throws和try/catch
會執行AOP
會執行Advice        
繼續往下執行





※Annotation設定

因為不要藕合,所以AOP.java不知道BeforeAfter.java的存在,所以只要在BeforeAfter增加即可


applicationContext.xml

<context:component-scan base-package="\" />
<aop:aspectj-autoproxy/>
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />


※第2和第3行擇其一即可


@Named
@Aspect
public class BeforeAfter {
    @Before("execution(public * aop.AOP.*(..))")
    public void before() {
        System.out.println("之前執行!");
    }
    
    @After("execution(public * aop.AOP.*(..))")
    public void after() {
        System.out.println("之後執行!");
    }
}



@Named
@Aspect
public class BeforeAfter {
    @Pointcut("execution(public * aop.AOP.*(..))")
    private void shareAOP(){}
    
    @Before("BeforeAfter.shareAOP()")
    public void before() {
        System.out.println("之前執行!");
    }
    
    @After("BeforeAfter.shareAOP()")
    public void after() {
        System.out.println("之後執行!");
    }
}

※這個等同xml的pointcut-ref 功能,一定要有「()」


@Named
@Aspect
public class BeforeAfter {
    @Pointcut("execution(public * aop.AOP.*(..))")
    private void shareAOP(){}
    
    @AfterReturning(pointcut = "BeforeAfter.shareAOP()")
    //    @AfterReturning("execution(public * aop.AOP.*(..))")
    public void returnAfter() {
        System.out.println("return 之後執行!");
    }
    
    @AfterThrowing(pointcut = "BeforeAfter.shareAOP()")
    //    @AfterThrowing("execution(public * aop.AOP.*(..))")
    public void throwAfter() {
        System.out.println("throw執行!");
    }
    
    @Around("BeforeAfter.shareAOP()")
    //    @Around("execution(public * aop.AOP.*(..))")
    public void after() {
        System.out.println("around執行!");
    }
}

※@AfterReturning、@AfterThrowing、@Around用法都一樣

※如果只要匹配A和C方法,可以這樣下
@Pointcut("execution(public * aop.AOP.A(..)) || execution(public * aop.AOP.C(..))")


2016年1月17日 星期日

AOP1-before after (Spring3.x 十八)

AOP簡單來說就是假如有一行程式的前面或後面,想增加固定的程式,但又不想讓這支程式知道,所以通過設定的方式來實現

Spring 的 AOP 是 runtime 等級的,有些是 compile 等級的
runtime 是以方法為基底,如在方法前後加程式碼;而 compile 可以更細,如在 某個方法裡的if 之前/後、for 迴圈之前/後


※1.先寫一支IOC,而且確定能run 

IAOP.java和AOP.java

public interface IAOP {
    public void A();
    public void B(String b);
    public String C();
    public String D(String d);
}
    
@Named
public class AOP implements IAOP {
    @Override
    public void A() {
        System.out.println("無參數A");
    }
    
    @Override
    public void B(String b) {
        System.out.println("有參數B");
    }
    
    @Override
    public String C() {
        System.out.println("無參數C");
        return "c";
    }
    
    @Override
    public String D(String d) {
        System.out.println("有參數D");
        return d;
    }
}



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IAOP aop = ctx.getBean("AOP", IAOP.class);
aop.A();
aop.B("bbb");
aop.C();
aop.D("ddd");
    
((ClassPathXmlApplicationContext) ctx).close();

※在applicationContext.xml加<context:component-scan base-package="\" />,然後執行,沒有錯再做第2步

※2.加入AOP

寫一支前後包住第1步寫的aop

BeforeAfter.java

@Named
public class BeforeAfter {
    public void before() {
        System.out.println("之前執行!");
    }
    
    public void after() {
        System.out.println("之後執行!");
    }
}



applicationContext.xml

<aop:config>
    <aop:aspect ref="beforeAfter">
        <aop:before method="before" pointcut="execution(public void aop.AOP.A())" />
        <aop:after method="after" pointcut="execution(public void aop.AOP.A())" />
    
        <aop:before method="before" pointcut="execution(public void aop.AOP.B(String))" />
        <aop:after method="after" pointcut="execution(public void aop.AOP.B(String))" />
    
        <aop:before method="before" pointcut="execution(public String aop.AOP.C())" />
        <aop:after method="after" pointcut="execution(public String aop.AOP.C())" />
    
        <aop:before method="before" pointcut="execution(public String aop.AOP.D(String))" />
        <aop:after method="after" pointcut="execution(public String aop.AOP.D(String))" />
    </aop:aspect>
</aop:config>

※結果:
之前執行!
無參數A
之後執行!
之前執行!
有參數B
之後執行!
之前執行!
無參數C
之後執行!
之前執行!
有參數D
之後執行!

※execution的語法和一般寫java的方法很像,可以參考官方完整的語法,但實在是太多了,已經有很厲害的人整理出來了,可參考這裡,我只大概了解一些,因為我也不太懂,有可能講錯,只是例子是真的有run過就是了

.格式為:<修飾子>    回傳值    包.類.方法(參數)    <異常>
.一定要用execution()包起來
.修飾子和異常可以不打,「*」代表全部,修飾子不打就是*,異常我不會用
.修飾子就是public、protected、private,預設我不清楚
.回傳值打「*」,代表任何回傳值
.方法打「*」,代表任何方法
.包.類打「*」,代表任何包.類
.包.類有個特殊的「..*」,譬如aop.*,表示aop下一層的任何類,但有可能有很多層,所以如果下aop..*就表示aop以下的任何類
.參數如果打「*」或「..」,表示任何參數,我看大部分都打..,很少人打*,但我試過是ok的
.execution(* aop..*.*(..)),這表示在aop以下的任何方法的任何參數,回傳值也是任何的就執行aop



上面的範例,方法ABCD的execution都用到兩次,很浪費,所以還可以把它提出來,變成以下的樣子
<aop:config>
    <aop:pointcut expression="execution(public void aop.AOP.A())" id="aaa" />
    <aop:pointcut expression="execution(public void aop.AOP.B(String))" id="bbb" />
    <aop:pointcut expression="execution(public String aop.AOP.C())" id="ccc" />
    <aop:pointcut expression="execution(public String aop.AOP.D(String))" id="ddd" />
    
    <aop:aspect ref="beforeAfter">
        <aop:before method="before" pointcut-ref="aaa" />
        <aop:after method="after" pointcut-ref="aaa" />
    
        <aop:before method="before" pointcut-ref="bbb" />
        <aop:after method="after" pointcut-ref="bbb" />
    
        <aop:before method="before" pointcut-ref="ccc" />
        <aop:after method="after" pointcut-ref="ccc" />
    
        <aop:before method="before" pointcut-ref="ddd" />
        <aop:after method="after" pointcut-ref="ddd" />
    </aop:aspect>
</aop:config>

※結果當然還是一樣

※也可以用剛剛講的AspectJ語法,一次使用
<aop:config>
    <aop:pointcut expression="execution(* aop.AOP.*(..))" id="abcd" />
    
    <aop:aspect ref="beforeAfter">
        <aop:before method="before" pointcut-ref="abcd" />
        <aop:after method="after" pointcut-ref="abcd" />
    </aop:aspect>
</aop:config>

※結果也是一樣

※這時候來了解一下名詞就會比較容易了解
.Join Point:BeforeAfter.java裡面的方法都是,可以想像成要連到主程式的連接點
.Point Cut:就是xml裡的<aop:pointcut/>,而裡面的屬性expression叫Point Cut表達式
.Advice:就是xml裡的<aop:before>等…,通知Point Cut匹配的方法之前(或之後…等)要執行Join Point裡的方法



※例外

如果程序有例外的情形又是怎麼樣呢?將程式碼改成以下的樣子

IAOP.java和AOP.java

public interface IAOP {
    public void A() throws Exception;
    public void B(String b);
    public String C();
    public String D(String d);
}
    
@Named
public class AOP implements IAOP {
    @Override
    public void A() throws Exception {
        System.out.println("無參數A");
        int a = 1 / 0;
    }
    
    @Override
    public void B(String b) {
        System.out.println("有參數B");
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            System.out.println("我錯了B");
        }
    }
    
    @Override
    public String C() {
        System.out.println("無參數C");
        int a = 1 / 0;
        return "c";
    }
    
    @Override
    public String D(String d) {
        System.out.println("有參數D");
        int a = 1 / 0;
        return d;
    }
}



測試類

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");    
IAOP aop = ctx.getBean("AOP", IAOP.class);
    
try {
    aop.A();
} catch (Exception e) {
    System.out.println("例外出現");
}
    
aop.B("bbb");
aop.C();
aop.D("ddd");
    
((ClassPathXmlApplicationContext) ctx).close();

※1/0是RuntimeException,可以不用try/catch,A和B方法用不同的方法包起來,C和D方法都不包,結果如下:
之前執行!
無參數A
之後執行!
例外出現
之前執行!
有參數B
我錯了B
之後執行!
之前執行!
無參數C
之後執行!
Exception in thread "main" java.lang.ArithmeticException: / by zero
//...
可以看出ABC可以正常出現之前之後,但因為C方法不包try/catch,所以不會繼續往下執行D方法了