利用在客戶端測試的空檔,測試Spring出品的Java Mail。
| import javax.mail.internet.MimeMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; public class SpringMailer { public static void main(String[] args) throws Exception { JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); javaMailSender.setHost("127.0.0.1"); MimeMessage msg = javaMailSender.createMimeMessage(); MimeMessageHelper msgHelper = new MimeMessageHelper(msg); msgHelper.setFrom("jemmy@from"); msgHelper.setTo("spring@to"); msgHelper.setSubject("Test Spring Mail!"); msgHelper.setText("Spring"); javaMailSender.send(msg); } } |
JavaMailSender是一個Interface,可以被Spring配置。這裡直接引用JavaMailSenderImpl,可以設置SMTP Host甚至它的login id/pwd。而setText還第二個參數,若為true則為html格式。Jemmy 發表在 痞客邦 留言(0) 人氣(199)
立冬後思春一下^^。應客戶要求,希望jdbc.properties不要被包進jar裡,可外顯手動修改再重啟standalone程式。在Spring3的applicationContext.xml改一下就好,原為 <util:properties id="jdbcProperties" location="classpath:jdbc.properties"/> 改為 <util:properties id="jdbcProperties" location="file:./jdbc.properties"/> 這樣改沒問題,但若要由crontab啟動這standalone程式就會找不到,把file:路徑寫死成絕對路徑又失去移植的彈性,所以麻煩被crontab執行的run.sh,在執行java -jar之前加一行change directory,如下: cd /home/yourAp/
Jemmy 發表在 痞客邦 留言(0) 人氣(70)
在尋求jQuery不能在FireFox 3.x運行的解法時,意外發現@Bean的新用法,如下:
| @Bean(name={"/account", "/queryAccount", "/insertAccount", "/updateAccount", "/deleteAccount"}) public AccountAction accountQuery() { return new AccountAction(); } |
也就是一個bean的instance可以宣告多個bean name,很像某人把妹,愛用多個hotmail、facebook帳號,其實都是同一個宅色夫^^。比在spring重複定義bean name清爽多了。
Jemmy 發表在 痞客邦 留言(0) 人氣(33)
目前研究進度是可以回傳JSON格式在DataTables.js正常顯示,但中文卻成了亂碼。UTF8已是通用的頁面編碼,過去得自撰Filter對response進行編碼,現在Spring提供了Filter,解決了亂碼問題,在web.xml設定如下:
| <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
Jemmy 發表在 痞客邦 留言(0) 人氣(43)
在前一篇
Spring3對JSR-330的支援,可以使用@Qualifier指定bean name,以區分同一Interface的不同implementation。而Spring還提供getBeansOfType這個method取所有實作同一Interface的所有的bean:
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class, value = "com.foo.dao.DaoConfig") public class TestGetBeansOfType { private static Logger logger = LoggerFactory.getLogger(TestGetBeansOfType.class); @Autowired private ApplicationContext ac; @Test public void testDao() { Map<String, IBaseDao> map = ac.getBeansOfType(IBaseDao.class); for (String key : map.keySet()) { logger.debug(key); } } } |
如何直接操作SpringJUnit4ClassRunner的ApplicationContext,今天方知可以使用@Autowired。藉由getBenasOfType,以下片段@Configuration都成為Return map的入幕之賓:
| @Configuration @Import(RootConfig.class) public class DaoConfig { // package com.foo.dao private static Logger logger = LoggerFactory.getLogger(DaoConfig.class); @Bean(name="accountDao") public IBaseDao<Accounts> AccountDao() { logger.debug("AccountDao is called!!"); return new AccountDaoImpl(); } @Bean(name="functionDao") public IBaseDao<Functions> FunctionDao() { logger.debug("FunctionDao is called!!"); return new FunctionDaoImpl(); } } |
Jemmy 發表在 痞客邦 留言(0) 人氣(75)
Spring3聲明完全支援JSR-330規格:@Inject、@Qualifier、@Named、@Provider、@Scope和@Singleton,最早Google Guice 2也支援,兩者有異曲同工之妙。在Spring3把@Autowired完全可以用@Inject(javax.inject)取代,因為@Autowired在JSR-330之前就有了,而現在有一個需求是,吾人定義一個共用的Interface:IBaseDao<T>,放著insert、update、delete等共同操作,但T可能是不同物件,如Account、Employee等,在Spring 2.5.x以前的版本,是在context xml裡配置好不同bean name,但在Spring3的annotion context如何做到?答案是@Qualifier。
在Config類別設定如下:
@Bean(name="AccountDao") // 相當於為bean指定name public IBaseDao AccountDao() { // 回傳型態為IBaseDao<Accounts>更精準 ^_^ return new AccountDaoImpl(); }
|
在JUnit去測試它:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class, value = "com.foo.dao.DaoConfig") public class TestAccountDao { private static Logger logger = LoggerFactory.getLogger(TestAccountDao.class); @Autowired @Qualifier("accountDao") // 指定bean name private IBaseDao<Accounts> baseDao; @Test public void testDao() { //… 略
} }
|
而Spring3的@Qualifier也可自訂annotation,和Guice的@Named用法頗為雷同,可參考
Guice用annotation配置DI。以下列表作比較
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }
|
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) @Retention ( RetentionPolicy.RUNTIME ) @BindingAnnotation public @interface Genre { String value(); }
|
因為Spring 2.5.x早在JSR-330釋出前有部份雷同實作,到Spring3再回頭補足JSR-330規範的Annotation名稱,是故Survey上會有所confuse。
Spring3的@Autowired與JSR-330的@Inject完全相同,Spring3習慣是用@Autowired,而Guice則是有自己的@Inject。
@Qualifier和@Named意義幾乎相同,差異在於後者是String base的@Qualifier。而Spring3選擇了@Qualifier,Guice選擇了@Named作為"修飾"。包括自訂annotation策略也相同。@Qualifier搭配@Autowired似乎可以修飾不同的DataSource
@Provider在Spring3官方文件是一筆帶過,而從Guice文件說明來看:@Provider是將class的new、destroy的權力從預定的container拿回開發者上決定。比如部份bean元件是給Spring Application Context管理,可以藉由@Provider改為Guice或其它Ioc Container管理。這方面尚未研究。
還有@Scope,就是決定bean是Singleton還是prototype還是其它,在Spring和Guice都有相應用法。而@Singleton則是Guice提一個專屬的@Scope,這是Spring所沒有,相應用法是:@Scope(BeanDefinition.SCOPE_SINGLETON)
Jemmy 發表在 痞客邦 留言(0) 人氣(248)
剛學技術時,SQL學得最好,Log是最差,現在反而倒過來了,對ORM產品上手特別鈍感,這也許我很快先學會iBatis的緣故,慣用SQL操作,卻沒有物件對映到資料庫的概念。以下飯粒只是用JUnit很簡單的存取一個Accounts table裡名字欄位,因為併使Spring3的annotation context、JPA using Hibernate,試完後,真的會癱掉: 使用
AnnotationConfigContext載入Config類別進行JUnit,其測試程式如下:
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class, value = "com.foo.dao.BaseDaoConfig") public class TestBaseDaoImpl { @Autowired private IBaseDao<Accounts> baseDao; @Test public void testDao() { List<Accounts> accounts = baseDao.find("from Accounts a where a.id = ?", new Integer(1)); assertNotNull(accounts); assertEquals(accounts.size(), 1); for (Accounts account : accounts) { System.out.println(account.getName_tw()); } } } (figure 1) |
上例是很一般的DAO的使用,但在JPA寫法有幾點要特別注意,一個是Value Object,要宣告@Entity才能被JPA取得如下片段:
| @Entity @Table(name = "_Accounts") public class Accounts { @Column(name = "id") private Integer id; @Column(name = "name_tw") private String name_tw; // get/set... } (figure 2) |
再來就是DAO的寫法:第一個要注意的是@PersistenceContext的注入;第二個是JPQL是不同於一般SQL,from後面是接Class Name(上述@Entity)而不是Table Name(@Table),可以從from後開始寫,若要用Select,則是Select a from Accounts a where a.id=?,不能用Select *,要用別名不帶欄位。
| public class BaseDaoImpl<T> implements IBaseDao<T> { @PersistenceContext protected EntityManager em; @SuppressWarnings("unchecked") public List<T> find(String jpql, Object param) { return em.createQuery(jpql).setParameter(1, param).getResultList(); } } (figure 3) |
接著是怎麼用Config類別載入JPA設定。過程是
TestBaseDaoImpl(figure 1)—>AnnotationConfigContextLoader—>BaseDaoConfig(figure 4)—>RootConfig(figure 5)—>load resources.xml。有關JPA設定就藏在resources.xml了. BaseDaoConfig.java @Configuration @Import(RootConfig.class) public class BaseDaoConfig { @Bean public IBaseDao iBaseDao() { return new BaseDaoImpl(); } } (figure 4) | RootConfig.java
@Configuration @ImportResource("classpath:resources.xml") public class RootConfig { private @Value("#{jdbcProperties.driverClassName}") String driverClass; private @Value("#{jdbcProperties.url}") String jdbcUrl; private @Value("#{jdbcProperties.username}") String username; private @Value("#{jdbcProperties.password}") String password; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(this.driverClass); dataSource.setUrl(this.jdbcUrl); dataSource.setUsername(this.username); dataSource.setPassword(this.password); return dataSource; } } (figure 5) |
resources.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:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <util:properties id="jdbcProperties" location="classpath:jdbc.properties"/> <tx:annotation-driven/> <!-- Inject EntityManager into @PersistenceContext,宣告可以使用JPA的annotation --> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <!-- EntityManagerFactory Object, @PersistenceContext所注釋的變數由此bean注入 --> <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" destroy-method="destroy"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <!-- 使用Hibernate做Adapter --> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> </property> <property name="jpaProperties"> <!-- Properties的Key是Hibernate要用的,JPA只是傳遞 --> <props> <prop key="showSql">true</prop> <prop key="generateDdl">true</prop> <prop key="databasePlatform">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.query.factory_class"> <!-- 這屬性可確保@Entity和@Table mapping --> org.hibernate.hql.classic.ClassicQueryTranslatorFactory </prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="dataSource" ref="dataSource"/> <property name="entityManagerFactory" ref="emf" /> </bean> </beans> |
呼!寫完了,若純粹單一Table存取,沒有用到JPA的@ManyToMany、@ManyToOne的話,那太殺雞用牛刀了。JPA還有得學,所以這記錄花了兩天時間用Spring3串JPA成功的sample。
Jemmy 發表在 痞客邦 留言(0) 人氣(160)
EJB可以說原來執政黨Sun在platform選區大力支持的候選人,但含著金湯匙的EJB太過顢頇,給在野黨Spring有機可乘,成為Java playform的當選人。隨著支持度水漲船高,Spring3也逐漸肥起來,但還稱不上顢頇,面對Google出品的Guice謹慎以對,連Guice特有的annotation DI到Spring3也學得十足十。但Google挾著Android和GAE的優勢,端看Spring怎麼瘦身擠進Mobile和Cloud的大窄門。 廢話結束,Spring3的@Import上還真的不太好用。寫一個DBConfig的@Configuration,提供DataSource的@Bean方便給application.xml與AnnotationConfigApplicationContext繼承,如下:
| @Configuration @ImportResource("classpath:resources.xml") public class DBConfig { private static Logger logger = LoggerFactory.getLogger(DBConfig.class); private @Value("#{jdbcProperties.driverClassName}") String driverClass; private @Value("#{jdbcProperties.url}") String jdbcUrl; private @Value("#{jdbcProperties.username}") String username; private @Value("#{jdbcProperties.password}") String password; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(this.driverClass); dataSource.setUrl(this.jdbcUrl); dataSource.setUsername(this.username); dataSource.setPassword(this.password); logger.debug("init dataSource"); return dataSource; } } |
@Import不好用便在於@ImportResource只能引入XML,所以要寫一個resources.xml來放jdbc.properties的設定值。可是官方文件舉的飯粒竟然在Jetty不work,如下:
| <beans> <context:property-placeholder location="classpath:jdbc.properties"/> </beans> |
那怎麼辦,結果參考之前component scan的飯粒,將resources.xml改寫如下,就OK了:
| <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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <util:properties id="jdbcProperties" location="classpath:jdbc.properties"/> </beans> |
很煩對吧!還要注意XML schema是否引入了spring-util-3.0.xsd,才能用<util:properties>。所以有需要連DB的Component類別,只需如下作法:
| @Configuration @Import(DBConfig.class) public class AppConfig { private static Logger logger = LoggerFactory.getLogger(AppConfig.class); @Bean public IBar ibar() { logger.debug("call Bar"); return new BarImpl(); } } |
明明是用@Configuration,我卻稱為Component類別,當然也可以稱為AppConfig。但像@Configuration、@Component、@Service和@Repository等,等同於是一個Context的配置(不等於Context),@Import則是用annotation做到Spring 2.5時context可以繼承的功能。 這時有個地方要特別小心,
Bean只能被定義一處。我在BarImpl類別上加個@Repository,結果出現bean不是unique的Exception,也就是我在@Bean回傳BarImpl,等於是配置BarImpl是為一個Spring bean,而在BarImpl類別上加@Repository又宣告它一次是spring bean,導致出現non-unique的Exception
Jemmy 發表在 痞客邦 留言(0) 人氣(137)
斷續踹了兩三天,搞定使用Annotation配置Data Tier,作為Spring-Security驗證來源。一樣用jdbc.properties對定義連線資訊,和applicationContext.xml放同一處可以被classpath找到,是故在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:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <context:component-scan base-package="com.foo" use-default-filters="false"> <context:include-filter type="regex" expression="com.foo.bar.*Config"/> </context:component-scan> <util:properties id="jdbcProperties" location="classpath:jdbc.properties"/> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans> |
接著設定DataSource的Bean,在AppConfig內容如下:
public class AppConfig { private static Logger logger = LoggerFactory.getLogger(AppConfig.class); private @Value("#{jdbcProperties.driverClassName}") String driverClass; private @Value("#{jdbcProperties.url}") String jdbcUrl; private @Value("#{jdbcProperties.username}") String username; private @Value("#{jdbcProperties.password}") String password;
@Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(this.driverClass); dataSource.setUrl(this.jdbcUrl); dataSource.setUsername(this.username); dataSource.setPassword(this.password); logger.debug("init dataSource"); return dataSource; } }
|
AppConfig上頭沒放@Component,照樣被Spring找到@Bean,而@Value是讀jdbc.properties內容載入後面的String變數,jdbcProperties對映到applicationContext.xml的<util:properties>標籤的id屬性,而jdbcProperties.後面接的屬性即是jdbc.properties的key name。
接著是寫Dao去引用AppConfig的Data Source @Bean,我是繼承Spring-Security的UserDetailsService當DAO用:
@Repository public class UserDetailsServiceImpl implements UserDetailsService { private static Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class); private SimpleJdbcTemplate simpleJdbcTemplate; @Autowired public void init(DataSource datasource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(datasource); logger.debug("init: after new JdbcTemplate"); } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { String sql = "select * from accounts where username=? "; RowMapper<UserDetails> mapper = new ParameterizedRowMapper<UserDetails>() { public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException { String username = rs.getString("username"); String password = rs.getString("password"); logger.debug("id/pwd=" + username + "/" + password); UserDetailsImpl user = new UserDetailsImpl(username, password); return user; } }; UserDetails user = this.simpleJdbcTemplate.queryForObject(sql, mapper, username); if (user == null) throw new UsernameNotFoundException(username + " not found!"); return user; } } |
類別宣告為@Repository,效果和@Component一樣,除了是標明為DAO功能外,還真不知和@Component有哪裡不同。也許不同處就再下面的@Autowired後接的method,它傳入一個DataSource的參數,正是來自dataSource @Bean注入的。而使用Composited模式放JdbcTemplate是Spring3官方文件所建議的Best Practices,因為是使用SimpleJdbcTemplate,較之JdbcTempalte語法乾淨,從第三個參數起可帶無不限個數的參數。
simpleJdbcTemplate.queryForObject(SQL字串, RowMapper實作, …)Jemmy 發表在 痞客邦 留言(0) 人氣(64)
Spring applicationContext.xml的<context:component-scan>標籤用途比我想像的還要實用。而且後來才知道,有了<context:component-scan>,另一個<context:annotation-config/>標籤根本可以移除掉,因為被包含進去了。原本我survery Spring3通常只配置成<context:component-scan base-package="com.foo.bar"/>,意即在base-package下尋找有@Component和@Configuration的target Class。而現在如下的飯粒:
| <context:component-scan base-package="com.foo" use-default-filters="false"> <context:include-filter type="regex" expression="com.foo.bar.*Config"/> <context:include-filter type="regex" expression="com.foo.config.*"/> </context:component-scan> |
<context:component-scan>提供兩個子標籤:<context:include-filter>和<context:exclude-filter>各代表引入和排除的過濾。而上例把use-default-filters屬性設為false,意即在base-package所有被宣告為@Component和@Configuration等target Class不予註冊為bean,由filter子標籤代勞。 filter標籤在Spring3有五個type,如下:
| Filter Type | Examples Expression | Description |
| annotation | org.example.SomeAnnotation | 符合SomeAnnoation的target class |
| assignable | org.example.SomeClass | 指定class或interface的全名 |
| aspectj | org.example..*Service+ | AspectJ語法 |
| regex | org\.example\.Default.* | Regelar Expression |
| custom | org.example.MyTypeFilter | Spring3新增自訂Type,實作org.springframework.core.type.TypeFilter |
所以上例用的regex就有個語病,
com.foo.config.* 可以找到com.foo.config.WebLogger,但也可以找到com1fool2config3abcde,因為小數點在Regex是任意字元,是故要用\.把小數點跳脫為佳。(2010/3/15補充:但要使用\.方式,其use-default-filters不能為false,否則抓不到,感覺是Bug) Spring3提供豐富的Filter支援,有益配置策略,不需面臨Configuration Hell,比如Regex的com\.foo\.*\.action\.*Config,這樣就可以找到com.foo package下所有action子package的*Config的target class。 2010/3/18補充:後來在AppConfig前忘了加@Component,AppConfig內尚留有@Bean,奇怪的是還是能work。我猜有加@Bean的method的class,若沒特別註解AppConfig是@Repository、@Service還是@Configuration,一律被Spring3視為@Component。
Jemmy 發表在 痞客邦 留言(0) 人氣(1,873)