學習spring的筆記

<!doctype html>Spring框架html

 

 

Spring框架

第一章 spring的使用

spring是現現在最流行的框架之一,有人說沒學過spring框架那麼就沒有資格說本身學過java。建立一個簡單的spring,須要哪些條件。node

一、添加一個spring依賴,固然若是是maven項目的話只須要添加倉庫地址便可,好比:mysql

 

 
 
 
 
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.2.0.RELEASE</version>
            </dependency>
 

 

二、建立一個spring.xml配置文件(元數據),在此文件經過一個個的bean來告訴spring管理哪些類,這個類必須是能夠實例化的,因此不能是接口,抽象類等。jquery

若是你不肯定spring.xml配置文件基礎格式是什麼樣的,沒關係的,咱們能夠經過idea的快捷鍵來建立一個spring.xml的配置,作法以下:git

 
 
 
xxxxxxxxxx
 
 
 
 
選中文件夾右鍵 ————> new ------>XML Configuration File ------->spring Config
 

若是你的idea沒有這個功能,你也別慌,你能夠把下面的配置代碼複製粘貼到你的配置文件裏:github

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    
</beans>
 

記住,spring的xml文件通常命名爲 applicationContext.xmlweb

 

三、在xml裏面寫上你要管理的類,好比我有一個叫UserInfo的類,那麼我在spring的xml就應該這樣配置:ajax

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

四、建立一個類,在類方法裏面建立一個ApplicationContext對象,通常是它子類的是實例(ClassPathXmlApplicationContext),實例化對象的時候傳遞配置文件名。spring

五、獲取管理的對象,用getBean(id:配置文件bean元素的id)獲取這個id管理的bean。

用法以下:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
    public static void main(String[] args){
        //獲取ApplicationContext對象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //經過getBean()方法獲取對象
        UserInfo userInfo = context.getBean("userInfo",UserInfo.class);
    }
}
 

六、哪些類應該被spring管理

dao、service、部分工具類能夠被spring管理,可是servlet是必定不能被spring管理的。實體類,通常不被spring管理,從技術的角度來看是能夠被spring管理的。

 

spring管理bean的做用域(scope)

做用域就是:你的臉有多大,你說的話就有多大的效果。

在spring中做用域其實指的就是被spring管理的bean的存活時間。

spring的做用域有4種:

一、prototype(原型):每次getBean的時候都會從新建立一個新的對象。

二、singleton(單例):此做用域類型的bean,在spring容器啓動時已經被建立,每次getBean的時候,直接從容器中獲取便可。

三、requet(請求):request與session做用域都是在web環境下才有效,被spring管理的bean它的生存期就是在一個完整的請求週期裏面。

四、session(會話):就是bean的做用範圍是一個完整的session,不一樣人有不一樣的會話,互相不干擾。

默認狀況下,被spring管理的bean的做用域是singleton,用的最多的就是一、2。singleton做用域的bean,其實例是在容器啓動時建立,之後就再也不建立。prototype做用的bean是在每次getBean的時候都反覆建立bean的實例出來。

如何建立一個原型的bean:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo" scope="prototype"/>
</beans>
 

 

初始化和銷燬

如何作到對象初始化的時候作某件事情?

若是沒有spring框架,寫代碼的一個規範是構造函數裏面不要寫很複雜的代碼,若是是這種狀況,建議是把這些代碼抽取到一個獨立的方法中去,這個方法通常叫init這種名字。按照之前的寫法就必須在構造函數中調用這個init方法。

有了spring以後,就能夠直接設置一下就會讓init被執行,init執行就是在對象建立出來後自動獲得執行。

init例子

下面寫一個spring管理bean初始化的例子:

好比有一個UserInfo類:

 
 
 
xxxxxxxxxx
 
 
 
 
public class UserInfo{
public void init(){
System.out.pringln("我是初始化方法");
}
}
 

在spring的xml文件裏就須要對這個init方法的調用。能夠兩個地方設置

第一種是在這個被spring管理的bean裏設置,好比:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo" init-method="init"/>
</beans>
 

這個配置只在當前bean生效。

還有一種是在當前spring的xml管理的bean生效,是在beans裏面配置一個default-init-method="init":

 
 
 
xxxxxxxxxx
 
 
 
 
<?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"
       default-init-method="init">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

若是做用域是singleton,init只會執行一次,prototype的話就會反覆調用。

設置了spring的xml配置後,那麼應該怎麼用呢?

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo userInfo = context.getBean("userInfo");
}
}
 

這樣它就自動調用了UserInfo類裏面的init方法

destroy例子

銷燬方法(destroy)跟init差很少的用法。它是在ApplicationContext對象關閉生效的。

好比:

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class UserInfo{
public void init(){
System.out.pringln("我是初始化方法");
}
    public void destroy(){
        System.out.pringln("我是銷燬的方法");
    }
}
 

在spring的xml配置跟init同樣也是有兩種配法:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo" init-method="init" destroy-method="destroy"/>
</beans>
 

還有一種整個spring配置文件管理的bean生效的配置

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?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"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置以下-->
    <bean id="userInfo" class="com.entity.UserInfo"/>
</beans>
 

用法就跟init不同了。它須要關閉ApplicationContext對象的時候纔會調用destroy的方法。可是ApplicationContext對象裏面是沒有close方法的,可是它子類有。因此關閉的時候就須要類型強轉。

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserInfo userInfo = context.getBean("userInfo");
        //執行下面這句話的時候調用銷燬的方法
        ((ConfigurableApplicationContext)context).close();
}
}
 

注意:若是全局設置後,spring管理的全部bean的初始化與銷燬都不須要在進行設置,若是某一個bean也設置了init-method或者destroy-method就會覆蓋全局的設置。

除了上面的方法外還能夠實現接口進行初始化和銷燬。

實習的接口有兩個:一個是初始化的接口InitializingBean,和銷燬的接口DisposableBean

實現這兩個接口的方法:

afterPropertiesSet():這個方法名取名叫:"在屬性設置完畢以後",其意思就是此類中setter方法被調用後纔會調用這個初始化的方法。

destroy():銷燬時運行。

代理類,返回代理類的實例

當調用一個類的方法的時候,返回另外一個類的實例,這種就是叫作代理類。

舉個例子:

當有個A類,它裏面有個update方法:

 
 
 
xxxxxxxxxx
 
 
 
 
public class A{
public void update(){
System.out.println("我是A類的update方法");
}
}
 

下面建立一個A類的代理類B類,有個方法返回一個A的實例:

 

 
 
 
xxxxxxxxxx
 
 
 
 
public class B{
public void update(){
System.out.println("我是A類的update方法");
}
    public static A createA(){
        return new A();
    }
}
 

在配置文件裏配置以下:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?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"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置以下-->
    <bean id="b" class="com.entity.B" factory-method="createA"/>
</beans>
 

上面這個配置方法createA方法必須是靜態的,若是不是靜態的,那麼應該先建立一個B的實例出來,而後調用它的方法,好比:

 

 
 
 
xxxxxxxxxx
 
 
 
 
<?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"
       default-init-method="init" default-destroy-method="destroy">
    <!--配置以下-->
    <bean id="b" class="com.entity.B"/>
   <bean id="createA" factory-bean="b" factory-method="createA" />
</beans>
 

那麼在用的時候:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main{
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("createA");
}
}
 

它就成功的返回了A的實例出來。

