2015年9月26日 星期六

5d3 調焦的方式

前言

第一次機身調焦…沒啥經驗搞了一個下午,能恢復鏡頭的準度,無價。

大部份的相機(單眼) 常常覺得對焦準了,但是放大在螢幕上看卻不是那麼一回事,就是有一點糊糊的… 很多時候是因為跑焦/移焦,有時候是前移,有時候是後移,如果是這樣,並非技術問題,一定要透過設定才能解決的。不解決日後的照片永遠都是糊的。

不能機身調焦的相機,只能把鏡頭送回原廠 ( 有些廠商像sigma 有出調焦器,也可以在家裡自己來 ),當然送回去了也不一定就幫你調到張張準,最好自己來。

測焦的方式

先把機身設成最高畫質的jpg就好,不需要用raw 來拍。

準焦的判定方式,以canon 家來說,就是先透過live view 先取得準焦的照片。拍的時候,記得放大倍率,通常可以放大到10倍。你可以採自動對焦,也可以手動對焦,我自己是以自動為主。

好了之後,關掉live view,轉轉對焦環,再直接拍一張自動對焦的照片。先回到電腦看圖。盡量用可以放很大的軟體來檢查,例如 lightroom , dpp 。如果二張你實在看不出差異,恭喜你沒有跑焦。如果比較模糊,以下是我的方法,比傳統的測焦方式來的快多了。傳統還要使用者判斷前移還是後移,這牽涉到很多問題,還需要二個被攝物來測試,比較麻煩。
直接透過機身,針對這個鏡頭,調 -5~ + 5 各拍一張,共11張 ,直接拿來和 live view的對照組做比對。

假設 +2 是最準的,就再來第二輪拍攝,這一輪的拍攝會縮小加減範圍。以 + 2 為主,向前/後各拍二張,包含 + 2 共五張。也就是0,1,2,3,4。拍攝過程,中間要轉轉對焦環讓它重新對焦。

在電腦中再行比對一次,如果還是覺得 +2 最準,那就成功了。如果你覺得+1看起來好像比較準,二張相差不多的情況下,就+1,+2 再多拍幾次以求確定。

被攝物的選擇

被攝物的基準,可以找有條碼的盒子,務必是方形垂直的物體,上面有字或是很細的條碼,比較有基準做比對。真的不行找個盒子貼1000元鈔票也可以,因為上面的細節比較多。

相機的設定

相機要在 1~2 公尺左右,固定 ISO , 光圈調最大,固定好相機,快門最好來個1/100 以上避免手震。

如果這個測試方式效果不好

如果-5~+5都和準焦的差很多...可能鏡頭偏移的比較嚴重,再放大範圍一些試試。如果調好了,時準時不準,那有可能是相機對焦性能的問題...佳能低階相機的對焦模組並不是很好。這個可能就要考慮砸錢換機啦。

2015年9月20日 星期日

5D3 如何註冊對焦點,並且一鍵切換成動態拍攝的模式

問題  :

拍攝的過程中,如果是靜物,可以慢慢的用bar 來移至自己想對焦的點,半按快門拍攝結束。

如果是突然出現的跑動中的小孩/小狗,你腦中想的是

 1. 要切到61點自動對焦 => 這樣他在螢幕中可視範圍中都對的到。
 2. 切換到 AI SERVO => 目的是針對移動中的被攝物鎖定持續追焦

當你手動做二件事的時候,被攝物早就跑走了。
於是我們透過一些調整,來讓你一舉達成此任務。

目的:

A. 一鍵切成  AI SERVO
B. 一鍵切成 61 點自動對焦
C. 二鍵齊按,則同時發生,放開則回到原來的對焦模式。


方法 :

A.
  A1 透過景深預覽鍵,長按為 AI SERVO ,放開回到ONE SHOT。如何做到 ? 先進MENU,移到這邊,點擊自訂控制。


A2 紅圈處為景深預覽,我們要把它改掉。按set 進去。

A3 裡面我們要把它改成 ONT SHOT <-> AI SERVO 切換


 A4. 確認

景深預覽鈕的位置 : 在你握持時,右手無名指那邊有一個凸凸的圓點,你按的時候,注意一下機頂螢幕,就會發現它從ONE SHOT ->AI SERVO 了。放開則自動回ONE SHOT。有這樣的話代表設定成功了。


B.
光這樣是不夠的,因為AI SERVO 的目的就是在拍攝移動物(雖然靜止物也可以),當移動物跑到預設的對焦區外的時候,手動調整對焦區會來不及,所以我們還需要一個鈕,按了會變成61點自動對焦,讓被攝物逃不出我們的手掌心。這個鈕會是 AF-ON 。但是它需要二個步驟來完成。

* 雖然你也可以將61點常時開著,這樣AI SERVO 就不用再擔心對焦區的問題。但有時相機並不如你想的那麼聰明…平常還是需要將對焦點限縮的。

第一個步驟 : 將AF-ON 的FOCUS 改成「註冊的對焦點」

 這一次我們要修改的是AF-ON 的按鈕



