三大重點 : IoC/DI、AOP、整合其他框架(例如 : Hibernate、MyBatis)

分為很多個模組,有需要某功能再把.jar 加進來

IoC/DI 機制

IoC 託管

  • 被託管的物件稱為 Bean 元件
  • 將 Java 中的物件託給 Spring 管理
  • 物件的 實例化、初始化、存活範圍、銷毀,皆由 Spring 控制
  • 官方建議將物件抽介面(抽象化)

控制反轉到底在做甚麼?

簡單來說,本來是靠我們 new 兂的物件來做事情,現在這件事交由 Spring 處理,我們只要利用 Spring 提供的容器型態就可以取得 Spring 管理的物件。

Spring 提供很多 IoC 容器型態供使用,共同父型態為 BeanFactory,有一重要方法 getBean()以取得 Bean 元件。
通常在寫 web 時將 ServletContext 轉換成 Java 可用的 ApplicationContext 物件。

1
2
3
4
5
6
   //以在web環境前提下使用WebApplicationContext
//透過⼯具類別WebApplicationContextUtils取得
public static <T> T getBean(ServletContext sc, Class<T> clazz) {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(sc);
return context.getBean(clazz);
}

要先託管好,依賴注入才拿的到東西。。。
不然你就會一直看到 this.dao is null。。。

DI 依賴注入

  • 如果要使用某 Bean 元件,透過 DI 來取得
  • DI 使用父型態(即介面),減少相依性

組態設定

這裡以 Java+annotation 示範。。。
xml 太麻煩ㄌ。。。

  • 記得掃描(Scan)套件(指定 Bean 元件位置)
  • 找到後會實例化並接管 Bean
  • 若 此 Bean 元件 ⼜依賴 其他 Bean 元件,亦會同時注⼊(DI)依賴的 Bean 元件
  • 為了區分語意不使用@Component 而是帶有語意的@Repository、@Service

設定託管 :

1
2
3
4
@Repository
public class MemberDaoImpl implements MemberDao {
// 略
}

設定注入 @Autowired :

1
2
3
4
5
6
@Service
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberDao dao;
// 略
}

建立核心組態類別 :

1
2
3
4
5
@Configuration
@ComponentScan("web.*.*.impl") //指定要掃描的Bean元件
public class AppConfig {
// 略
}

在 web.xml 中指定 IoC/DI 組態 :

ContextLoaderListener 類別

  • 由 Spring 定義,為 Servlet API 中 ServletContextListener 介⾯的⼦實作類別
  • 註冊此監聽器,並設定特定參數值,即可指定
    Spring 的 組態檔/組態類別

參數 :

  • contextConfigLocation : String type,用來指定 Spring 組態檔或是組態類別
  • contextClass : Class<?> type,用來指定 IoC 容器型態,default 為配合 xml based 的 XmlWebApplicationContext 類別,如要使用 Java based 須額外設定 AnnotationConfigWebApplicationContext 類別
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<web-app ..略>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
</web-app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app ..略>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>core.config.AppConfig</param-value>
</context-param>
</web-app>

Spring 中 JNDI 的支援

  • Spring 提供 JndiObjectFactoryBean 類別,以對應 Web 容器提供的 JNDI
  • 會接管 託 JNDI 管理的物件(像是 DataSource)
  • 接著再設定成 Bean 元件,即可在 Spring IoC/DI 機制使用 JNDI 中的物件

重要屬性 :

  • resourceRef : boolean 型態,預設為 false。若設為 true,則 jndiName 屬性值可省去 “java:comp/env/“
  • jndiName : String 型態,託管在 JNDI 的物件之名稱
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() //為了可以到處注入
throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setResourceRef(true);
bean.setJndiName("jdbc/javaFramework");
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
}

jdbc/javaFramework : 對應 context.xml 中,標籤的 name 屬性之值
afterPropertiesSet() : 使⽤ Java 組態時,須⼿動呼叫此⽅法

Spring 中 Hibernate 的支援

Hibernate 組態設定皆在 tag <session-factory> 中,可以說 SessionFactory 物件是 Hibernate 最核心的物件
Spring 為 Hibernate 提供以下支援

  • XML Based : 使⽤ LocalSessionFactoryBean
  • Java Based : 使⽤ LocalSessionFactoryBuilder

Java Based :

在之前建立的核心組態 class AppConfig 託管 LocalSessionFactoryBuilder。

基本上就是將 hibernate.cfg.xml 中的設定,搬移⾄核⼼組態類別中

與 hibernate.cfg.xml 不同的是

  • Session 環境管理 : 使⽤ Spring 提供的SpringSessionContext 類別
  • 註冊實體類別: 可以使⽤ scanPackages()⽅法,指定欲掃描的套件,與 Hibernate 一樣都要記得 mapping entity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() throws IllegalArgumentException, NamingException {
// 略
}
@Bean
public SessionFactory sessionFactory() throws IllegalArgumentException, NamingException {
return new LocalSessionFactoryBuilder(dataSource())
.scanPackages("web.*.entity")
.addProperties(getHibernateProperties())
.buildSessionFactory();
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", MySQL8Dialect.class.getName());
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "true");
properties.setProperty(
"hibernate.current_session_context_class",
SpringSessionContext.class.getName());
return properties;
}
}