若是不想太依賴spring框架,那麼能夠實現接口來實現代理方法:

 
 
 
x
 
 
 
 
public class MyFactoryBean implements FactoryBean<A> {
    /**
     * 這個方法用來建立一個對象
     * @return
     * @throws Exception
     */
    public A getObject() throws Exception {
        return new A();
    }
    /**
     * 這個方法是用來代表此工廠Bean建立出來的對象的class
     * @return
     */
    public Class<?> getObjectType() {
        return A.class;
    }
    /**
     * 這個方法代表此工廠Bean建立出來的對象,在spinrg管理下的做用域
     * true表示是singleton
     * @return
     */
    public boolean isSingleton() {
        return true;
    }
}
 

上面這個配置方式返回的是A這個類的實例,緣由是由於MyFactoryBean實現了FactoryBean這個接口,若是沒有這個實現,那麼返回的就是MyFactoryBean這個實例自己。

小常識

BeanFactory與ApplicationContext的不一樣

這兩個類是spring框架中2個表明性的容器類型(接口)功能上BeanFactory主要有獲取實例的功能(getBean)

ApplicationContext在BeanFactory基礎上進行了功能的加強,好比,讀取消息(作國際化),發佈事件,從新加載資源

spring-core,spring-beans,spring-web各個依賴裏面大體有哪些功能?

spring-core:提供spring框架的一些核心的基礎功能,好比動態代理的建立,讀取文件等

spring-beans:提供spring框架的核心的bean管理,ioc,aop等能力

spring-web:spring框架與servlet技術整合使用

配置bean的時候,其lazy-init是什麼意思?

lazy-init:延遲初始化的意思。 spring容器啓動時,默認是立刻實例化scope爲singleton對象,這種作法有一個缺點,就是若是spring容器管理的bean過大,就會致使性能低,因此就提供了一個選項lazy-init,設置爲true的時候,容器啓動的時候,這個設置的bean不會當即實例化,而會等到第一次調用getBean獲取實例對象是才實例化。

這個lazy-init屬性對scope爲prototype的bean無效.

理解prototype做用域時,爲何銷燬方法不生效?

由於prototye就是每次getBean的時候臨時建立一個新的對象 ,此對象不會被spring所引用,因此就沒法對其生命週期進行管理, 因此就不會有銷燬方法被調用。

singleton的bean,因爲在容器啓動時會建立對象出來,並且 會被spring所引用,管理,因此當spring關閉時,會調用其銷燬方法

 

第二章 依賴注入

IOC:inverse of control 控制反轉

DI:dependency injection 依賴注入

ioc==DI ,網上有些人認爲ioc跟DI不是一回事。

瞭解控制反轉

假設有兩個類UserService、UserDao,不反轉的狀況,意思是:

UserService service = new UserService();

service.setDao(new UserDao);

由於上面的UserService 類須要UserDao,給其添加UserDao實例的過程是由本身寫代碼控制,就是一切都在自己的掌控中,這種狀況就叫作不反轉。

反轉的就是:

context.getBean("UserService",UserService.class);

上面的代碼執行完畢以後,UserService已經幫其設置了UserDao。

依賴注入:全部被spring管理的bean,當它依賴一個被其它spring管理的bean的時候,spring負責把其注入進來。

spring管理的依賴注入的形式有三種:

一、構造函數的注入。

二、屬性注入(setter)。

三、接口注入(不推薦使用)。

構造函數注入與屬性注入