進去之後,我們會選擇第一個「測光與自動對焦啟動」,再按INFO 做詳細設定。
進去之後,選擇右邊的「註冊的自動對焦點」,按SET 結束。
這個目的是什麼 ? 也就是按下AF-ON 的時候,本來是FOCUS 在之前選擇的對焦點,我們改成「註冊的自動對焦點」(HP) 之後,他會用我們註冊的模式來對焦。 邏輯是

  1. 因為註冊的自動對焦點是61點 ( 等一下會做 )
  2. AF-ON 的對焦設定成註冊的自動對焦點
  3. 所以AF-ON 會變成61點自動對焦


第二個步驟 : 將61點自動對焦變成註冊的自動對焦點

我們從手冊來看好了。請翻到330頁。



這邊剛剛我們已經設定過了喔,請再確定一遍是不是選到 「註冊的自動對焦點」了。沒選好的話AF-ON 不會從切換成61點。



先回到相機的拍攝模式~

1. 按下圖紅鈕的「自動對焦點」,再透過右手食指轉盤或M-FN 切成 61點自動對焦,這是第一句的意思。因為我們要的是61點,所以2的部份你就選成61點如下下圖。

圖1

61點自動選擇對焦長這樣。

圖2

先隨便拍拍看,看是不是相機自動選擇對焦點了。

3.

現在我們就要「註冊」了。
註冊的意思是說,把「當下」的對焦點模式,變成一組選擇。

 * 當然 ! 為了避免你是用背的,而不了解其義,思考一下,你怎麼倒過來,把AF-ON變成單點,而一般快門模式是61點。也是可以的! 但現下不討論這個以免離題。

如何註冊,請看圖示,依順序按。聽到嗶一聲就成功了。我們已經把61點變成註冊的自動對焦點了。




最後確認與操作流程

現在我們都設定好了。要來驗證一下是否如我們所願。我們現在的機器是這樣子 :

1 .平常我是用手冊 73 頁的對焦區域。必要的時候用BAR 移動位置。

2.  突然出現可愛小狗的處理方式


  1. 右手無名指觸發景深鍵變成AI SERVO 模式
  2. 右手姆指按下AF-ON變成 「61點自動選擇自動對焦」,並且會自動對焦+該焦點測光。
  3. 右手食指按下快門鍵久一點 : 連續釋放快門 (其它指頭不要離開喔! 這樣下一張才會保持 AI SERVO + 61 點)
3. 補充
翻到手冊 113頁,建議拍攝方式改成 「靜音連續拍攝」。這樣抓拍的時候,才會連續拍照,並且「靜音連續拍攝」,對反光鏡的壽命比較好。









2013年3月30日 星期六

如何快速在aws 上架網站

這個寫起來可能落落長,不過順序大致上是幾項:

1 申請帳號,填寫信用卡
2 啟動一個ec2 的服務,選預設免錢的那個
3 security group 要設定好http 也要allow ,不然無法存取
4 在裡面安裝apahce server

sudo yum install httpd

5 設定eip 的mapping (可設可不設)
6 放一個歡迎頁html 在  /var/www/html

細節慢慢再寫


安裝tomcat

sudo yum install tomcat6
這樣就裝好了。

記得aws console 中 security group 中要選custom tcp ,然後open 8080 port.
可以在webapps建立一個專案,放一個index.html

如何啟動關閉tomcat
sudo service tomcat6 start
sudo service tomcat6 stop

*建立windows server+sql server的時候,記得在 mgmt studio 中改回mixed mode,否則一般的使用者無法登入。

2013年3月24日 星期日

當Spring MVC+Hibernate搭配的時候出現 No Hibernate Session bound to thread


SpringMVC + Hibernate Error: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.


這個惱人的問題在於Spring MVC 的設定方式。
首先你的DispatchServlet 對應的xml先找出來
你應該有設定
<context:component-scan base-package="tw" />
這一類的東西。
如果tw是你的根目錄,Spring 就會開始掃瞄所有有annotation的bean並加入管理。
你應該有另一個xml 是管理Spring 交易的,它那邊也會有一個
<context:component-scan base-package="tw" />
因為第一個scan已經把所有的bean load進來了,在交易管理的那個scan變成完全沒作用了。

這樣會導致Spring 的交易沒有被注入,也就是你會因此看到這個錯誤訊息。

解決方法就是把二者的類別錯開,盡量讓spring mvc的scan只掃controller的目錄,不要去掃到Service 層的類別。

讓交易的那個xml去掃service那一層,這樣交易才會被注進去。

2013年3月16日 星期六

Spring 最輕鬆的完整配置方式

本文目的是要去除所有無謂的宣告,並且將多datasource與交易的設定全都建立起來,aop 的功能也要建立起來。

一般工程師抗拒使用Spring的原因通常只是為了設定很麻煩。如果能直接new 為何要設定半天 ? 不過如果連new 都不用,也不用寫什麼設定檔,不是更好嗎 ?

我看過一些實際專案的Spring的XML,有些團隊好像覺得用了Spring就是了不起的事,但是當專案越來越龐大的時候,過多的xml 絕對是痛苦的來源。真正好的設計是在專案中驗證出來的,一開始的易用,並不代表當需求開始增加時,仍能保持易用性。這個時候就可以看的出來設計上有沒有問題。

以下的配置以Spring 3.2為基礎。

學一個東西先看怎麼用,好用的話再參考。

Action



@Controller
@Scope("prototype")

public class EmeaAction {
@Autowired
EmeaService emeaService;

public String index() {

System.out.println(emeaService.list());
return "success";
}  
}