HibernateFilter支援

在 Hibernate 中可以使用 Filter 來做交易控制

Spring 有提供 OpenSessionInViewFilter 可以做使用

在 web.xml 中註冊 :

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<web-app ..略>
<filter>
<filter-name>HibernateFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>HibernateFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

在Spring中注入Session物件

在核心組態設定時已經託管好了 SessionFactory 物件,我們的 DAO 即可注入此物件直接取的 Session 物件
不需要再寫像

1
Session session = getSession();

這樣的程式,但有另一個問題,Session 物件非執行續安全(Thread-Safe),直接使用 Spring 來注入會有多個執行續共用同一 Session 物件的問題,因此使用@PersistenceContext 解決問題

@PersistenceContext 為 JPA 提供之 API

  • 用來注入 EntityManager 型態的物件,⽽ Hibernate 的 Session 型態,即為
    EntityManager 的⼦代
  • 在底層仍是注入 SessionFactory 物件,但會自動呼叫
    openSession()/getCurrentSession(),以取得 Session 物件
  • 效能上並不會提升,僅用來簡化程式碼
1
2
3
4
5
6
@Repository
public class MemberDaoImpl implements MemberDao {
@PersistenceContext
private Session session;
// 略
}

Spring - AOP

AOP 為(Aspect-oriented programming),譯為切面導向程式設計

在 Spring in action 4 中有一段文字是這樣描述的 :

AOP 允許你把遍布應用各處的功能分離出來行程可重複使用的組件。

概念有點像是,你沒有改這段程式碼,但這段程式碼被改變了。
實際開發情境中,卻⼜常將系統功能相關程式,穿插撰寫在商業邏輯相關程式中,且重複出現。
這樣的程式,讓本⾝就繁雜的商業邏輯相關程式,更加複雜,且不易維護。
通常會用在 :

  • 系統功能 : 純軟體技術形成的功能,EX.交易控制、⽇誌(Log)、資安處理
  • 商業邏輯 : 依專案需求形成的功能,EX.會員註冊、會員登⼊、會員登出、會員資料編輯

這個概念比較抽象,但就我自己的理解和老師的說明,@Transcational 的這個 annotation 有點像這個概念,詳見講義,不再此多做說明。

交易控制

  • 交易控制(Transaction Control),⼜稱交易管理(Transaction Management)
  • 意指控制資料庫的交易
  • 順利執行 -> commit
  • 發生執行例外 -> rollback

重要型態

  • 套件 org.springframework.transaction

  • PlatformTransactionManager

    • 交易管理器,是 Spring-交易控制最核⼼的型態
    • 常用子實作類別
      1. DataSourceTransactionManager : 配合 JDBC 使⽤
      2. JpaTransactionManager : 配合 JPA 使⽤
      3. HibernateTransactionManager : 配合 Hibernate 使⽤
  • TransactionDefinition

    • 交易定義,交易的細節設定。相關設定值由@Transactional 設定
    • 傳播⾏為(Propagation behavior) : 交易 何時開始、何時結束、是否需要交易
    • 隔離等級(Isolation level) : 當前交易,與其他交易的隔離程度
    • 逾時(Timeout) :
      • 針對較耗時的交易,可在逾時時自動 rollback
      • 須配合傳播⾏為 PROPAGATION_REQUIRED 或 PROPAGATION_REQUIRES_NEW 使⽤
    • 唯讀(Read-only) :
      • 若交易中只執⾏了 SQL 的 select 敘述,通常資料庫端可做⼀些優化
      • 須配合傳播⾏為 PROPAGATION_REQUIRED 或 PROPAGATION_REQUIRES_NEW 使⽤
  • TransactionStatus

    • 交易狀態,除了部分取代 JDBC API 中的 Connection 型態外,亦可⽤來取得交易相關資訊
    • 可設定 新建儲存點、釋放儲存點、還原⾄儲存點、設定交易只能還原 等控制
    • 可取得 有儲存點否、完成否、為新建否、只能還原(rollback)否 等資訊
  • @Transcational

    • 用以標註需要交易控制的⽅法。亦可加註在型態上,表⽰其內所有⽅法皆需交易控制
    • 交易管理器會使⽤ Spring-AOP 機制,連接有標註此 Annotation 的⽅法,進⽽做交易控制
    • 預設狀況下,發⽣ RuntimeException 例外時,才會 Rollback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan("web.*.*.impl")
@EnableTransactionManagement
public class AppConfig {
// ..略
@Bean
public SessionFactory sessionFactory() throws IllegalArgumentException, NamingException {
// ..略
}
@Bean
public TransactionManager transactionManager() throws IllegalArgumentException,NamingException {
return new HibernateTransactionManager(sessionFactory());
}
}

MVC 架構中,@Transcational 應放在 service 中欲受交易控制的 method 上。

以上即完成 Spring 基礎轉換,後續還可將 DAO、VO 抽換成 Spring Data JPA/MyBatis、整體抽換成 Spring MVC、Spring Boot。
詳見後續筆記。