有個類:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DbConfig {
    private String url;
    private String username;
    private String password;
    private String driverClassname;
    public DbConfig(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getDriverClassname() {
        return driverClassname;
    }
    public void setDriverClassname(String driverClassname) {
        this.driverClassname = driverClassname;
    }
    @Override
    public String toString() {
        return "DbConfig{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", driverClassname='" + driverClassname + '\'' +
                '}';
    }
}
 

上面這個類,既有屬性又有構造函數

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
<!-- 下面的配置順序與構造函數是一一對應,因此再也不須要額外的設置-->
    <!--<bean id="dbConfig" class="inject.basic.DbConfig">
        <constructor-arg value="url.."/>
        <constructor-arg value="root"/>
        <constructor-arg value="pwd"/>
        <property name="driverClassname" value="driver"/>
    </bean>-->
    <!--
   當下面的構造設置順序與想複製的類的構造函數順序不一致時
   不能僅僅只是設置一個value,還須要額外的信息告訴
   spring,誰賦值給誰
    -->
    <bean id="dbConfig" class="inject.basic.DbConfig" >
        <!--原本這個root應該是賦值給url,但由於加了name=username
        因此就把root 賦值給了構造函數的第二個參數
        -->
        <constructor-arg value="root" name="username"  />
        <constructor-arg value="pwd" name="password"/>
        <constructor-arg value="url.." name="url"/>
        <property name="driverClassname" value="driver"/>
    </bean>
    <!--<bean id="dbConfig" class="inject.basic.DbConfig">
        <constructor-arg value="root" index="1" />
        <constructor-arg value="pwd" index="2"/>
        <constructor-arg value="url.." index="0"/>
        <property name="driverClassname" value="driver"/>
    </bean>-->
<!--配置時,通常是java類型對應一樣名字的配置元素
好比List集合,你就用list來配置,Set集合就用set元素配置
集合類型與配置元素類型都會起做用:意思是Set集合類型的元素裏面是不能重複
若是你用List元素配置了重複的內容,但Set集合仍然不會有重複
若是你是List集合,但用set元素來配置,就可讓此List集合內部沒有重複
-->
    <bean id="addr" class="inject.basic.Address">
        <property name="province" value="gd"></property>
        <property name="city" value="zhuhai"/>
    </bean>
    <bean id="userInfo" class="inject.basic.UserInfo">
        <property name="name" value="aaaa"/>
       <!-- <property name="address" ref="addr"/>-->
        <!--下面的配置方式叫作inner bean(內部bean)
            只是給address屬性使用,沒法經過getBean方式獲得這個對象
        -->
        <property name="address" >
            <bean class="inject.basic.Address" />
        </property>
        <property name="hobbyList" >
            <list>
                <value>football</value>
                <value>basketball</value>
                <value>basketball</value>
            </list>
        </property>
        <property name="xueLi">
            <set>
                <value>chu zhong</value>
                <value>gao zhong</value>
                <value>gao zhong</value>
            </set>
        </property>
        
        <property name="xueFen">
            <map>
                <entry key="yuwen" value="95"/>
                <entry key="shuxue" value="100"/>
            </map>
        </property>
        <!--若是用props配置,值只能是字符串類型-->
        <property name="properties">
            <props>
               <prop key="javaT">111</prop>
               <prop key="netT">yongguang</prop>
            </props>
        </property>
        <property name="addressList">
            <list>
                <ref bean="addr"/>
                <bean class="inject.basic.Address">
                    <property name="city" value="ganzhou"/>
                    <property name="province" value="jiangxi"/>
                </bean>
            </list>
        </property>
    </bean>
</beans>
 

因此說,構造函數的模糊性解決辦法有三個:

一、根據name來匹配

二、根據index來匹配

三、根據type來匹配

 

注入的類型

你們都知道,在java裏面是有不少類型的。那麼在配置的時候應該怎麼來配置:

一、普通類型:能夠是用value來匹配:

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="name" value="aaaa"/>
 

二、對象類型。若是是本身定義的類,那麼可使用ref或者使用內部bean的方式。

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="address" ref="b"/>
 
 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="address">
     <bean class="com.entity.Address"></bean>
</property>
 

三、list屬性

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
     <list>
         <value>aaa</value>
     </list>
</property>
 

四、map

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
      <map>
            <entry key="yuwen" value="95"/>
            <entry key="shuxue" value="100"/>
       </map>
</property>
 

五、set

 
 
 
xxxxxxxxxx
 
 
 
 
 <property name="list">
     <set>
         <value>bbb</value>
     </set>
</property>
 

set跟list的區別。list的值能夠重複,set不能夠重複

五、properties

 
 
 
xxxxxxxxxx
 
 
 
 
 <!--若是用props配置,值只能是字符串類型-->
        <property name="properties">
            <props>
               <prop key="javaT">111</prop>
               <prop key="netT">yongguang</prop>
            </props>
        </property>
 

ref:references的縮寫,引用的意思,它的值通常是另外一個被spring管理的bean的id。

六、若是類型是String的類型,它想賦值爲null。那麼直接設置爲」「是表示空字符串,而不是null。

 
 
 
xxxxxxxxxx
 
 
 
 
<property name="name">
    <null></null>
</property>
 

 

命名空間

namespace:命名空間

在spring裏命名空間有兩個:分別是

p:表明着屬性的意思

c:表明着構造函數的意思

命名空間主要是讓咱們少寫一些代碼。

用法以下:

 
 
 
x
 
 
 
 
<?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: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.xsd">
  <bean id="dataSource"
        class="inject.namespace.MyDataSource"  p:username="sa" c:url="url--" >
      <property name="password" >
          <null></null>
      </property>
  </bean>
    <bean id="factory" class="inject.namespace.MySqlFactory"
          p:dataSource-ref="dataSource"
    c:_0-ref="dataSource">
    </bean>
</beans>
 

引入了

xmlns:p="http://www.springframework.org/schema/p" ​ xmlns:c="http://www.springframework.org/schema/c"

就能夠在配置裏用p跟c了。

父子bean

在子bean裏面設置了parent屬性,只是表名在xml配置中會有繼承配置的關係,並不要求實際的java類有繼承關係。

當一個bean設置abstract爲true的時候,意思就是告訴spring不要實例化這個bean出來跟實際的java類是不是抽象無關。通常狀況若是一個bean的主要的做用是給子bean繼承用,其class能夠不設置。

好比:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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: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.xsd">
<!--在子bean裏面設置了parent屬性,只是代表在xml配置中會有繼承配置的關係
並不要求實際的java類有繼承關係
當一個bean設置abstract爲true的時候,意思就是告訴spring不要實例化這個bean出來
跟實際的java類是不是抽象無關
通常狀況若是一個bean的主要的做用是給子bean繼承用,其class能夠不設置
-->
    <bean id="myParent"  abstract="true">
        <property name="p1" value="p1"/>
        <property name="p2" value="p2"/>
    </bean>
    <bean id="childOne"
          class="inject.parentchild.ChildOne" parent="myParent">
        <property name="childOne" value="childOne"/>
    </bean>
</beans>
 

autowire

autowire的理解

 
 
 
xxxxxxxxxx
 
 
 
 
 關於自動裝配(autowire)的理解
        1.啓動spring容器
        2.掃描xml文件.
        3.spring就知道全部被它管理的bean
        4.每一個bean,spring經過反射是否是都知道
        其類型,以及構造函數,以及全部的屬性(property)
        5.由於bean的做用域是singleton,因此spring要建立出
        對象,建立對象包含實例化和初始化
         5.1 實例化就是調用構造函數,若是構造函數有依賴
         ,spring就會嘗試解決掉這個依賴是什麼東西
         5.2 初始化:全部的屬性,spring都嘗試幫你注入值進來
        6.spring嘗試給某個bean全部依賴的東西,幫你注入進來
        7.若是你配置了自動裝配,那麼spring就幫你找一個合適的
        8.如何找呢? 須要依據自動裝配類型的設置,autowire的值
            8.1 byType:由於你的Service類依賴的類型是UserDao
                spring又知道全部被它管理的UserDao bean配置只有一個
                因此就自動幫你注入這一個.
                若是找到多個:報錯
             8.2 byName:找到service類裏面有一個屬性名叫dao
             找全部被管理bean的名字爲dao的,就幫你注入.
            若是找到的bean id值爲dao的類型不符合要求,也會報錯
若是有多個bean符合自動裝配
能夠經過在全部符合條件的被裝配bean上進行設置來解決
1.在想被注入的bean上設置primary=true,就表示用這個
2. 在不想被注入的bean上設置autowire-candidate=false
還能夠在beans這個根元素上,配置default-autowire-type來設定
一個全局的自動裝配類型,這樣就不須要在每個bean上進行設置了
beans上的default-autowire-candidates =設置做爲候選baen的名字模式
多個之間用逗號分隔,好比*dao這個名字,意思就是以dao結尾的bean 的名字
 

xml配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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"
default-autowire-candidates="*dao" default-autowire="byType">
    <bean id="dao" class="autowire.UserDaoImpl" ></bean>
    <bean id="userDao2" class="autowire.UserDaoImpl2"  ></bean>
    <bean id="userService"
          class="autowire.UserServiceImpl"
          >
    </bean>
</beans>
 

在 default-autowire-candidates="*dao" 能夠寫多個值。

第三章 與其它框架整合

與dbutil+druid整合

既然是和dbutil、druid整合,那麼它們的依賴確定要導入項目裏來。

在這裏dbutil是apache下的dbutil,你能夠在中央倉庫找這個依賴。若是你使用的是maven框架寫項目的話就加入以下配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
       <groupId>commons-dbutils</groupId>
       <artifactId>commons-dbutils</artifactId>
       <version>1.7</version>
</dependency>
 

druid是阿里的druid。一樣若是你要這個jar包,你也能夠去中央倉庫去找。你使用的是maven框架寫項目的話就在配置里加入:

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.19</version>
            </dependency>
 

弄好依賴就開始寫項目了。咱們最後的效果是在瀏覽器裏顯示數據,簡單來講就是使用spring+dbutils+druid整合寫一個簡單的web項目。

一開始確定要寫好項目結構,這裏我就默認建立好了。

實體類:Employee

 
 
 
xxxxxxxxxx
 
 
 
 
public class Employee {
    private Integer id;
    private String username;
    private BigDecimal salary;
    private Integer gender;
    private  Integer did;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public BigDecimal getSalary() {
        return salary;
    }
    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public Integer getGender() {
        return gender;
    }
    public void setGender(Integer gender) {
        this.gender = gender;
    }
    public Integer getDid() {
        return did;
    }
    public void setDid(Integer did) {
        this.did = did;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", salary=" + salary +
                ", gender=" + gender +
                ", did=" + did +
                '}';
    }
}
 

dao層:EmployeeDao

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeDaoImpl {
    private  QueryRunner queryRunner ;
    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }
    public Employee getById() {
        String sql = "select id,username from employee where id = 1";
        BeanHandler<Employee> handler = new BeanHandler<>(Employee.class);
        Employee employee = null;
        try {
            employee = queryRunner.query(sql,handler);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("query failed...");
        }
        return employee;
    }
}
 

service層:EmployeeService

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl {
    private EmployeeDao dao;
    public void setDao(EmployeeDao dao) {
        this.dao = dao;
    }
    public Employee getById() {
        return dao.getById();
    }
}
 

web層(放置servlet、jsp文件的):EmployeeServlet類 、index.jsp

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeController extends HttpServlet {
@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeService service =  context.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

若是使用這種方法的話,若是servlet多了,就會建立不少個spring容器對象,那麼相應的也會致使被spring容器管理的bean不具有單例的狀況,有多少個spring容器,單例的bean就會有多少個,全部spring容器應該只須要一個就能夠了。

解決方法:

使用監聽器,確保spring容器與web共存亡,且只建立一次。

這裏我就建立一個listener包,用來存放監聽器:

 
 
 
xxxxxxxxxx
 
 
 
 
public class ApplicationContextInstantiateListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
          ServletContext servletContext = servletContextEvent.getServletContext();
        //獲取在web.xml配置的context-param標籤的值,也就是動態的實現調用spring配置文件
        String configFille = servletContext.getInitParameter("configFile");
        ApplicationContext context = new ClassPathXmlApplicationContext(configFille);
        servletContext.setAttribute("context",context);
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}
 