對一般人來說不清楚的就是紅色的Annotation的部份。先不說細節。

Service


@Service
public class EmeaService {
@Autowired
private QuotationDAO quotationDAO ;

@Transactional(value="quo", readOnly=true)
public List<QuoQuotationMain>  list() {
return quotationDAO.listAll();
}
}


到目前為止應該都還ok吧,Service layer的部份又看到一次 @Autowired,  這很明白的表示,請spring 自動幫我注入這個dao。其它細節也不先說。

DAO


@Repository
public class QuotationDAO { 

@Autowired

private SessionFactory sessionFactory2;

public List<QuoQuotationMain> listAll() {
return sessionFactory2.getCurrentSession().createQuery("from    QuoQuotationMain").list();
}
}


好,程式的部份就這樣子了。應該還算好用吧,如果覺得很難用的話,那就回去原來的世界吧…只是原來的寫法不可能更少,如果原來的Java寫法程式可以這麼少的話,還有誰想要學Spring。先來解釋一下這三層最上面會出現的註示。

@Controller=@Component
@Service =@Component
@Repository=@Component

這算什麼解釋啊 ? 因為這些注釋都是語意而已,其實都是代表 @Component,那@Component是做什麼的,這樣宣告可以讓Spring知道說「喔,這個就是Bean,我會自動幫你們註冊,然後id就和類別一樣吧,只是前一個字要小寫」等於Spring 自動幫你產生xml了,於是乎我們就等同有了三個已註冊的bean 在xml裡了。

@Controller 代表他是MVC 中的C,控制流程為主,下面的scope=prototype就是說每次呼叫都會產生一個新的。不信的話你在方法中去印this,得到的物件其實每次都會不同。

@Service 是中間層的處理方式,也包含了交易的部份,與dao溝通的部份。
@Repository 就是指DAO

對程式設計人員來說,你所要知道的就只要這樣子就好了。就算是Grails來說,寫法也不會再更簡單了,頂多它不用宣告Annotation,以命名方式為主,但是有一點註解其實還是比較好,一看到就知道這個類別負責的功能是哪一塊。

好了,再來你應該會想像有很多的xml要設定。但其實不會,你看上面的程式之後,一定要設定的就是datasource與session factory,這個不可能主動幫你建吧,所以這個要怎麼設定?



<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/deu"></property>
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/sap"></property>
</bean>


你的jndi是什麼就自己改吧。


<bean id="sessionFactory1" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource1" />
<property name="hibernateProperties" value="classpath:hibernate.cfg.xml" />  
<property name="packagesToScan" value="tw.entity.deu" />
</bean>
<bean id="sessionFactory2" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource2" />
<property name="hibernateProperties" value="classpath:hibernate.cfg.xml" />
<property name="packagesToScan" value="tw.entity.emea" />
</bean>


目前不到20 行,而且這二個東西和db 有關,通常很少再動了。就算dba 逼你連到另一個schema 來存取資料,只要加一個sessionFactory和一個dataSource即可。然後在service layer中,value記得用你取的新名字即可。整個衝擊修改過程不用1分鐘。

packagesToScan 這個好像有一點陌生? 他可是專案膨風後的救星,因為再也不用「多一個表格 就要再去xml宣告一次」了。我一直覺得多一個資料庫表格就要在Spring設定一次真的是很無聊的事,現在你可以叫session factory 去掃那個資料夾就好。專案中多一個表格的衝擊就是 : 打開編輯器,自動從table建出annotation 式的entity後結束。當你一次要弄20幾個table出來一個一個宣告這些無意義的程式時,就知道這個屬性真棒!

好了,那xml 裡還有什麼呢?

還有交易的部份。這個訂xml也不過份啦,而且日後就很少需要改。我把會動到的用粗體標示。


<bean id="transactionManager1" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory1"></property>
<qualifier value='deu'></qualifier>
</bean>
<bean id="transactionManager2" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory2"></property>
<qualifier value='quo'></qualifier>
</bean>


交易管理員會以aop 的方式插到service layer去,一個交易管理員對應一個session factory(剛設定過了),一個session factory對應一個datasource(也設定好了),所以交易的部份現在都ready了,專案膨風後也不太需要再做設定。

你說他用aop 的部份,剛剛怎麼看不出來?


Service


@Service
public class EmeaService {
@Autowired
private QuotationDAO quotationDAO ;

@Transactional(value="quo", readOnly=true)
public List<QuoQuotationMain>  list() {
return quotationDAO.listAll();
}
}


@Transactional(value="quo", readOnly=true)這裡就是叫Spring 用aop 的方式在前、後自動補上交易管理,並且使用的是quo 這個交易管理員(transactionManager2啦,上面有)。

這邊全部已經設定完了。在xml 的最上面加上一些tag告訴Spring你要用掃瞄的方式注入。這三行一定要寫。



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

<aop:aspectj-autoproxy />
<tx:annotation-driven />


第一行是叫Spring用掃的方式注入。
第二行是啟動aop +annotation來注入 ( 在之前講過不再多提)
第三行是啟動annotation+aop注入交易管理員。

再整理一次。xml 只要寫這三行,以及

  • Datasource
  • Transaction manager
  • Session Factory

