1 Spring框架的學習路線
Spring(一):IOC容器(Bean管理),Spring與Web項目整合
Spring(二):AOP(面向切面編程),Spring的JDBC模板類
Spring(三):Spring的事務管理,SSH整合開發,Hibernate逆向工程
2 Spring的框架概述
2.1 什么是Spring
Spring是一個開源框架,Spring是於2003 年興起的一個輕量級的Java開發框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中闡述的部分理念和原型衍生而來。它是為了解決企業應用開發的復雜性而創建的。框架的主要優勢之一就是其分層架構,分層架構允許使用者選擇使用哪一個組件,同時為 J2EE 應用程序開發提供集成的框架。Spring使用基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅限於服務器端的開發。從簡單性、可測試性和松耦合的角度而言,任何Java應用都可以從Spring中受益。Spring的核心是控制反轉(IoC)和面向切面(AOP)。簡單來說,Spring是一個分層的JavaSE/EEfull-stack(一站式) 輕量級開源框架。
EE開發分成三層結構:
WEB層:Spring MVC
業務層:Bean管理:(Spring IOC)
持久層:Spring的JDBC模板,ORM模板用於整合其他的持久層框架
相關書籍:
Expert One-to-One J2EE Design and Development:J2EE的設計和開發(2002.EJB)
Expert One-to-One J2EE Development without EJB:J2EE不使用EJB的開發(@interface21)
2.2 為什么學習Spring
- 方便解耦,簡化開發:Spring就是一個大工廠,可以將所有對象創建和依賴關系維護,交給Spring管理
- AOP編程的支持:Spring提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能
- 聲明式事務的支持:只需要通過配置就可以完成對事務的管理,而無需手動編程
- 方便程序的測試:Spring對Junit4支持,可以通過注解方便的測試Spring程序
- 方便集成各種優秀框架:Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
- 降低JavaEE API的使用難度:Spring 對JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠程調用等),都提供了封裝,使這些API應用難度大大降低
2.3 Spring的版本
Spring 3.X 和 Spring4.X
3 SpringIOC入門案例
3.1 快速搭建Spring項目
步驟一:下載Spring的開發jar包
http://repo.spring.io/webapp/search/artifact/?0&q=spring-framework
解壓(Spring目錄結構)
docs:API和開發規范
libs:jar包和源碼
schema:約束
步驟二:創建web項目,引入Spring IOC開發包
IOC:Inverse of Control,控制反轉,將對象的創建權反轉給Spring
Spring IOC的核心內容:

引入jar包(符合上述IOC開發相關的jar包就行了,再加上日志記錄相關兩個jar包):

注:hibernate中的Slf4j包不做具體的日志記錄,只是用於整合其他日志jar包。
步驟三:創建包結構,編寫接口和實現類