監聽器的配置在WEB-INF/web.xml

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>configFile</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>com.spring.ApplicationContextInstantiateListener</listener-class>
    </listener>
</web-app>
 

用的時候能夠經過實現ApplicationContextAware接口的類來實現調用

 
 
 
xxxxxxxxxx
 
 
 
 
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
    public static ApplicationContext getApplicationContext(){
        return context;
    }
    public static <T> T getBean(String name,Class<T> clz){
        return context.getBean(name,clz);
    }
}
 

當調用一次ApplicationContext對象時,那麼spring就會自動幫這個類注入一個ApplicationContext對象。固然這個類應該被spring管理。

用法:

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        EmployeeService service =  ApplicationContextHolder.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

配置文件:beans.xml

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg ref="dataSource"></constructor-arg>
        <constructor-arg value="true"/>
    </bean>
    <bean id="employeeDao" class="com.dao.impl.EmployeeDaoImpl">
        <property name="queryRunner" ref="queryRunner"/>
    </bean>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDao"/>
    </bean>
    <!---->
    <bean class="com.web.ApplicationContextHolder"/>
</beans>
 

與mybatis+druid整合

MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java對象)映射成數據庫中的記錄。

它所在的依賴配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
 

由於是mybatis與spring的整合,因此須要用到mybatis-spring依賴,

什麼是 MyBatis-Spring?

MyBatis-Spring 會幫助你將 MyBatis 代碼無縫地整合到 Spring 中。它將容許 MyBatis 參與到 Spring 的事務管理之中,建立映射器 mapper 和 SqlSession 並注入到 bean 中,以及將 Mybatis 的異常轉換爲 Spring 的 DataAccessException。最終,能夠作到應用代碼不依賴於 MyBatis,Spring 或 MyBatis-Spring。

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.2</version>
</dependency>
 

不是有了mybatis-spring就不用spring的依賴了,還須要用到spring的依賴:

spring-jdbc

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

spring-context

 
 
 
xxxxxxxxxx
 
 
 
 
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

下面看下怎麼去實現一個web項目

不使用mybatis配置文件與mapper映射文件,使用註解的方式:

跟上面的dbutils+spring+druid代碼類似,惟一不一樣的就是dao層的代碼與applicationContext的配置:

dao層,只須要一個接口,跟mybatis的作法同樣。還記得mybatis是用SqlSession對象的getMapper(Class clz)方法獲取一個代理對象,接口就不須要有具體的實現類。

 
 
 
xxxxxxxxxx
 
 
 
 
public interface EmployeeDao {
    @Select("select id,username from employee where id=1")
    Employee getById();
}
 

由於須要用到SqlSession對象的getMapper()方法獲取一個代理類,因此就開始就須要一個SqlSessionFactory對象。applicationContext裏面就要怎麼代理這個對象。若是咱們本身去實現的話就十分麻煩,慶幸mybatis-spring幫咱們作了這件事。

 
 
 
xxxxxxxxxx
 
 
 
 
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
 

有了SqlSessionFactory對象就能夠建立一個dao層的代理類:

 
 
 
xxxxxxxxxx
 
 
 
 
 <bean id="employeeDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
       <!--注入要建立實現類的mapper接口就能夠獲得class信息-->
        <property name="mapperInterface" value="com.dao.EmployeeDao"/>
       <!--注入了SqlSessionFactory就能夠獲得SqlSession-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
 

最終applicationContext.xml的效果是:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
<!--
這個配置是一個FactoryBean,用來建立SqlSessionFactory
-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--
    這個配置用來給某一個特定的Mapper接口(dao)建立出它的實現類
    sqlSession.getMapper(XxxDao.class);
    若是項目中有多個Mapper接口,就須要多個MapperFactoryBean的配置
-->
    <bean id="employeeDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
       <!--注入要建立實現類的mapper接口就能夠獲得class信息-->
        <property name="mapperInterface" value="com.dao.EmployeeDao"/>
       <!--注入了SqlSessionFactory就能夠獲得SqlSession-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDao"/>
    </bean>
<!--配置此bean是由於此bean實現了ApplicationContextAware
被spring管理後會自動往此bean裏面注入spring容器對象
-->
    <bean class="com.spring.ApplicationContextHolder"/>
</beans>
 

這個方法如今都廢棄不用了,由於若是有多個dao的話,就要有多個MapperFactoryBean配置。

下面說兩種比較常見的整合方式:

第一種(有mybatis-config文件)

保留mybatis配置文件與mapper映射文件。

mybatis配置文件:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <mappers>
        <mapper resource="employeeMapper.xml"/>
    </mappers>
</configuration>
 

若是須要插件就要在mybatis配置文件裏面配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
      <!--這是是否使用分頁插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="supportMethodsArguments" value="true"/>
        </plugin>
    </plugins>
    <mappers>
        <mapper resource="employeeMapper.xml"/>
    </mappers>
</configuration>
 

至於mapper文件是用來寫sql語句的:

 
 
 
xxxxxxxxxx
 
 
 
 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dao.EmployeeDao">
    <select id="getById" resultType="com.entity.Employee">
        select id,username from employee where id = 1
    </select>
</mapper>
 

這樣的配置的好處:遵循開閉原則。在dao層這裏配置註解的方式,若是須要修改sql語句那麼就須要修改代碼。

applicationContext配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:mybatis="http://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring
       http://mybatis.org/schema/mybatis-spring.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="password" value="root"></property>
        <property name="username" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <mybatis:scan base-package="com.dao"/>
    <bean id="employeeService" class="com.service.impl.EmployeeServiceImpl" autowire="byType"/>
</beans>
 

這裏sqlSessionFactory裏面是能夠配置mybatis配置文件的,因此咱們能夠給他一個資源名字。而後它就會自動去找這個mybatis配置文件。

上面提到的須要多個MapperFactoryBean配置,咱們能夠用:

<mybatis:scan base-package="com.dao">

若是dao層有多個dao類,它都會代理出來準備你去用它。

那麼問題來了,EmployeeService須要的dao,那麼咱們怎麼知道須要注入哪一個dao類。這裏咱們就可使用自動注入的機制了:

第二方法(沒有mybatis配置文件)

若是咱們想不要mybatis的配置文件,那麼咱們也是能夠作到的,那麼咱們就須要在spring的配置文件裏配置mybatis所須要的配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:mybatis="http://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/com.demo"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:employeeMapper.xml"/>
        <property name="configuration">
            <!--這個是配置sql日誌的-->
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"></property>
            </bean>
        </property>
        <!--配置插件-->
        <property name="plugins">
            <list>
                <!--分頁的插件-->
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>
                            supportMethodsArguments=true
                            resonable=true
                        </value>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <mybatis:scan base-package="com.dao"/>
    <bean id="employeeService" class="com.service.EmployeeService" autowire="byType"/>
</beans>
 

sqlSessionFactoryBean類下有不少屬性,其中就有配置mybatis所須要的配置:

mapperLocations,經過這個屬性能夠告訴spring咱們用的是哪一個映射文件。咱們就能夠把mybatis的配置丟掉了。