之後xml 幾乎就不太需要再更動了。
至於新的Action的話,目前我是用Struts2,改成Spring後再分享一下以Spring 為 MVC 要如何設定。

2013年3月9日 星期六

Spring 宣告式交易管理

聽起來很華麗,用起來也很過癮,但是一開始沒有搞的很清楚,加上又找不到詳細的解釋,在Stackoverflow上問了問題,再參照其它的文章,才搞懂他真正的用法。


本文章的關鍵字是 : Transaction propagation , declarative transaction,Spring宣告式交易管理。



首先先講測試的架構,都是用annotation來做。前面有介紹過方法了。

1。 發起方是一個action裡的method,它會call UserService一個 testAddUserButAddCountryFail的方法。

2. 在這個service裡面,注了一個dao,與另一個service。

3. 這個testAddUserButAddCountryFail方法內我們做二件事: 首先呼叫dao 來insert 一筆資料,這邊我們會讓它insert 成功。再來用另一個service來呼叫,這個service裡面我們會對交易的方式做測試,都是要讓他fail ,但是要看他對testAddUserButAddCountryFail 產生什麼影響。

看程式比較容易:

UserService裡


     @Resource
   private UsersDAO userDao;
     @Resource
    CountryService countryService;


     @Transactional(value="deu"   )
    public void testAddUserButAddCountryFail(){
      userDao.addUser();
      countryService.addCountry();   
  }

CountryService 裡


     @Resource
   private CountryDAO countryDao;

@Transactional(value="deu"  )
public void addCountry(){  
countryDao.addCountryFail();
}

測試 一 : CountryService中的@Transactional 不做什麼設定,預設就會是串在一起的交易。所以當 countryService.addCountry(); 裡面報錯時,exception丟出來 後,整個交易都會失敗,所以user不會新增成功。

如果我們把  countryService.addCountry(); 包try catch不讓它擴散呢?  不行噢,當程式呼叫 countryService.addCountry(); 時,addCountry已被視為是同一個交易,當countryService.addCountry(); 裡出問題自動rollback 時,已經馬上影響到addUser了,也就是原來add的user在一出問題時已經被rollback了。當程式回到 testAddUserButAddCountryFail 你想再處理時已經來不及了。

測試二 : 將


@Transactional(value="deu"  )
public void addCountry(){  
countryDao.addCountryFail();
}



改為


@Transactional(value="deu", propagation=Propagation.REQUIRES_NEW  )
public void addCountry(){  
countryDao.addCountryFail();
}


我們想要知道,根據Spring的文件來說,這邊會變成一個新的交易,我們要讓它出錯,看影響的狀況如何。我們預期它是一個新的交易,所以它出錯不會影響到新增的User.

結果呢…新增的user還是被rollback了。這一點讓我百思不解。後來研究了網站上的說明,才發現 addCountry雖然是一個新的交易,它自己也rollback了,但是它的exception還是會丟回外部的交易(也就是原來addUser開的那一個),因為我們在

@Transactional(value="deu"  )

public void testAddUserButAddCountryFail(){
   userDao.addUser();
   countryService.addCountry();    
 }

中沒有做任何處理,這個exception間接導致了 testAddUserButAddCountryFail 也rollback了。

解決方式 : 在    countryService.addCountry(); 加try catch,白話來說就是 : 它是一個獨立的交易,要是裡面出錯了,它的exception不對我( testAddUserButAddCountryFail ) 產生任何影響。

這邊懂了之後,順便講一下Spring的術語:

當程式一進去 testAddUserButAddCountryFail 時,因為我們有@Transactional ,所以這邊是一個新的交易,叫outer transaction,也就是外面的交易。當我們  countryService.addCountry(); 的時候,因為  addCountry() 裡也是一個交易,這邊叫 inner transaction (內部) 。整個測試案例就是在測試,inner一但有exception時如何對 outer 產生影響。

propagation=Propagation.REQUIRES_NEW會把inner transaction變成一個獨立交易,不寫的話會變成inner的一部份。雖然透過 propagation=Propagation.REQUIRES_NEW變成了一個獨立交易,但是outer還是要對它丟出的例外做處理,都不處理的話,這個例外會影響到原有的交易行為,變成跟著也rollback了。

我這邊順便再貼一下JB Nizet 的回應,他講的夠清楚了。

You got it right. 

If the second service uses REQUIRED, both services are in the same transaction, and the transaction will be marked for rollback if an exception is thrown by the second (or first) service. Catching the exception in the first service won't change anything: the transaction is marked for rollback already. 

If the second service uses REQUIRES_NEW and throws an exception, it has its own transaction, which will be rollbacked. But the exception, as any other exception, will propagate to the caller (the first service). So if it doesn't catch it, its transaction will also be rollbacked. – JB Nizet









2013年1月20日 星期日

spring aop + transaction 控制

整個aop的目的在於 : 如果你會一直寫一樣的東西,那就抽出來,用動態的方式安插在需要的地方即可。

交易是在aop 中一定會被提及的部份,除非你的程式都是單純的報表查詢,否則交易需求是一定會被提到。

在這裡透過aop + annotation 可以超簡單的達成。

spring的xml中要有這幾樣東西。


  1. <context:annotation-config />
  2. <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  3.         <property name="sessionFactory" ref="sessionFactory"></property>
  4.  </bean>
  5.  <tx:annotation-driven transaction-manager="transactionManager" />