步驟四:創建Spring的配置文件
首先在src目錄下創建Spring的配置文件:applicationContext.xml。
然后引入spring的bean約束(spring-framework-3.2.0.RELEASE\docs\spring-framework-reference\html\xsd-config.html的最下面copy)。
最后完成bean配置。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Spring的快速入門=================== --> <bean id="userService" class="com.itheima.spring.demo1.UserServiceImpl"> <!--屬性依賴注入--> <property name="name" value="老王(XML)"/> </bean> </beans>
步驟五:編寫測試程序
新建SpringDemo1類:
@Test /** * 傳統方式開發: */ public void demo1(){ UserServiceImpl userService = new UserServiceImpl(); // 手動設置: userService.setName("老王"); userService.sayHello(); } @Test /** * Spring框架開發: */ public void demo2(){ // 使用Spring的工廠: ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // 通過工廠獲得類: UserService userService = (UserService) applicationContext.getBean("userService"); userService.sayHello(); }
3.2 控制反轉(IOC)和依賴注入(DI)
IOC:控制反轉,將對象的創建權反轉給Spring。
DI:依賴注入,必須將對象的創建權交給Spring,將對象所依賴的屬性注入進來.
3.2.1 面向對象開發中類的三種關系
依賴(A依賴B):
public class A{ private B b; public void setB(B b){ this.b = b; } }
繼承:is a
聚合:has a。又分松散聚合關系與緊密聚合關系。
3.3 Spring的工廠
3.3.1 ApplicationContext

ApplicatioContext接口有兩個實現類:
ClassPathXmlApplicationContext:加載類路徑下Spring的配置文件
FileSystemXmlApplicationContext:加載本地磁盤下Spring的配置文件
/**
* Spring框架開發:讀取本地磁盤配置文件
*/
public void demo3(){
// 使用Spring的工廠:
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\applicationContext.xml"); // 通過工廠獲得類: UserService userService = (UserService) applicationContext.getBean("userService"); userService.sayHello(); }
關閉工廠的方法只有其子類中才有:

3.3.2 BeanFactory

3.3.3 ApplicationContext和BeanFactory的區別
ApplicationContext:在加載applicationContext.xml時就會創建類的實例
BeanFactory:是在調用getBean方法時才會生成類的實例

4 Spring的Bean管理
4.1 XML方式
4.1.1 Spring創建Bean的方式
4.1.1.1 使用無參數構造方法實例化Bean(默認)
<!-- 無參數的構造方法: --> <bean id="bean1" class="com.itheima.spring.demo2.Bean1"></bean>
4.1.1.2 靜態工廠實例化Bean
提供靜態工廠:
public class Bean2Factory { public static Bean2 getBean2Instance(){ return new Bean2(); } }
完成配置:
<!-- 靜態工廠實例化 --> <bean id="bean2" class="com.itheima.spring.demo2.Bean2Factory" factory-method="getBean2Instance"/>
4.1.1.3 實例工廠實例化Bean
提供實例工廠:
public class Bean3Factory { public Bean3 getBean3Instance(){ return new Bean3(); } }
完成配置:
<!-- 實例工廠實例化 --> <bean id="bean3Factory" class="com.itheima.spring.demo2.Bean3Factory"></bean> <bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3Instance"></bean>
4.1.2 bean的配置
4.1.2.1 屬性id和name的區別
id:給Bean起名。在schema約束中要求采用的ID必須唯一,必須以字母開始,可以使用字母、數字、連字符、下划線、句號、冒號,不能出現特殊字符。如:<bean id=”bookAction”>
name:給Bean起名。沒有采用ID的唯一約束,可以出現特殊字符。如果<bean>沒有id屬性的話,name屬性可以當做id使用。
注:整合struts1的時候需要包含特殊字符'/',如:<bean name=”/loginAction” >
其他屬性還包括:class(Bean的全路徑)、factory-bean、factory-method。
4.1.2.2 scope屬性
指定Bean的作用范圍,值包括:
- singleton:單例(默認)
- prototype:多例。action交給struts2管理時,默認是多例,交給spring管理時,需要設置scope="prototype"
- request:應用在Web項目中,spring創建完對象后,將對象存入到request域中
- session:應用在Web項目中,spring創建完對象后,將對象存入到session域中
- globalsession:應用在Web項目中,應用在Porlet環境(主網站登陸之后子網站就不用再次登陸了,也就是單點登錄)
4.1.2.3 init-method和destroy-method屬性
Bean的生命周期相關屬性。
init-method:初始化的時候執行的方法
destroy-method:銷毀的時候執行的方法。使用該方法的前提:Bean生成必須是單例的,在工廠關閉的時候銷毀。
<bean id="orderDao" class="com.itheima.spring.demo3.OrderDao" init-method="setup" destroy-method="teardown"></bean>
4.1.2.4 Bean的生命周期
Bean的完整的生命周期有十一個步驟:
- instantiate bean對象實例化(執行bean的構造函數)
- populate properties封裝屬性
- 如果Bean實現BeanNameAware接口,執行setBeanName方法
- 如果Bean實現BeanFactoryAware接口或者ApplicationContextAware接口,執行設置工廠setBeanFactory方法或者上下文對象的setApplicationContext方法
- 如果存在類實現BeanPostProcessor接口(后處理Bean),執行postProcessBeforeInitialization方法
- 如果Bean實現InitializingBean接口,執行afterPropertiesSet方法
- 調用<bean init-method="init">指定的初始化方法
- 如果存在類實現BeanPostProcessor接口(后處理Bean),執行postProcessAfterInitialization方法
- 執行業務處理
- 如果Bean實現DisposableBean接口,執行destroy方法
- 調用<bean destroy-method="customerDestroy">指定的銷毀方法
第3步和第4步讓JavaBean了解Spring的容器,可以在bean中調用spring容器的方法。
第5步和第8步非常重要,BeanPostPorcessor作用是在Bean的實例化的過程中對Bean的功能進行增強。
Factory hook(工廠鈎子) that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies.
增強一個Bean中某個方法的幾種方式:
繼承
能夠控制這類的構造!!!也就是自己能夠創建該類的實例。
裝飾者模式:
被增強類和增強類實現相同的接口。
在增強的類中獲得到被增強類的引用。
缺點:如果接口中方法太多,其他方法都需要原封不動地調用。
動態代理:
比較靈活
JDK動態代理前提:類實現接口
對實現接口的類產生代理
4.1.3 屬性注入
4.1.3.1 構造方法注入
實體類提供構造方法:

<bean id="car" class="com.itheima.spring.demo5.Car"> <!-- 按屬性名稱--> <!-- <constructor-arg name="name" value="寶馬"/> <constructor-arg name="price" value="500000"/> --> <!-- 按屬性角標,type屬性可以不加 --> <constructor-arg index="0" type="java.lang.String" value="奔馳"/> <constructor-arg index="1" value="800000"/> </bean>
4.1.3.2 set方法注入
實體類需要提供屬性的set方法
<bean id="car2" class="com.itheima.spring.demo5.Car2"> <property name="name" value="奇瑞QQ"/> <property name="price" value="35000"/> </bean> <bean id="employee" class="com.itheima.spring.demo5.Employee"> <property name="name" value="老王"/> <!-- ref屬性指定值為一個bean --> <property name="car2" ref="car2"/> </bean>
4.1.3.3 p名稱空間的注入(Spring2.5提供)
set方法注入問題:屬性一旦很多,就需要很多的property標簽。
引入p名稱空間約束:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
p名稱空間注入語法:p:屬性名=””或者p:屬性名-ref=””
<bean id="car2" class="com.itheima.spring.demo5.Car2" p:name="長安奔奔" p:price="40000"/> <bean id="employee" class="com.itheima.spring.demo5.Employee" p:name="隔壁老王" p:car2-ref="car2"></bean>
4.1.3.4 SpEL(Spring Expression Language)注入(Spring3.0提供)
語法:#{SpEL}
<bean id="car2" class="com.itheima.spring.demo5.Car2" > <property name="name" value="#{'法拉利'}"/> <property name="price" value="#{1200000}"/> </bean> <bean id="employee" class="com.itheima.spring.demo5.Employee"> <property name="name" value="#{'豆豆'}"/> <!-- #{beanid}指定類類型屬性的值 --> <property name="car2" value="#{car2}"/> </bean>
<bean id="carInfo" class="com.itheima.spring.demo5.CarInfo"></bean> <bean id="car2" class="com.itheima.spring.demo5.Car2" > <property name="name" value="#{carInfo.carName}"/> <property name="price" value="#{carInfo.calculatorPrice()}"/> </bean>
4.1.3.5 數組,集合(List,Set,Map),Properties屬性注入
<bean id="collectionBean" class="com.itheima.spring.demo6.CollectionBean"> <!-- 數組屬性注入 --> <property name="arrs" > <list> <value>老王</value> <value>鳳姐</value> <value>如花</value> </list> </property> <!-- List屬性注入 --> <property name="list"> <list> <value>豆豆</value> <value>奶茶</value> <value>綠茶</value> </list> </property> <!-- Set屬性注入 --> <property name="set"> <set> <value>王堯</value> <value>劉健</value> <value>周玉</value> </set> </property> <!-- Map屬性注入 --> <property name="map"> <map> <entry key="老王2" value="38"/> <entry key="鳳姐" value="38"/> <entry key="如花" value="29"/> </map> </property> <!-- Properties屬性注入 --> <property name="properties"> <props> <prop key="username">root</prop> <prop key="password">123</prop> </props> </property> </bean>
4.2 注解方式
4.2.1 注解IOC的快速入門
步驟一:下載Spring的開發包
同3.1節的步驟一。
步驟二:創建web項目,引入Spring IOC開發包
同3.1節的步驟二。
步驟三:創建包結構,編寫接口和實現類
同3.1節的步驟三。
步驟四:創建Spring配置文件,開啟組件掃描
首先在src下創建applicationContext.xml文件。
然后除了引入spring的bean約束以外,還要引入context約束。
最后開啟Spring的組件掃描配置。
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 開啟Spring組件掃描 --> <context:component-scan base-package="com.itheima.spring.demo1"/> <beans>
開啟組件掃描后,Spring會掃描base-package屬性指定包里的所有類上的@Component注解。在這些類的屬性上就可以使用屬性注入的注解,比如@Value
多個包的掃描可以用逗號隔開,也可以直接掃描上層包,比如com.itheima.spring。
注:
context:component-scan:會掃描包,支持類上注解和Resource注解
context:annotation-config:只支持不是類上的注解
步驟五:在類上添加注解
@Component(value="userService")
不配value默認將類名首字母小寫后作為id
相當於xml方式的:<bean id=”userService” class=”...”/>
步驟六:在屬性上添加注解
普通屬性:@Value(value="老王")
屬性就不需要提供set方法了

步驟七:編寫測試類
@Test public void demo2() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml"); UserService userServie = (UserService)applicationContext.getBean("userService"); userServie.sayHello(); }
4.2.2 Spring的Bean管理的常用注解
4.2.2.1 組件注解@Component
作用在類上。
Spring提供了@Component的三個衍生注解,功能目前來講是一致的:
@Controller:作用在Controller層的類上
@Service:作用在Service層的類上
@Repository:作用在DAO層的類上
這三個注解是為了讓標注類本身的用途清晰,Spring在后續版本會對其增強
4.2.2.2 屬性注入注解
在類中的屬性上使用屬性注入注解時,對應屬性可以不提供setter方法。
屬性注入注解有:
@Value:用於注入普通類型屬性,比如String類型。其value屬性指定要注入的屬性值。
@Autowired:自動裝配,用於注入類類型屬性,默認按類的類型進行注入(裝配)。默認情況下必須要求依賴對象必須存在,如果要允許null值,可以設置它的required屬性為false,例如:@Autowired(required=false)
@Qualifier:與@Autowired一塊使用,用於注入類類型屬性,並強制使用類的名稱注入。其value屬性指定要注入類類型屬性的類的名稱,類的名稱則通過組件注解的value屬性自定義。
@Resource:用於注入類類型屬性,相當於@Autowired和@Qualifier一起使用,默認使用類的名稱注入(裝配),其name屬性指定要注入類類型屬性的類的名稱,不指定name屬性的值則名稱默認值為屬性名。
@Resource應用在字段(成員變量)上注入規則是:
a.先使用字段名字匹配bean,查找到bean則注入。如果bean中定義的類型與注入的類型不匹配則異常
b.如果沒有指定name屬性並且字段名字沒有匹配到Bean,則會嘗試采用字段類型匹配,如果找到bean則注入。如果字段類型是接口則有可能會匹配到多個類型,會拋出匹配到多個bean的異常
@Resource應用在setter方法上注入規則是:
a.先使用setter方法形參參數名匹配bean,如果bean中定義的類型與注入的類型不匹配則異常
b.如果屬性名字沒有匹配到Bean,則會嘗試采用屬性參數類型匹配。如果找到bean則注入,如果屬性參數類型是接口則有可能會匹配到多個bean的異常,注入失敗。所以,一般采用實現類做為set方法的參數,這也是之所以有字段注入還存在set注入的原因。
理解字段注入和set注入區別的例子:
//接口UserDao public interface UserDao; //接口實現1 public class UserDao1 implements UserDao {...}; //接口實現2 public class UserDao2 implements UserDao {...}; //1、采用字段注入,則會注入失敗(會出現匹配多個bean的異常) @Resource private UserDao userDao; //2、采用setter方法注入,則可以注入UserDao1 @Resource public void setUserDao(UserDao1 userDao) //注入UserDao2 @Resource public void setUserDao(UserDao2 userDao)
屬性注入注解使用例子:

注意:
@Autowired是屬於Spring的注解
@Resource為JSR-250標准的注釋,屬於J2EE,使用它減少了與spring的耦合,代碼看起比較優雅。
4.2.2.3 Bean的作用范圍注解
@Scope
默認value屬性值為singleton,單例。還可以指定為prototype,多例。

4.2.2.4 Bean的生命周期配置注解
@PostConstruct:相當於xml方式的init-method屬性
@PreDestroy:相當於xml方式的destroy-method屬性

Bean的作用范圍必須是單例singleton才能在spring工廠執行close方法時執行@PreDestry注解標注的方法。
@Resource、@PostConstruct、@PreDestroy所屬的包是javax.annotation,如果jdk版本過低,比如1.5,這三個注解將無法使用。
4.2.3 使用Java類定義Bean信息(了解)
Spring3.0以JavaConfig為核心,提供以JavaBean作為配置文件方式的開發。
@Configuration public class BeanConfig { @Bean(name="car") public Car showCar(){ Car car = new Car(); car.setName("蘭博基尼"); car.setPrice(2000000d); return car; } @Bean(name="product") public Product showProduct(){ Product product = new Product(); product.setName("蘋果6sp"); product.setPrice(8500d); return product; } }
4.3 Spring的Bean管理方式比較

XML:結構清晰
注解:屬性注入,開發方便
實際開發中還有一種XML和注解整合開發:Bean的創建由XML配置,但是屬性使用注解注入。
5 Spring整合Web項目
5.1 Spring整合Web項目步驟
步驟一:創建web項目,引入Spring IOC開發包
參考3.1的步驟二。
步驟二:創建包結構
com.itheima
web
UserServlet
service
UserService
dao
UserDao
步驟三:創建Spring的配置文件
這里使用xml方式管理Spring中的bean。所以只需要引入bean的約束即可。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service和DAO到Spring的配置文件中 --> <bean id="userService" class="com.itheima.service.UserService"> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.itheima.dao.UserDao"></bean> </beans>
步驟四:在Servlet中獲得Service對象
// 創建工廠 ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml"); //獲取Service對象 UserService userService = (UserService) applicationContext .getBean("userService"); System.out.println("UserServlet的方法執行了..."); //執行Service對象方法 userService.save();
步驟五:Spring工廠初始化原理及配置
問題:此時訪問一次Servlet,就會創建一個Spring的工廠,因而效率很低。
思路1:將Spring工廠的創建放入到Servlet的init方法中。這種方式不好,有多個Servlet的情況,創建一個Servlet就會調用一下init方法創建一個Spring的工廠。
思路2:將Spring工廠創建好以后放入ServletContext域中。使用工廠的時候,從ServletContext中獲得。
ServletContextListener接口的實現類是用來監聽ServletContext對象的創建和銷毀的監聽器,Spring的核心監聽器ContextLoaderListener實現了ServletContextListener接口:

當ServletContext對象創建的時候,創建spring工廠, 然后將spring工廠存入到ServletContext對象中:

因此,Spring整合Web項目時還需要引入spring-web-3.2.0.RELEASE.jar包,它包含了Spring的核心監聽器ContextLoaderListener。
然后還需要在web.xml中配置Spring的核心監聽器ContextLoaderListener:
<!-- 配置Spring的核心監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 因為該監聽器默認是加載WEB-INF目錄下的配置文件,所以需要修改參數讀取類路徑下的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
步驟六:在Servlet中獲得Spring工廠
web.xml中配置servlet:
<servlet> <servlet-name>UserServlet</servlet-name> <servlet-class>com.itheima.web.UserServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UserServlet</servlet-name> <url-pattern>/userServlet</url-pattern> </servlet-mapping>
在Servlet中從ServletContext中獲得Spring工廠:
方式一:ServletContext.getAttribute(“...”);//參數名太長,不推薦
方式二:WebApplicationContextUtils.getWebApplicationContext(ServletContext context);

5.2 Spring的分配置文件開發
Spring中提供了兩種方式加載多個配置文件:
一、主配置文件applicationContext.xml中包含其他的配置文件:
<import resource="applicationContext2.xml"/>
二、工廠創建的時候直接加載多個配置文件:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml","applicationContext2.xml");
實際開發中Spring整合web項目在web.xml中配置spring監聽器ContextLoaderListener,並加載spring容器至web容器ServletContext中
<!-- 配置Spring的核心監聽器:ContextLoaderListener,實現了ServletContextListener用來監聽ServletContext對象(即web容器)的創建和銷毀 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 加載spring容器至web容器中 --> <context-param> <param-name>contextConfigLocation</param-name> <!-- spring的配置文件名稱以applicationContext-XXX.xml命名,方便維護 --> <!-- Include包含的方式缺陷是每個模塊的配置文件都需要手動包含至web.xml中 --> <param-value>/WEB-INF/classes/spring/applicationContext.xml,/WEB-INF/classes/spring/applicationContext-*.xml</param-value> </context-param>
6 Spring整合JUnit單元測試
Spring為了簡化測試,提供與JUnit的整合,就不需要每次在測試方法中手動創建spring工廠對象。
首先手動添加Junit4的環境:

然后在程序中引入spring-test-3.2.0.RELEASE.jar。
測試類編寫:
@RunWith(SpringJUnit4ClassRunner.class)//spring和JUnit整合的核心運行類 @ContextConfiguration("classpath:applicationContext.xml")//spring配置文件路徑 public class SpringDemo1 { @Resource(name="userService")//此時引入了以上jar包之后,不需要開啟spring組件掃描配置了 private UserService userService; @Test public void demo2(){ userService.save(); } }