mybatis-config文件解析以後mybatis是用Configuration類型來表明(入口對象),因此咱們就能夠配置是否使用各類mybatis的配置。

plugins:配置插件。

 

使用spring-web

對了,說下以前咱們作過一個web項目,用到過sprng容器根據tomcat啓動而建立且只建立一次的知識。

那時是實現了一個接口ApplicationContextAware接口,其實這個功能已經有人幫咱們作了,咱們只須要拿來用就行了。怎麼拿呢,咱們能夠添加一個spring-web的依賴。

 
 
 
xxxxxxxxxx
 
 
 
 
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
 

而後在web.xml配置監聽器:

 
 
 
xxxxxxxxxx
 
 
 
 
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--
    這個上下文參數的配置是給ContextLoaderListener監聽器使用的
    用來告訴監聽器在建立spring容器對象時元數據文件的路徑
    若是不配置,默認就是在WEB-INF目錄下找applicationContext.xml文件
    -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>
 

tomcat一啓動時,它就會加載spring容器。而後把applicationContext對象注入到WebApplicationContextUtil裏,而後咱們就能夠用它:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
        EmployeeService service =  context.getBean("employeeService", EmployeeService.class);
        Employee employee = service.getById();
        req.setAttribute("emp", employee);
        req.getRequestDispatcher("index.jsp").forward(req, resp);
    }
}
 

 

在這裏我寫個與pageHelper整合的web層的代碼:

servlet:

 
 
 
xxxxxxxxxx
 
 
 
 
@WebServlet("/index")
public class EmployeeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/index.jsp").forward(req,resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        int pageNum = Integer.parseInt(req.getParameter("pageNum"));
        int pageSize = Integer.parseInt(req.getParameter("pageSize"));
        EmployeeService service = new EmployeeService();
        List<Employee> emps = service.getEmpsByPageHelper(pageNum, pageSize);
        PageInfo<Employee> pageInfo = new PageInfo<Employee>(emps,3);
        Gson gson = new Gson();
        String json = gson.toJson(pageInfo);
        resp.getWriter().print(json);
    }
}
 

jsp:

 
 
 
xxxxxxxxxx
 
 
 
 
<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2019/10/14
  Time: 9:55
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="${pageContext.request.contextPath}/jquery/jquery-3.4.1.js"></script>
</head>
<body>
<table>
    <thead>
    <tr>
        <th>編號</th>
        <th>姓名</th>
        <th>工資</th>
        <th>性別</th>
        <th>部門編號</th>
    </tr>
    </thead>
    <tbody class="tab-body">
    </tbody>
</table>
<br>
<div id="data">
</div>
<script>
    $.ajax({
        url: "${pageContext.request.contextPath}/index",
        method: "post",
        data: {
            pageNum: 1,
            pageSize: 3
        },
        dataType: "json"
    }).done(function (res) {
        resultReduction(res)
        c()
        //事件
        function c() {
            $(".pageClick").click(function (e) {
                var pageNum = $(this).val()
                $.ajax({
                    url: "${pageContext.request.contextPath}/index",
                    method: "post",
                    data: {
                        pageNum: pageNum,
                        pageSize: 3
                    },
                    dataType: "json"
                }).done(function (res) {
                   resultReduction(res)
                    c()
                })
            })
        }
        
        function resultReduction(res) {
            $(".tab-body").html("")
            $("#data").html("")
            res.list.forEach(function (emp) {
                var sql = "<tr><td>" + emp.id + "</td><td>" + emp.username + "</td><td>" +
                    emp.salary + "</td><td>" + emp.gender + "</td><td>" + emp.did + "</td></tr>";
                $(".tab-body").append(sql);
            })
            var sql = "<button class='pageClick' value ='1'>首頁</button>" +
                "<button class='pageClick' value ='" + res.prePage + "'>上一頁</button>" +
                "<button class='pageClick' value ='" + res.nextPage + "'>下一頁</button>" +
                "<button class='pageClick' value ='" + res.pages + "'>尾頁</button>";
            res.navigatepageNums.forEach(function (num) {
                sql += "<br><button class='pageClick' value ='" + num + "'>"+num+"</button>";
            })
            $("#data").append(sql);
        }
    })
</script>
</body>
</html>
 

 

第四章 aop

aop的理解

在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。

在使用spring的aop以前應該導入一個依賴:

 
 
 
xxxxxxxxxx
 
 
 
 
 <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
 </dependency>
 

aspectjweaver是spring的切入點表達式須要用的包。

案例

下面經過一個案例實現aop的運用,主要的目標是在執行業務層的時候輸出日誌:

業務層EmployeeImpl類的代碼:

 
 
 
xxxxxxxxxx
 
 
 
 
package com;
public class EmployeeServiceImpl {
    public final void insert(){
        System.out.println("開始插入(insert)-----");
    }
    public void update(){
        System.out.println("開始更新(update)-----");
    }
}
 

日誌類的代碼:

 
 
 
xxxxxxxxxx
 
 
 
 
public class LogImpl {
    public void beforeXXX(){
        System.out.println("開始執行*********");
    }
}
 

applicationContext.xml的配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="log" class="com.LogImpl"/>
    <bean id="employeeService" class="com.EmployeeServiceImpl"/>
    <aop:config>
        <!--這是切面類的配置-->
        <aop:aspect id="logAspect" ref="log">
            <!--這是切點表達式的配置-->
            <aop:pointcut id="myPointcut" expression="execution(* com.*.*())"/>
            <!--before的通知,主要用來肯定當前這個切面類的什麼方法在何時切入到
            切點表達式指向的方法裏面.
            -->
            <aop:before method="beforeXXX" pointcut-ref="myPointcut" />
        </aop:aspect>
    </aop:config>
</beans>
 

而後就可使用:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeServiceImpl service = context.getBean("employeeService", EmployeeServiceImpl.class);
        service.insert();
        service.update();
    }
}
 

輸出:

 
 
 
xxxxxxxxxx
 
 
 
 
開始執行*********
開始插入(insert)-----
開始執行*********
開始更新(update)-----
 

 

案例講解

Aspect:切面 pointcut:切點

aop的幾個術語:
 
 
 
xxxxxxxxxx
 
 
 
 
1.切面(aspect):案例中指的就是LogImpl這個類
2.切點(pointcut):它也稱爲切點表達式,目的是描述符合條件的方法
3.目標(target):就是案例中的EmployeeServiceImpl對象
4.鏈接點(join point):就是目標對象中的insert,update方法
5.通知(advice):就是切面類中的beforeXXX這個方法
6.aop代理(aopProxy):spring aop的實現就是靠代理來作到的,默認利用jdk代理和cglib代理  來實現
7.織入(weaving):是個動詞,表示把切面類的通知與目標對象鏈接點糅合在一塊兒的過程就叫織入
 
通知類型

上面說到before是一種通知類型,那麼相對應的還有其餘通知類型:

 
 
 
xxxxxxxxxx
 
 
 
 
1.before:前置通知,在鏈接點方法以前執行,並且不能控制鏈接點是否執行
2.after:後置通知也叫最終通知,意思就是鏈接點方法只要執行(無論是否有錯誤),它都會獲得執行。它是在目標方法執行完執行。
3.after-return:返回通知,鏈接點正常執行(不報錯)時纔會執行這個通知.
4.throwing:異常通知:鏈接點方法拋出異常時纔會獲得執行.這個通知不能處理異常只能獲得異常信息.異常通知若是想把目標方法拋出的異常傳遞給通知方法只須要在異常通知的throwing屬性設置的值等於通知方法的參數名就能夠.
當異常通知方法沒有異常類型做爲參數時,潛臺詞就是目標方法拋出任何異常,通知都會獲得執行
當異常通知方法"有"異常類型做爲參數是,潛臺詞是隻有目標方法拋出的異常是參數指定類型的異常或是子類型時,此通知方法纔會獲得執行
5.around通知:環繞通知,環繞通知是最強的通知類型,它能夠徹底取代上面的4種
也能夠進行異常的捕獲處理,也能夠組織目標方法執行
 