最後一行是說,我要用過annotation的方式來設定transaction。如果不加這一行是無法達成用anno 的方式來設定。

(sessionFactory我假設你已經設定好了…)

再來,只要在你想要做交易的地方宣告@transactional即可。


  1.      @Transactional
  2.      public class UserService {
  3.        @Reso urce
  4.         private UsersDAO userDao;
  5.        
  6.        @Transactional
  7.         public List<Users> list() {
  8.             userDao.addUserErrorTest();
  9.             return userDao.listAll();
  10.         }
  11.         public void testInsertError(){
  12.            
  13.         }
  14. }


我userDao裡的這個方法,會故意新增一個成功,另一個失敗,當這樣宣告的時候,此方法裡呼叫的程式將會被視為一次交易,要就是全部成功,否則就是全部失敗。

aop + transaction的顆粒度極限是方法。所以在dao某個方法中,要重新思考,若不需要交易的程式區塊,要再重構出新的方法,畢竟交易是需要佔時間與資源的。

以下補充一下sessionFactory 的設定方式,我的範例是以 jndi 的方式來做。


  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  2.         <property name="dataSource" ref="dataSource" />
  3.         <property name="hibernateProperties">
  4.             <value>
  5.             hibernate.dialect=org.hibernate.dialect.SQLServerDialect
  6.               hibernate.show_sql=true
  7.             </value>
  8.         </property>
  9.         <property name="annotatedClasses">
  10.             <list>
  11.                 <value>entity.Users</value>
  12.             </list>
  13.         </property>
  14.     </bean>
  15.     <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  16.         <property name="jndiName" value="java:comp/env/jdbc/deu"></property>
  17.     </bean>

spring 3 aop 簡單實現 - 透過Annotation.

前言 : 若你是搭配 struts2的話,請取消繼承 ActionSupport。否則將會遇到java.lang.NoSuchMethodException: $Proxy..這一類的問題。


AOP 是幹麼的 ? 我一直覺得這個名詞翻譯的很不吸引人,導致很多人一聽到就退縮了。那至少你是聽過「注入」吧,就是 Spring標榜的核心功能。AOP 只要翻成「插入」就行了。雖然聽來有一點色情,不過先這樣翻譯比較吸引人。

物理上的插入還要定義方向,在AOP的術語來說,他是Cross-cutting,也就是橫的插進去。想像程式碼是一個磚頭,現在我在上面放一片木頭( 做log ),在下面也放一片木頭 (log ),我這個木頭是共用的,所以不用因為每一個磚頭就製造一片新的木頭。

我們再想一個案例 : 客戶反應執行某段程式的時候異常的慢,他們和你攤牌了 : 超過7秒的真的無法接受,請全面檢查。

主案眉頭一皺,請你把所有使用者「按下去」到得到結果的所有功能,中間執行時間做一個EXCEL出來,星期一開會檢討。

若沒有AOP 的話,好吧,你不會想拿手錶出來計時吧? 

如果有一個東西可以像盒子一樣,把你所有的程式像樂高積木裝進去,當進去要執行的時候紀錄一次時間,出來的時候紀錄一次時間,只要超過7秒就記在什麼LOG檔吧,那該有多好?

AOP 可以解決類似這樣的問題。畢竟你不想把所有的程式都在開始與結束寫一次,當然你可以透過攔截器或是什麼過濾器達到,不過AOP 的動配裝配蠻有趣的,不彷也可以了解一下。

首先我們要先寫一個紀錄時間的類別,這玩意叫Aspect,切入面,他是實際會插到程式裡的東西,所以你裡面自然會想到 : 前插、後插、前後插(環繞)、異常插等等,為了不要搞的太複雜就只說前插入與後插入。

先寫一個class

  1. package aop;
  2. import org.apache.log4j.Logger;
  3. import org.aspectj.lang.JoinPoint;
  4. import org.aspectj.lang.annotation.After;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. import org.aspectj.lang.annotation.Pointcut;
  8. @Aspect
  9. public class LogExecuteTime {
  10.    
  11.    
  12.     @Pointcut("execution(* action.IndexAction.*(..))")
  13.     private void anyMethod(){}
  14.    
  15.     @Before("anyMethod()")
  16.     public void beforeAdvice(JoinPoint jp){
  17.         String noti = jp.getTarget().getClass().getName()+" "+jp.getSignature().getName();
  18.         System.out.println("before" + noti+" has been started.");
  19.    
  20.     }
  21.    
  22.     @After("anyMethod()")
  23.     public void afterAdvice(JoinPoint jp){
  24.         String noti = jp.getTarget().getClass().getName()+" "+jp.getSignature().getName();
  25.        
  26.         System.out.println("after" + noti+" has been executed.");
  27.     }
  28. }

這邊伴隨著annotation做直觀的設定。比較特別的是你要先定義哪些程式需要插入。

  1. @Pointcut("execution(* action.IndexAction.*(..))")
  2.     private void anyMethod(){}

這裡代表IndexAction這個類別下的所有方法都要被套用。
接下來你想要前插,就寫@before的方法,後插就寫@after的方法。
我們在軟體工程常常講去耦合,aop 就是一個超好的例子。怎麼說?
因為要被插入的類別中,完全不知道這件事的存在! 
也就是悄悄的來,悄悄的去,有一點像百貨公司的人數計算器一樣,進場的民眾不需要做什麼,但是臨時在入口裝一個計數器,就知道進場有多少人了。如果客戶、主管滿意後,這個aop甚至可以告一段落,主程式完全不受影響。

ok,但是在xml 中還是要把這個程式交由Spring管理,所以請先加

<aop:aspectj-autoproxy/> ( 這代表要使用aspectj)

再把計時器加入管理即可。
<bean id="logAspect" class="aop.LogExecuteTime" />


所有的耦合只存在於計時器的類別
  @Pointcut("execution(* action.IndexAction.*(..))")

所以當你要增加、減少控制項目時,非常的簡單。
我建議採用annotation甚於xml,看程式的時候即可一目了然,不需要再看xml來對照。

2013年1月19日 星期六

struts 2 +spring (含aop )+hibernate lib 設定

請參考前一篇將前二者的lib加入後。

針對hibernate的部份 :

spring :

spring-jdbc-3.2.0.RELEASE
spring-orm-3.2.0.RELEASE
spring-tx-3.2.0.RELEASE ( 以前好像叫spring-transaction-.....jar)
--for spring aop
spring-aop-3.2.0.RELEASE
spring-instrument-3.2.0.RELEASE
spring-context-support-3.2.0.RELEASE
spring-aspects-3.2.0.RELEASE
aopalliance.jar
aspectjweaver.jar
http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.7.1

hibernate: (在他下載包的其它目錄找找)
antlr-2.7.6.jar
jta-1.1
slf4j-api-1.6.1
hibernate-jpa-2.0-api-1.0.1.Final
hibernate3
dom4j-1.6.1

spring3.2 + struts2 的 lib 配置

spring 3.2 的配置上有一個差別。
也就是asm 這個獨立的jar檔要把它移除,因為asm已經被放在
spring-core-3.2.0.RELEASE.jar 中了。

要配置spring 3.2 + struts 2.3的話,最少需要這幾個jar 檔才能跑起來。

commons: (下載大包的struts壓縮檔中都有)
commons-beanutils-1.8.0
commons-collections-3.1
commons-fileupload-1.2.2
commons-io-2.0.1
commons-lang-2.4
commons-lang3-3.1
commons-logging-1.1.1

for spring: (當然是從spring的大包來)
spring-beans-3.2.0.RELEASE
spring-context-3.2.0.RELEASE
spring-core-3.2.0.RELEASE
spring-expression-3.2.0.RELEASE
spring-web-3.2.0.RELEASE

for struts2: ( from struts的大包)
struts2-core-2.3.8
struts2-spring-plugin-2.3.8
xwork-core-2.3.8

others: ( from struts的大包)
freemarker-2.3.19
javassist-3.11.0.GA
ognl-3.0.6

2012年10月13日 星期六

如何在Grails 上實現多對多的關係(四) : 怎麼實作

寫了這麼多廢話,該說如何實作了。我們先從使用者開始。


class LoginUsers{
   static hasMany = [roleLink : RoleUsers]
   String userid
   static constraints = {
      userid(unique:true)  
   }

 List roles(){
    return roleLink.collect{it.role}
 }
 List addRole(Roles r){
    RoleUsers.link( this , r )
    return roles()
 }
 List addRoles(List roleids){
    for(rid in roleids){
       RoleUsers.link( this , Roles.get(rid))
    }
    return roles()
 }
 List removeRole(Roles r){
    RoleUsers.unlink(this,r)
    return roles()
 }
 void removeAllRoles(){
    RoleUsers.unlinkAllRolesFromUser(this)
 }
}
稍微解釋一下。還記得測試的程式嗎, aska.roles() 就可以得到角色,背後怎麼做呢?使用者的類別中我們這樣定義過…

   static hasMany = [roleLink : RoleUsers]

不用宣告一個 Set roleLink 在上面,GORM 自動幫我們加入了。所以呼叫 aska.roleLink() 就會馬上得到屬於aska的List<RoleUsers>

但我說過 RoleUsers 不算是一個真正的「物件」,我們並不關心它( 雖然它要做很多事),我們要的是群組。所以這一部份,我們要再加上collect後,封裝在 roles() 的方法裡丟出Roles。 

如果你還不知道collect的話,請看前面的介紹,這裡很簡潔的把 RoleUsers裡屬於 aska 的群組蒐集起來丟出去。

class LoginUsers {
    static hasMany = [roleLink : RoleUsers]
}
class RoleUsers {
    LoginUsers user
    Roles role
    static constraints = {  
    }
    static belongsTo = [user:LoginUsers , role:Roles]
}

這樣夠清楚吧。有這樣的宣告後,aska只要呼叫 roleLink.collect{it.role} 一行就完事了,非常給力。

可想而知 Roles的部份也是 userLink.collect{it.user} 來得到一個群組下的使用者們。這邊我就不再解釋一次了。

aska.addRole ,因為我們要新增一個 RoleUsers的物件,所以責任上我們就給RoleUsers去做。先定義方法叫做link。加多數的群組,就是呼叫link 多次。removeRole就先定義叫 unlink。 Roles的類別,可想而知也定義了類似的方法,不再解釋