這五種是比較常見的通知類型,其實大部分在項目裏比較經常使用的就只有環繞通知。

獲取目標方法的參數

通常來講,目標方法是有一些是有參數的,若是須要獲取這些參數的值那麼應該怎麼作呢?

咱們先來講說環繞通知怎麼獲取目標方法參數:

好比:

主要實現功能:有個類,方法裏面有參數,而後在日誌類裏獲取其參數。

EmployeeServiceImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl {
    public void update(){
        System.out.println("update*****");
    }
    public void insert(){
        System.out.println("insert***");
    }
    public int add(int a,int b){
        return a+b;
    }
}
 

這個方法有返回值,也有參數。

而後日誌類:

 
 
 
xxxxxxxxxx
 
 
 
 
public class LogImpl {
    public Object aroundParam(ProceedingJoinPoint joinPoint){
        System.out.println("before");
        //獲取參數的集合
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("arg = " + arg);
        }
        Object result = null;
        try {
            //獲取返回值
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("throw......");
        }
        System.out.println("after");
        return result;
    }
}
 

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="log" class="com.demo.LogImpl"/>
    <bean id="employeeService" class="com.demo.EmployeeServiceImpl"/>
    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.demo.*.*(..))"/>
        <aop:aspect id="logAspect" ref="log">
          <aop:around method="aroundParam" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
 

使用:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeServiceImpl employeeService = context.getBean("employeeService", EmployeeServiceImpl.class);
        employeeService.add(5,6);
    }
}
 

結論:若是是環繞通知,那麼就能夠經過ProceedingJoinpoint裏面的getArgs()來獲取全部參數。同時若是有返回值的話,能夠定義一個變量存儲,而後result = joinPoint.proceed();。

若是是其餘方法想要獲取目標方法的參數:

 
 
 
xxxxxxxxxx
 
 
 
 
  1. 切點表達式中的args與execution都叫切點指示器(PointCut Designer)
        2.args是用來表示查找有多少個參數,args(x),表示找到只有一個參數
        3.args裏面的名字至關於一個變量名,在調用目標方法時,依據調用目標方法
        傳遞的數據,給這個變量名賦值
        4.通知執行的時候,須要參數,那麼這些參數的值就經過從變量裏面找,默認就是找同名的
        因此在不設置arg-names的時候,就會找args()表達式裏面與通知方法同名的數據
        5.可是4步的作法會致使切點表達式與通知方法的形參名耦合一塊兒,因此spring提供了一個靈活的機制
        就是在配置通知時,利用arg-names指定"別名"
        6.args-names指定了別名後就以別名爲準,那麼就會致使args表達式裏面就須要與args-names對應起來
     總而言之: 想讓通知方法獲取到傳遞給目標方法的參數須要這麼作:
     1. args表達式裏面的名字與通知方法的名字與個數匹配便可,此時不須要配置通知的arg-names
     配置代碼以下:
      <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..)) and args(m,n)"/>
      <aop:before method="before2" pointcut-ref="myPointcut1" />
     2. args表達式裏面的名字任意,此時就須要在通知方法的arg-names配置得與args表達式名字同樣
     此時這二者配置的名字不須要與通知方法的形參名同樣.
     <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..)) and args(x1,y1)"/>
      <aop:before method="before2" pointcut-ref="myPointcut1" arg-names="x1,y1"/>
      3.因爲全部的通知方法均可以添加JoinPoint類型做爲第一個參數,而這個類型
      是能夠很方便的獲得參數,你也能夠採用這種方式,這樣就不須要上面的2種方法來達成一樣的目的
 

配置文件:

 
 
 
xxxxxxxxxx
 
 
 
 
 <aop:config>
        <aop:pointcut id="myPointcut1" expression="execution(* com.EmployeeServiceImpl.*(..)) and args(x1,y1)"/>
        <aop:aspect ref="paramAspect" order="3">
            <aop:before method="before2" pointcut-ref="myPointcut1" arg-names="x1,y1"/>
            <!-- <aop:around method="aroundParam" pointcut-ref="myPointcut1"/>-->
        </aop:aspect>
    </aop:config>
 

aop排序

 
 
 
xxxxxxxxxx
 
 
 
 
在同一個切面中,配置的同類型通知,其順序官方沒有明確的說明就不能認爲以配置的順序爲準,可是若是是在不一樣的切面中配置,能夠經過order屬性來準確的控制其執行順序數字小的先執行.
 

好比:

 
 
 
xxxxxxxxxx
 
 
 
 
<aop:config>
    <aop:pointcut id="myPointcut1" expression="execution(* com.service.EmployeeServiceImpl.*(..))"/>
    
    <aop:aspect ref="logImpl" order="3">
        <aop:around method="aroundAdvice" pointcut-ref="myPointcut1"/>
    </aop:aspect>
    <aop:aspect ref="logImpl" order="4">
        <aop:around method="aroundAdvice2" pointcut-ref="myPointcut1"/>
    </aop:aspect>
</aop:config>
 

上面這個案例確定是order="3"先執行,

 

切點表達式

spring aop只針對方法進行aop代理,不想aspectj聯盟搞的aop實現 這個aop聯盟的實現功能比spring aop要強大,好比能夠針對字段進行切面編程.

 

1.切點指示器(PCD:PointCutDesigner):指示器能夠理解爲一種描述找到方法的方式.spring支持的切點指示器有以下幾個:

 
 
 
xxxxxxxxxx
 
 
 
 
1.1 execution:用來匹配鏈接點方法的,用的最多的一個指示器 
1.2 within:英文的意思是:在某某以內,通常就是指的是在"某些"類以內 within(com.service.*)就是指com.service包下的全部類的方法進行aop代理 
1.3 this:指的就是動態代理生成的對象,這種指示器通常表示某個動態代理 對象是某個類型,好比this(com.service.EmployeeService),就表示 動態代理對象是EmployeeService的實現類
1.4 target:指的是被代理的對象.指定的是"某個"特定的目標對象 
1.5 args:此指示器是在方法的參數層面來描述,好比args(integer,String)就表示 全部有2個參數,而且類型分別是integer,string的方法 
1.6 @target:就表示目標類型上有特定註解修飾的目標對象 @target(com.MyFirstAnnotation),就會找全部被spring 管理的bean上面有MyFirstAnnotation註解的目標類 
1.7 @args:與arg相似,只不過是代表參數上有特定註解的 
1.8 @Within:與within相似,只不過是代表類上有特定的註解修飾 
1.9 @annotation:指的是鏈接點方法上有特定註解 
1.10 bean:這個指示器不是aop聯盟的標準,是spring本身提供的 指示特定bean的名字的指示器
 

二、 指示器的邏輯運算符

而且:and(&&)

或者:or(||)

非:not(!)

 

三、 execution指示器不一樣的指示器,表達式的寫法的模式多是不同的額, 它的編寫格式: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 上面格式的含義以下:

 
 
 
xxxxxxxxxx
 
 
 
 
 3.1 ?:表示有或者沒有  ,沒有問號就表示必須寫 
 3.2 modifiers-pattern:修飾符模式
 ret-type-pattern:返回類型模式      
 declaring-type-pattern:聲明類型模式     
 name-pattern:指的就是方法的名字模式    
 param-pattern:指的就是方法參數的模式      
 throws-pattern:指的是方法拋出的異常的模式  
 
        
 其中三個模式必不可少:返回類型,方法名,方法參數   
 