class Roles {
        String name
        static constraints = {
             name(unique:true)
        }
        static hasMany = [ userLink:RoleUsers  ]
 List users(){
    return userLink.collect{it.user}
 }
 
 List addUser(LoginUsers u){
    RoleUsers.link( u , this)
    return users()  
 }
 List addUsers(List uids){
    for(uid in uids){
       RoleUsers.link(LoginUsers.get(uid) , this)
    }
    return users() 
 } 
 List removeUser(LoginUsers u){
    RoleUsers.unlink( u , this)
    return users()
 }
 void removeAllUsers(){
    RoleUsers.unlinkAllUsersFromRole(this)
 }
}

重頭戲在 RoleUsers 裡。

class RoleUsers {
 LoginUsers user
 Roles role
 static constraints = {
  
 }
 static belongsTo = [user:LoginUsers , role:Roles]
 
 static RoleUsers link(LoginUsers u , Roles r){
   def lk = RoleUsers.findByUserAndRole(u,r)
   if(!lk){
   //if not in db then we start to link
     lk = new RoleUsers()
     u.addToRoleLink(lk)
     r.addToUserLink(lk)
     lk.save(flush:true)   
   }
   return lk
 }
 static void  unlink(LoginUsers u , Roles r ){
   def lk = RoleUsers.findByUserAndRole(u,r)
   if(lk){
     u.removeFromRoleLink(lk)
     r.removeFromUserLink(lk)
     lk.delete(flush:true)
  }
 }
 static void unlinkAllRolesFromUser(LoginUsers u){
  def lks = RoleUsers.findAllByUser(u);
  if(lks){
   for(lk in lks){
      lk.user.removeFromRoleLink(lk)
      lk.role.removeFromUserLink(lk)
      lk.delete(flush:true)
   }
  }
 }
 static void unlinkAllUsersFromRole(Roles r){
  def lks = RoleUsers.findAllByRole(r);
  if(lks){
    for(lk in lks){
      lk.role.removeFromUserLink(lk)
      lk.user.removeFromRoleLink(lk)
      lk.delete(flush:true)
   }
  }    
 }
}

我假設你已經會基本的GORM了,所以只說重點。

在做鏈結與非鏈結時,記得先「解除關係」。

link 這個方法中透過


  • LoginUsers 的 hasMany 裡定義的變數名稱為roleLink,
  • 並且RoleUsers屬於 LoginUsers
  • 於是Grails就會在LoginUsers中,自動幫你合成方法 addToRoleLink



addToRoleLink 如此就變成是內建的方法了,不用再實作了。

記得要從「一」方去加「多」方,因為有主權的一方擁有責任。
aska.addToRoleLink(link),把自己加到鏈結當中。Roles也要做一樣的事。最後再save 那個 link (也就是RoleUsers) 則大功告成。

相反的,非鏈結要做的步驟也是一樣。把二邊的關聯去除後,再把 link 刪除。若你忘了先把關聯解除,就會得到exception。

再來是一個使用者想要把所有的群組刪掉。首先得到屬於他的 List<RoleUsers>後,一樣是要先解除關聯。要從「一」方來呼叫removeFrom,所以還得要先連回「一」方再分別解除,這也就是 lk.role.remove... 與 lk.user.remove....的意義。

程式是否少到令你難以置信?這中間沒有任何的 xml 任何的 annotation。類別的關聯性也不用再做文件說明,非常的明白易懂。定義好 RoleUsers裡的方法實作後,再來就沒有什麼事可以做了。呃,對,你也不用再管DB裡的表格了,Grails會自動幫你處理。

快速的做一些ui 的假想

有一個使用者的列表頁,點進去某個使用者之後,會出現基本資料維護,下方是他可以使用的群組。

在controller 裡我們快速的呼叫這二個方法塞成params丟到gsp中馬上就可以使用。


[role_list : Roles.list() , checked_role_list : aska.roles() ]

在維護頁裡,按下確認後回到controller,我們會先刪除aska的所有群組,然後再加有勾選的部份。

aska.removeAllRoles()
aska.addRoles(params.checkedRoleId)
二行搞定… 若前端頁面變成奇怪的新式UI,使用者點一下某個群組就會動態的Ajax往後端呼叫做更新,再按一下就刪除這個群組,那也是 aska.addRole(Roles.get(rid)) 與 aska.removeRole(Roles.get(rid)) 而已… 花了蠻多時間寫的,希望對大家有幫助。

如何在Grails 上實現多對多的關係(三) : 三者的結構

首先你一定會先定義類別吧,我們一次要定義三個類別出來。

class LoginUsers {

}
class Roles{

}
class RoleUsers{
      //這個就是多對多的中間表,我們需要它來做很多事。
}
再來我們開始定義它們的關聯,也是很直觀。


// LoingUsers 1 <-> * RoleUsers * <-> 1 Roles

class LoginUsers {
    static hasMany = [ roleLink : RoleUsers ]
}
class Roles{
    static hasMany = [ userLink : RoleUsers ]
}
class RoleUsers{
    LoginUsers user
    Roles role
    static constraints = {
  
    }
    static belongsTo = [user:LoginUsers , role:Roles]
}
LoingUsers 1 <-> * RoleUsers * <-> 1 Roles 別說你看不懂喔,正規化是幼幼班的課程。
這邊比較特別的是…明明LoginUsers與Roles參照同一個表格,為何我取不同的名字,一個是roleLink一個是userLink ? 這是你使用上的自由…

從使用者的觀點來看,這是一個群組鏈結表。
從群組的觀點來看,這是一個使用者鏈結表。

所以我訂不同的名字。為了方便,你也可以都叫做 link:RoleUsers,這邊就隨你高興囉! 不過 Roles 不一定只和Users 做多對多,他也可以與功能做多對多,所以取名時還是多加幾個字比較好。

中間表裡的每一個record,每一個物件,都一定對到1個使用者與1個群組。

從使用者的觀點來說,他是一對多的,就像一個人可以寫很多本書,多方的書「屬於」作者。 所以這裡的多方 RoleUsers 是「屬於」使用者的。

從群組的觀點來說,也是一對多的,一個群組裡可以有多個使用者。

所以在這個中間鏈結表中,他同時「屬於」二者,中間表沒有什麼主權,所以他「屬於」別人。

用這樣的方式去理解應該就簡單了。

剛剛說,中間表裡的每一列都對應到一個使用者與一個群組,所以你當然要宣告他們才連的回去。 結構的部份搞清楚了,後面就不是很難理解了。內容請看下一章。

如何在 Grails 上透過 GORM 實現多對多的關係。(二) 我就是要這樣呼叫

二個類別,LoginUsers 和 Roles,還記得吧。

        LoginUsers aska = new LoginUsers(userid:'aska')
     LoginUsers gina = new LoginUsers(userid:'gina')
     Roles admin_role = new Roles(name:'admin')
     Roles normal_role = new Roles(name:'normal')
     aska.save(flush:true)
     gina.save(flush:true)
     admin_role.save(flush:true)
     normal_role.save(flush:true)
aska 是admin , gina 是一般使用者。簡單來說這個測試就是如此。 我們要做的測試是,從aska,gina的角度加入群組,以及從群組的角度加入使用者。 當然也要從aska與gina的角度來退出群組,以及從群組的角度來刪除使用者。 GORM 是「絕對直觀」派的東西。你先看我下面怎麼寫,就知道「絕對直觀」是指什麼。 我們先來測試從群組的觀點來加入使用者,並且倒過來從使用者的角度來得到群組。

        println 'add aska to admin_role' + admin_role.addUser(aska)
     println 'add gina to normal role '+ normal_role.addUser(gina)
     println 'add aska to normal role' + normal_role.addUser(aska)
     println 'aska should have two roles = '+aska.roles()
     println 'gina should have one role = ' + gina.roles()
     println 'admin user should be aska= ' + admin_role.users()
     println 'normal users should be aska and gina= '+normal_role.users()
我們實作的程式,就是這樣呼叫。也就是說,這樣寫一定是你最滿意的。得到一個群組的物件時,呼叫 addUser 就可以加入user,還有比這樣寫更簡單的方式嗎? 先說一下,這邊的程式一定都不會過,因為這些方法都還不存在。你可能有想到說,多對多不是有「中間」的表格嗎,怎麼都沒看到? 我們在實作的時候,會透過一個中間類別來做處理,這個中間類別對我來說,不應該出現在controller或是service 之中,它不是我們關心的物件。雖然等下他要做很多事! 再來我們測remove

        //test remove 
     normal_role.removeUser(gina)
     println 'remove gina from normal users, now normal role should be only aska = '+normal_role.users()
     normal_role.removeAllUsers()
     println 'remove all users from normal role, now normal role = '+normal_role.users()
     println 'aska should have no groups after unlink all roles' + aska.removeAllRoles()
好,當我們在刪除的時候,要是你是從使用者的角度出發,你只想呼叫removeRole(role) , removeAllRoles()這樣吧。我所謂的「絕對直觀」,就是程式員的本能,如果可以這樣寫就下班有多好,從這個角度來設計就對了。從群組的角度來說,就是 removeUser(user) , removeAllUsers()這樣,反之亦然,找不到更短的寫法了。 在得到使用者的群組亦是 aska.roles() , 得到管理員群組下的人也該是admins.users(),這就是直觀導向的寫法。 下面再從使用者的角度,加入一堆群組。這個在維護畫面中常常見到啊,管理者勾一勾這個人能加入的群組,將群組id送到controller等待寫入db,所以我們也是需要這樣的功能。

        aska.addRoles([1,2])
     println 'add aska to admin and normal group, now aska join : ' +aska.roles()
     println 'add gina to normal role' + gina.addRole(normal_role)
     println 'remove gian from both roles'+gina.removeAllRoles()
     println 'gina should have no roles now'+gina.roles()
     println 'add gina to admin and normal roles '+ gina.addRoles([1,2])
     println 'clear all user from role'
     normal_role.removeAllUsers();
     admin_role.removeAllUsers();
     println 'try to add people to roles.'
     println 'add aska and gina to normal role.'+normal_role.addUsers([1,2])
     println 'add aska to admin role.'+admin_role.addUsers([1])
     println 'test remove user from roles'
     aska.removeAllRoles()
     gina.removeAllRoles()
     println aska.roles()
     println gina.roles()

我不用解釋你也看的懂,這就是我們要的程式,再來下一章要講背後我們要如何實作,以得到這直觀寫法。