通配符: *:表示任意名字 ..:任意段的任意字符

常見的例子:

 
 
 
xxxxxxxxxx
 
 
 
 
1.  execution(public * *(..)):找到全部的公有方法
2.  execution(* get*(..)):找到以get開頭的方法
3.  public * com.service.emp.EmployeeService+.*(..))
+ 表示接口的全部方法以及此接口實現類本身的方法都會被aop代理
4. public * com.service..*.*(..)) 表示com.service包以及子孫包下
5. within(  com.service..*) 表示com.service包以及子孫包下的全部類,寫法與execution指示器是不一樣的,不須要寫返回類型
6. target(  com.service.emp.impl.EmployeeServiceImpl) 給EmployeeServiceImpl進行aop代理,不能使用通配符.表達式中指定父類型或者接口時是能夠的.
7. this(com.service.emp.impl.EmployeeServiceImpl):給EmployeeServiceImpl類進行aop代理.寫法與target相似.意思是:若是能代理成功,那麼生成的代理類是表達式裏面設定的類型的實例
8. bean(emp)表示給emp這個bean進行aop代理
 

proxy-target-class的用法

若是有個接口叫EmployeeService,其有個實現類EmployeeServiceImpl,若是在aop那裏代理的是實現類。而後獲取這個類的時候用的是接口來接受的,這樣是依賴倒轉設計原則裏面所說的。可是若是你在調用其方法是錯誤的。

緣由是:默認狀況下,EmployeeServiceImpl被aop代理的時候生成的是一個代理類。因此代理類是不能夠向上轉型成爲EmployeeService的。若是你想要生成EmployeeServiceImpl的子類的話,就要設置:

 
 
 
xxxxxxxxxx
 
 
 
 
<aop:config proxy-target-class="true">
...
</aop:config>
 

這樣設置就是叫aop代理的時候使用jdk的方式代理,生成的是EmployeeServiceImpl的子類。然而這個子類是能夠向上轉型成爲EmployeeService的。

 

aop的通知器(advisor)

Advice:通知,表明一種注入方式,好比前置,後置。

Advisor:通知器或者統治者.有某個或某些特定通知類型的切面類

特定通知是靠此類實現某些接口來表示的.

spring有以下接口來代表不一樣的通知類型:

 
 
 
xxxxxxxxxx
 
 
 
 
MethodBeforeAdvice:前置通知
AfterReturningAdvice:返回通知
ThrowsAdvice:異常通知
MethodInterceptor:環繞通知
注意:沒有最終通知(after通知)
 

實現這些接口就是告訴spring,我這個實現類裏的哪一個方法是什麼樣的通知。

這四種接口裏,除了異常通知接口是空接口外,其它都是有方法的。

可是咱們若是實現了異常接口,那麼咱們就須要本身寫一個方法,否則是會保錯的。

異常接口的方法的簽名必須是:

 
 
 
xxxxxxxxxx
 
 
 
 
1.返回類型是void
2.方法名是afterThrowing
3.方法的參數能夠是
    3.1 Method method, Object[] args, Object target
    3.2 或者Method method, Object[] args, Object target,異常類
     
 

可是,我試了下,可能第三個方法參數不能爲第一個。

下面咱們經過一個案例瞭解一下:

業務層:EmployeeServiceImpl

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public int add(Integer x, Integer y) {
       // throw new RuntimeException("failed----"); //異常通知測試
        return x + y;
    }
}
 

通知器類:MyAdvisor

 
 
 
xxxxxxxxxx
 
 
 
 
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyAdvisor implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor, ThrowsAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before----");
    }
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----debug: returnValue = " + returnValue);
    }
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //這行代碼至關於改變了傳遞給目標對象方法的參數值.
        invocation.getArguments()[0] = (Integer) invocation.getArguments()[0] + 100;
        System.out.println("around before");
        Object result = invocation.proceed();
        System.out.println("around after");
        return result;
    }
    //異常通知方法
    public void afterThrowing(Method method, Object[] args, Object target, RuntimeException re) throws Throwable {
        System.out.println("throw----" + re.getMessage());
    }
}
 

由於咱們告訴了spring,我這些方法是什麼方法。因此就不須要在applicationContext.xml裏面在聲明我調用的是什麼方法了,<aop:before />。咱們只須要告訴spring,我用的是哪一個通知器就行,固然通知器也是須要被spring所代理。

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--通知器被spring管理-->
    <bean id="myAdvisor" class="com.advisor.MyAdvisor"/>
    <bean id="emp" class="com.dao.EmployeeDaoImpl"/>
    <aop:config>
        <!--配置切點表達式-->
        <aop:pointcut id="myPointcut"
                      expression="execution(* com.service..*.*(..))"/>
        <!--告訴spring。我用的是哪一個通知器-->
        <aop:advisor advice-ref="myAdvisor"
                     pointcut-ref="myPointcut"/>
    </aop:config>
</beans>
 

這樣當你用的方法符合切點表達式的時候,通知器裏面的方法只要符合通知類型且須要通知,那麼都會執行。

Main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmployeeService employeeService = context.getBean("emp", EmployeeService.class);
        int result = employeeService.add(5, 6);
        System.out.println(result);
    }
}
 

aop代理實現通知器

這個我也不怎麼懂,原理是什麼。可是仍是寫個案例吧。其主要功能就是代理目標對象,而後設置下,就能作到aop代理這個目標方法。

配置文件:applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        關於這方面的詳細能夠在官方文檔中搜索ProxyFactoryBean去了解
        主要在官方文檔的spring aop api這一節中
    -->
    <bean id="myAdvisor" class="com.advisor.MyAdvisor"/>
    <bean id="emp" class="com.dao.EmployeeDaoImpl"/>
    <bean id="empFactory" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="emp"></property>
        <property name="interfaces">
            <list>
                <value>com.service.EmployeeService</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myAdvisor</value>
            </list>
        </property>
    </bean>
</beans>
 

main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_factoryBean.xml");
        EmployeeService employeeService = context.getBean("empFactory", EmployeeService.class);
        int result = employeeService.add(5, 6);
        System.out.println(result);
    }
}
 

有興趣去官網看吧。

 

第五章 spring事務管理器

事務管理

一個數據庫事務是一個被視爲單一的工做單元的操做序列。這些操做應該要麼完整地執行,要麼徹底不執行。事務管理是一個重要組成部分,RDBMS 面向企業應用程序,以確保數據完整性和一致性。事務的概念能夠描述爲具備如下四個關鍵屬性說成是 ACID

  • 原子性:事務應該看成一個單獨單元的操做,這意味着整個序列操做要麼是成功,要麼是失敗的。
  • 一致性:這表示數據庫的引用完整性的一致性,表中惟一的主鍵等。
  • 隔離性:可能同時處理不少有相同的數據集的事務,每一個事務應該與其餘事務隔離,以防止數據損壞。
  • 持久性:一個事務一旦完成所有操做後,這個事務的結果必須是永久性的,不能因系統故障而從數據庫中刪除。

一個真正的 RDBMS 數據庫系統將爲每一個事務保證全部的四個屬性。使用 SQL 發佈到數據庫中的事務的簡單視圖以下:

  • 使用 begin transaction 命令開始事務。
  • 使用 SQL 查詢語句執行各類刪除、更新或插入操做。
  • 若是全部的操做都成功,則執行提交操做,不然回滾全部操做。

Spring 框架在不一樣的底層事務管理 APIs 的頂部提供了一個抽象層。Spring 的事務支持旨在經過添加事務能力到 POJOs 來提供給 EJB 事務一個選擇方案。Spring 支持編程式和聲明式事務管理。EJBs 須要一個應用程序服務器,但 Spring 事務管理能夠在不須要應用程序服務器的狀況下實現。

局部事物 vs. 全局事務

局部事務是特定於一個單一的事務資源,如一個 JDBC 鏈接,而全局事務能夠跨多個事務資源事務,如在一個分佈式系統中的事務。

局部事務管理在一個集中的計算環境中是有用的,該計算環境中應用程序組件和資源位於一個單位點,而事務管理只涉及到一個運行在一個單一機器中的本地數據管理器。局部事務更容易實現。

全局事務管理須要在分佈式計算環境中,全部的資源都分佈在多個系統中。在這種狀況下事務管理須要同時在局部和全局範圍內進行。分佈式或全局事務跨多個系統執行,它的執行須要全局事務管理系統和全部相關係統的局部數據管理人員之間的協調。

 

spring事務寫法要點:

 
 
 
xxxxxxxxxx
 
 
 
 
1.配置一個DataSource
2.配置事務管理器,用上dataSource
3.配置一個事務通知tx:advice   
3.1 對某些方法進行事務相關屬性配置,好比超時(timeout),事務隔離級別 ,事務傳播方面的配置,只讀配置    3.2 必定要記得關聯事務管理器,默認名字是transactionManager
4.配置aopconfig,肯定對哪些業務類的方法進行事務處理
***事務的處理是針對業務類,不是dao***
 

事務管理器:

主要用來管理物理鏈接,事務提交,回滾等功能有了事務配置,對咱們的dao裏面用的鏈接相關的信息就有了要求:

1.由於這個事務管理器是針對DataSource,因此咱們的dao必須用"同一個"dataSource

2.DataSource獲取方法必須是Spring提供的方式

事務管理器案例

下面經過案例來講明spring事務管理器

主要實現功能:兩個表刪除數據,實現事務功能——若是一個表的列刪除失敗有異常,則另外一個表的列也不會刪除

這就是回滾機制。

dao層:由於有兩張表,因此有兩個dao類

DeptDaoImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DeptDaoImpl extends BaseDao {
    public void deleteById(int id) throws Exception {
        String sql = "delete from dept where id =?";
        //int i = 5/0; //這個註釋是會拋異常的
        //jdbcTemplate是spring-jdbc裏的方法。其主要功能是用來操做數據庫的
        jdbcTemplate.update(sql, id);
    }
}
 

EmployeeDaoImpl:

 
 
 
xxxxxxxxxx
 
 
 
 
public class EmployeeDaoImpl extends BaseDao {
    public void deleteByDeptId(int id){
        String sql = "delete from employee where deptid =?";
        jdbcTemplate.update(sql, id);
    }
}
 

上面兩個類都繼承了父類,該父類是個抽象類。主要是兩個子類的重複代碼的封裝:

 
 
 
xxxxxxxxxx
 
 
 
 
public abstract class BaseDao {
    protected JdbcTemplate jdbcTemplate;
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
 

而後是業務層的代碼:

 
 
 
xxxxxxxxxx
 
 
 
 
public class DeptServiceImpl {
    private DeptDaoImpl deptDao;
    private EmployeeDaoImpl employeeDao;
    public void setDeptDao(DeptDaoImpl deptDao) {
        this.deptDao = deptDao;
    }
    public void setEmployeeDao(EmployeeDaoImpl employeeDao) {
        this.employeeDao = employeeDao;
    }
    public void deleteWholeDeptById(int id) throws Exception {
        employeeDao.deleteByDeptId(id);
        deptDao.deleteById(id);
    }
}
 

主要的applicationContext.xml配置:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
        <property name="password" value="root"/>
        <property name="username" value="root"/>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="baseDao" abstract="true">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="deptDao" class="com.dao.DeptDaoImpl" parent="baseDao">
    </bean>
    <bean id="empDao" class="com.dao.EmployeeDaoImpl" parent="baseDao">
    </bean>
    <bean id="deptService" class="com.service.impl.DeptServiceImpl">
        <property name="employeeDao" ref="empDao"/>
        <property name="deptDao" ref="deptDao"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--
        tx:advice的事務管理器設置:
        若是你配置的事務管理器的名字就叫作transactionManager,
        那麼transaction-manager就能夠不用設置
    -->
    <tx:advice id="txAdvisor" transaction-manager="txManager">
        <tx:attributes>
            <!--查詢操做-->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED" rollback-for="com.dao.MyCheckEx"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="serivceTx" expression="execution(* com.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvisor" pointcut-ref="serivceTx"/>
    </aop:config>
</beans>
 

能夠配置多個<tx:method />,通常的配置,查詢操做用只讀事務,會優化性能它也支持通配符*默認狀況下,spring會對運行時異常產生回滾,檢查異常不回滾若是想針對檢查異常也回滾,那麼就須要配置rollback-for

mybatis這種持久層框架,其全部數據庫操做的異常都是運行時異常因此method的rollback-for保留默認便可,不須要額外配置

注意:tx:attributes是必須配置,若是不配置,整個就運行在非事務環境下

 
 
 
xxxxxxxxxx
 
 
 
 
 ///////////////////////******************源碼解析*******************/////////////////
        tx:attributes是必須配置的,不然在xml這種配置狀況下就沒有事務相關的配置信息,
        spring並不提供事務相關屬性的默認值,因此會致使方法運行在非事務環境下
        研究方法:點擊tx:advice跳轉到xsd文件,會看到TransactionInterceptor,在此類的invoke方法中能夠看到
        調用了invokeWithinTransaction方法,在此方法中能夠看到獲取TransactionAttribute信息時,考慮到了方法.若是沒有
        相關的事務信息配置,就不會建立調用事務管理器的getTransaction方法,就不會有事務.
        1.有配置tx:attributes時會用到NameMatchTransactionAttributeSource,沒有配置時會用到AbstractFallbackTransactionAttributeSource
        2.方法調用流:invoke()->invokeWithinTransaction()->createTransactionIfNecessary()
        ///////////////////////******************源碼解析*******************/////////////////
        
 

Main:

 
 
 
xxxxxxxxxx
 
 
 
 
public class Main {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        DeptServiceImpl deptService = context.getBean("deptService", DeptServiceImpl.class);
        try {
            deptService.deleteWholeDeptById(22);
        } catch (Throwable throwable) {
            System.out.println("-----debug: throwable.getClass().getName() = " + throwable.getClass().getName());
        }
    }
}
 

事務與mybatis整合

注意:整合時,事務管理器用的dataSource必須與sqlSessionFactory同樣(與mybatis整合時);

其實這個整合和mybatis與spring的整合差很少。只是添加了事務而已。可是他們不會相互影響。

applicationContext.xml:

 
 
 
xxxxxxxxxx
 
 
 
 
<?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:mybatis="http://mybatis.org/schema/mybatis-spring"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations" value="classpath*:*Mapper.xml"/>
    </bean>
    <mybatis:scan base-package="com.dao" />
    <bean id="manager" class="com.service.XXXManager" autowire="byType"></bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:advice id="txAdvisor">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="servicePointcut"
                      expression="execution(* com.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvisor" pointcut-ref="servicePointcut"/>
    </aop:config>
</beans>
 

其餘都是沒什麼變化的,具體的能夠去看上面的mybatis與spring的整合。

相關文章
相關標籤/搜索