1. 程式人生 > 實用技巧 >spring-ioc筆記

spring-ioc筆記

1、Spring-IOC

程式的耦合:

  • 耦合:程式間的依賴關係。包括:

    • 類之間的依賴

    • 方法間的依賴

  • 解耦:降低程式間的依賴關係

  • 實際開發:

    • 應該做到,編譯期不依賴,執行時才依賴

  • 解耦思路:

    • 第一步:使用反射來建立物件,而避免使用new關鍵字。

    • 第二步:通過讀取配置檔案來獲取要建立的物件全限定類名。

1.1、bean工廠

自己建立一個簡單的beanfactory(bean工廠)

  1. BeanFactory

    /**
    * 一個建立Bean物件的工廠
    *
    * Bean:在計算機英語中,有可重用元件的含義。
    * JavaBean:用java語言編寫的可重用元件。
    * javvabean >> 實體類(javabean遠遠大於實體類)
    * 他就是建立我們的service和dao物件的。
    *
    * 第一個:需要一個配置檔案來配置我們的service和dao
    * 配置的內容:唯一標識=全限定類名(key=value)
    * 第二個:通過讀取配置檔案中配置內容,反射建立物件
    *
    * 我的配置檔案可以是xml也可以是properties
    */
    public class BeanFactory {
    //定義一個Properties物件
    private static Properties props;

    //使用靜態程式碼塊位properties物件賦值
    static{
    try {
    //例項化物件
    props = new Properties();
    //獲取properties檔案的流物件
    //InputStream in = new FileInputStream(""); //這是要輸入具體地址,一般要採用相對地址,也就是下面這個
    InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
    props.load(in);
    }catch (Exception e){
    System.out.println("匯入錯誤");
    }
    }

    public static Object getBean(String beanName){
    Object bean = null;
    try {
    String beanPath = props.getProperty(beanName);
    bean = Class.forName(beanPath).newInstance(); //每次都會呼叫預設建構函式建立物件
    }catch (Exception e){
    System.out.println("初始化錯誤");
    }
    return bean;
    }

    }

  2. UserMapper介面(相當與UserDao)

    public interface UserMapper {
    void queryUser();
    }

    UserMapperImpl(相當與UserDaoImpl)

    public class UserMapperImpl implements UserMapper {
    public void queryUser() {
    System.out.println("查詢");
    }
    }

  3. UserService介面

    public interface UserService {
    void queryUser();
    }

    UserServiceImpl

    public class UserServiceImpl implements UserService {
    //private UserMapper userMapper = new UserMapperImpl();
    private UserMapper userMapper = (UserMapper) BeanFactory.getBean("UserMapper");
    public void queryUser() {
    userMapper.queryUser();
    }
    }

  4. Client(測試類)

    public class Client {

    public static void main(String[] args) {
    //UserService userService = new UserServiceImpl();
    UserService userService = (UserService)BeanFactory.getBean("UserService");
    userService.queryUser();
    }

    }

  5. bean.properties

    UserService=com.gzk.service.UserServiceImpl
    UserMapper=com.gzk.mapper.UserMapperImpl

  6. 執行Client

1.2、單例,原型

我們的1.1中的bean工廠是單例的還是原型的呢?

有程式碼來解釋:

public class UserServiceImpl implements UserService {
private UserMapper userMapper = (UserMapper) BeanFactory.getBean("UserMapper");
private int i = 1;
public void queryUser() {
userMapper.queryUser();
System.out.println(i++);
}
}

public class Client {

public static void main(String[] args) {
for(int i=0;i<5;i++) {
UserService userService = (UserService) BeanFactory.getBean("UserService");
System.out.println(userService);
userService.queryUser();
}
}
}

由程式碼測試可得,我們這個工廠是原型的

原型(也就是說,每呼叫一次就new一個物件【一般用在存線上程安全問題的情況下使用】)

單例(也就是說,無論呼叫多少次都只有一個例項物件【只被建立你一次,物件只會初始化一側】)

物件被建立多次,執行效率就會比較低,所以我們一般情況採用單例

所以我們要將這個工廠改造成單例的

/**
* 一個建立Bean物件的工廠
*
* Bean:在計算機英語中,有可重用元件的含義。
* JavaBean:用java語言編寫的可重用元件。
* javvabean >> 實體類(javabean遠遠大於實體類)
* 他就是建立我們的service和dao物件的。
*
* 第一個:需要一個配置檔案來配置我們的service和dao
* 配置的內容:唯一標識=全限定類名(key=value)
* 第二個:通過讀取配置檔案中配置內容,反射建立物件
*
* 我的配置檔案可以是xml也可以是properties
*/
public class BeanFactory {
//定義一個Properties物件
private static Properties props;
//建立一個HashMap容器
private static Map<String ,Object> beans;

//使用靜態程式碼塊位properties物件賦值
static{
try {
//例項化物件
props = new Properties();
//獲取properties檔案的流物件
//InputStream in = new FileInputStream(""); //這是要輸入具體地址,一般要採用相對地址,也就是下面這個
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//例項化容器
beans = new HashMap<String, Object>();
//取出配置檔案中所有的key
Enumeration keys = props.keys();
//遍歷列舉
while(keys.hasMoreElements()){
//取出每一個key
String key = keys.nextElement().toString();
//根據key獲取value
String beanPath = props.getProperty(key);
//通過反射建立物件
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key ,value);
}
}catch (Exception e){
System.out.println("錯誤");
}
}

public static Object getBean(String beanName){
return beans.get(beanName);
}

}

1.3、IOC控制反轉

傳統方式

IOC方式

控制反轉(Inversion of Control,英文縮寫IOC)把建立物件的權利交給框架,是框架的重要特徵,並非面向物件程式設計的專業術語,它包括依賴注入(Dependency Injection,簡稱DI)和依賴查詢(Dependency Lookup)。

除main函式之外,其他程式碼都在【1.1、bean工廠】中。

Client

public class Client {

/**
* 獲取spring的IOC核心容器,並根據id獲取物件
*
* ApplicationContext的三個常用實現類
* ClassPathXmlApplicationContext --> 它可以載入路徑下的配置檔案,要求配置檔案必須在類路徑下。不在的話,載入不了【相對路徑】
* FileSystem.XmlApplicationContext --> 它可以載入磁碟任意路徑下的配置檔案(必須要有訪問許可權)【絕對路徑】
* AnnotationConfigApplicationContext --> 它是用於讀取註解建立容器的
*
* 核心容器的兩個介面引發的問題
* ApplicationContext:
* 它在構建核心容器時,建立物件採取的策略時採用立即載入的方式。也就是說,只要一讀取完配置檔案馬上就建立配置檔案中配置的物件。(單例時)
* 它在構建核心容器時,建立物件採取的策略時採用延遲載入的方式。也就是說,什麼時候根據id獲取物件了,什麼時候才真正的建立物件。(原型時)
* BeanFactory:
* 它在構建核心容器時,建立物件採取的策略時採用延遲載入的方式。也就是說,什麼時候根據id獲取物件了,什麼時候才真正的建立物件。
* @param args
*/

public static void main(String[] args) {
//1.獲取核心容器物件
//ApplicationContext context = new FileSystemXmlApplicationContext("D:\\idea-workspace\\my-site\\spring02-study\\spring-01\\src\\main\\resources\\bean.xml");
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//2.根據id查詢對應的bean例項
//UserService userService = (UserService) context.getBean("userService");
UserService userService = context.getBean("userService", UserService.class);
userService.queryUser();
}

}

<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userMapper" class="com.gzk.mapper.UserMapperImpl"/>
</beans>

1.4、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
https://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 把物件的建立交給spring來管理 -->
<!--
spring對bean的管理細節
1.建立bean的三種方式
2.bean物件的作用範圍
3.bean物件的生命週期
-->
<!-- 建立bean的三種方式 -->
<!--
1.使用預設建構函式建立
在spring配置檔案中使用bean標籤,配以id和class屬性之後,且沒有其他屬性和標籤時,
採用的時預設建構函式(無參構造)建立bean物件,若沒有無參建構函式,則物件無法建立
<bean id="userMapper" class="com.gzk.mapper.UserMapperImpl"/>
-->

<!--
2.使用普通工廠中的方法建立物件(使用某個類中的方法建立物件,並存入spring容器)
<bean id="instanceFactory" class="com.gzk.factory.InstanceFactory"/>
<bean id="name" factory-bean="instanceFactory" factory-method="getAccountService"/>
-->


<!--
3.使用工廠中的靜態方法建立物件(使用某個類中的靜態方法建立物件,並存入spring容器
<bean id="name" class="com.gzk.factory.StaticFactory" factory-method="getAccountService"/>
-->


</beans>

1.5、bean物件的作用範圍

bean標籤的scopc屬性:

  • 作用:用於指定bean的作用範圍

  • 取值:

    • singleton:單例的(預設值)

    • prototype:原型的

    • request:作用於web應用的請求範圍

    • session:作用於web應用的會話範圍

    • global-session:作用於叢集環境的會話範圍【當有多臺伺服器時(全域性會話範圍)】,當不是叢集環境時,它就是session

<bean id="userService" class="com.gzk.service.UserServiceImpl" scope="prototype"/>

bean物件的生命週期

  • 單例物件:

    • 出生:當容器建立時物件出生

    • 活著:只要容器還在,物件一直活著

    • 死亡:容器銷燬,物件消亡

    • 總結:單例物件的生命週期和容器相同

  • 原型物件(多例):

    • 出生:當我們使用物件時spring框架為我們建立

    • 活著:物件只要是在使用過程中就一直活著

    • 死亡:當物件長時間不用,且沒有別的物件引用時,由Java垃圾回收器回收

public class AccountServiceImpl implements AccountService {

public AccountServiceImpl(){
System.out.println("AccountServiceImpl的無參建構函式");
}

public void init(){
System.out.println("物件初始化!");
}
public void getAccountService(){
System.out.println("方法執行中");
}
public void destroy(){
System.out.println("物件銷燬!");
}
}
public interface AccountService {
void init();
void getAccountService();
void destroy();
}
public class Client {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = null;
for(int i=0;i<5;i++) {
accountService = context.getBean("accountService", AccountService.class);
System.out.println(accountService);
accountService.getAccountService();
System.out.println();
}
}

<!--單例-->
<bean id="accountService" class="com.gzk.service.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"/>
<!--原型-->
<!--<bean id="accountService" class="com.gzk.service.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"/>-->

單例:

原型:

1.6、spring的依賴注入

依賴注入:Dependency Injection

IOC的作用:

  • 降低程式間的耦合(依賴關係)

依賴關係的管理:

  • 以後都交給spring來維護

在當前類需要用到其他類的物件,由spring為我們提供,我們只需要在配置檔案中說明

依賴注入:

  • 能注入的資料:有三類

    • 基本型別和String

    • 其他bean型別(在配置檔案中或者註解配置過的bean)

    • 複雜型別/集合型別

  • 注入的方式:有三種

    • 使用建構函式

    • 使用set方法

    • 使用註解

1.6.1、建構函式注入:

  • 使用的標籤:constructor-arg

  • 標籤出現的位置:bean標籤的內部

  • 標籤中的屬性

    • type:用於指定要注入大的資料的資料型別,該資料型別也是建構函式中某個或某些引數的型別

    • index:用於指定要注入的資料給建構函式中指定索引位置的引數賦值。索引的位置是從0開始

    • name:用於指定給建構函式中指定名稱的引數賦值 =====以上三個用於指定給建構函式中某些引數賦值=====

    • value:用於提供基本型別和String型別的資料

    • ref:用於指定其他的bean型別資料。他指的就是再spring的IOC核心容器中出現過的bean物件

  • 優勢:

    • 在獲取bean物件時,注入資料是必須的操作,否則物件無法建立成功。

<bean id="constructorService" class="com.gzk.service.ConstructorService">
<constructor-arg index="0" value="111"/>
<constructor-arg index="1" value="張三"/>
<constructor-arg index="2" ref="dateTime"/>
</bean>

<bean id="dateTime" class="java.util.Date"/>

1.6.2、set方法注入

  • 使用的標籤:property

  • 標籤出現的位置:bean標籤的內部

  • 標籤中的屬性

    • name:用於指定注入時所呼叫的set方法

    • value:用於提供基本型別和String型別的資料

    • ref:用於指定其他的bean型別資料。他指的就是再spring的IOC核心容器中出現過的bean物件

  • 優勢

    • 建立物件時,沒有明確的限制

  • ss

<bean id="constructorService2" class="com.gzk.service.ConstructorService2">
<property name="id" value="111"/>
<property name="name" value="張三"/>
<property name="dateTime" ref="dateTime"/>
</bean>

<bean id="dateTime" class="java.util.Date"/>

  • 複雜型別注入/集合型別注入

    • 用於給List結構【set<T>,List<T>,String[]】集合注入的標籤:

      • list array set【三者可以混用,無明確規定】

    • 用於給Map結構【Map<K ,V>,Properties】集合注入的標籤:

      • map props【二者可以混用,無明確規定】

public class SetService {
private String name;
private String[] stringList;
private List<String> list;
private Set<String> set;
private Map<String ,Object> map;
private Properties properties;

public void setName(String name) {
this.name = name;
}

public void setStringList(String[] stringList) {
this.stringList = stringList;
}

public void setList(List<String> list) {
this.list = list;
}

public void setSet(Set<String> set) {
this.set = set;
}

public void setMap(Map<String, Object> map) {
this.map = map;
}

public void setProperties(Properties properties) {
this.properties = properties;
}

@Override
public String toString() {
return "SetService{" +
"name='" + name + '\'' +
", stringList=" + Arrays.toString(stringList) +
", list=" + list +
", set=" + set +
", map=" + map +
", properties=" + properties +
'}';
}
}

<bean id="setService" class="com.gzk.service.SetService">
<property name="name" value="張三"/>
<property name="stringList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>

</property>

<property name="list">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>

<property name="set">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>

<property name="map">
<props>
<prop key="1">DDD</prop>
<prop key="2">EEE</prop>
<prop key="3">FFF</prop>
</props>

</property>

<property name="properties">
<map>
<entry key="1" value="DDD"/>
<entry key="2">
<value>EEE</value>
</entry>
</map>
</property>
</bean>

1.6.3、註解注入

  • 用於建立物件的

    • 它們的作用就和在XML配置檔案中編寫一個<bean>標籤實現的功能是一樣的

    • @Component:

      • 作用:用於把當前類物件存入spring容器中

      • 屬性:

        • value:用於指定bean的id。當我們不寫時,預設為當前類名,且首字母小寫

    • @Controller:一般用於表現層

    • @Service:一般用於業務層

    • @Repository:一般用於持久層

    • 以上三個註解的作用和屬性與Component是一摸一樣的

    • 他們三個是spring框架為我們提供明確的三層使用的註解,是我們三層結構更加清晰

    • 在配置註解時,要注意:要在配置檔案中配置掃描的包

    <?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.gzk"/>

    </beans>

    //@Component("userService")
    @Service("userService")
    public class UserServiceImpl implements UserService {
    private UserMapper userMapper = new UserMapperImpl();

    public UserServiceImpl(){
    System.out.println("被建立了");
    }

    public void queryUser() {
    userMapper.queryUser();
    }
    }

  • 用於注入資料的

    • 他們的作用就和在xml配置檔案中的bean標籤中寫一個<property>標籤的作用是一樣的

    • @Autowired

      • 作用:自動按照型別注入。只要容器中有唯一的bean物件型別和注入的變數型別匹配,就可以注入成功

      • 出現位置:

        • 可以是變數上,也可以是方法上

      • 細節:

        • 在使用註解注入是,set方法就不是必須的。

      • Autowired查詢過程:

        • key存放bean物件的唯一ID值,value存放ID的型別。

        • 第一步,在Map容器中查詢value值;(如:UserMapper在下面這個容器有兩個,所以,匹配導兩個;【注意:如果只匹配導一個,那麼就直接取出對應的key值賦值在被註解的引數,不用走第二步了】)

        • 第二步,在Map容器中查詢value對應的key值,(如果有多個value值,則會查出多個key值,那麼key值要與id相互匹配,要不然會報錯【注意:要是隻有一個,ID值與key值可以互不相同】),然後取出對應的key值賦值在被註解的引數。

    @Repository("userMapper")
    public class UserMapperImpl implements UserMapper {
    public void queryUser() {
    System.out.println("查詢");
    }
    }

    @Service("userService")
    public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    public UserServiceImpl(){
    System.out.println("被建立了");
    }

    public void queryUser() {
    userMapper.queryUser();
    }
    }
    • @Qualifier

      • 作用:在按照類中注入的基礎之上再按照名稱注入。他在給類成員注入時不能單獨使用。但是在給方法引數注入時可以

      • 屬性

        • value:用於指定注入bean的id

    • @Resource

      • 作用:直接按照bean的id注入。可以單獨使用

      • 屬性:

        • name:用於指定bean的id

    • 以上三個注入都只能注入其他bean型別的資料,而基本型別和String型別無法使用上述註解實現。另外,集合型別的注入只能通過XML來實現

    • @Value

      • 作用:用於注入基本型別和String型別的資料

      • 屬性:

        • value:用於指定資料的值。他就可以i使用spring中SpEL(也就是sprinng的el表示式)SpEL的寫法:${表示式}

  • 用於改變作用範圍的

    • 他們的作用就和在bean標籤中使用scope屬性實現的功能時一樣的

    • @Scope:

      • 作用:用於指定bean的作用範圍

      • 屬性:

        • value:指定範圍的取值。常用取值:singleton,prototype

@Service("userService")
@Scope("prototype")
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

public UserServiceImpl(){
System.out.println("被建立了");
}

public void queryUser() {
userMapper.queryUser();
}
}

  • 和生命週期相關的

    • 他們的作用就和在bean標籤中使用init-method和destroy-method的作用是一樣的

    • @PreDestroy

      • 作用:用於指定銷燬方法

    • @PostConstruct

      • 作用:用於指定初始化方法

@Repository("userMapper")
public class UserMapperImpl implements UserMapper {
public void queryUser() {
System.out.println("查詢");
}

@PostConstruct
public void init(){
System.out.println("初始化");
}

@PreDestroy
public void destroy(){
System.out.println("銷燬");
}
}

1.7、spring的新註解

  • @Configuration:

    • 作用:指定當前類是一個配置類

    • 細節:

      • 當AnnotationConfigApplicationContext指定某一個配置類時,那個配置類不加@Configuration也可以

      • 或者在SpringConfig配置類中加入@Import註解,指向具體的類時,那個類也可以不加@Configuration註解

      • 以上兩種情況都不成立的話,那個配置類就需要加@Configuration註解,且要被掃描到。

ApplicationContext application = new AnnotationConfigApplicationContext(SpringConfig.class);
  • ttt

  • @ComponentScan

    • 作用:用於通過註解指定spring在建立容器時要掃描的包

    • 屬性:

      • valur:他和basePackages的作用一樣的,都是用於指定建立容器時要掃描的包。

    <context:component-scan base-package="com.gzk"/>
  • @Bean

    • 作用:用於把當前方法的返回值作為bean物件存入spring的ioc容器中

    • 屬性:

      • name:用於指定bean的id。當不寫時,預設值是當前方法的名稱

    • 細節:

      • 當我們使用註解配置方法時,如果方法有引數,spring框架會去容器中查詢有沒有可用的bean物件。

      • 查詢方式和@Autowried註解的作用時一樣的

  • @Import

    • 作用:用於匯入其他的配置類

    • 屬性:

      • value:用於指定其他配置類的位元組碼

    • 當我們使用Import的註解之後,有Import註解的類就父配置類,而匯入的都是子配置類

  • PropertySource

    • 作用:用於指定properties檔案的位置

    • 屬性:

      • value:指定檔案的名稱和路徑。

        • 關鍵字:classpath,表示類路徑下

純註解:

可以將xml配置檔案刪掉,但要自己定義一個config配置檔案

1.建立一個包【config】,包中建立一個主配置類SpringConfig

//在測試時,會將該配置類放入AnnotationConfigApplicationContext(SpringConfig.class),所以@Configuration可有可無
//@Configuration
@ComponentScan(basePackages = "com.gzk")
@Import(JdbcConfig.class)
@PropertySource("classpath:db.properties")
public class SpringConfig {
}

2.建立一個功能子類,專門做特定的事務的:JdbcConfig

//因為在主配置類中註解了@Import(JdbcConfig.class),所以@Configuration可有可無
//又因為在主配置類中註解了@PropertySource("classpath:db.properties"),所以,子類繼承父類,所以可以通過@Value直接取db.properties中的值。
public class JdbcConfig {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${name}")
private String name;
@Value("${password}")
private String password;

//配置queryRunner
@Bean("queryRunner")
@Scope("prototype")
public QueryRunner queryRunner(DataSource dataSource) {
//注入資料來源
return new QueryRunner(dataSource);
}

//配置資料來源
@Bean(name = "dataSource")
public DataSource dataSource() {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
try {
//這是連線資料庫必要的資料
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(name);
dataSource.setPassword(password);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
return dataSource;
}

}

3.測試類

public class UserTest {
// private ApplicationContext application = new ClassPathXmlApplicationContext("bean.xml");
private ApplicationContext application = new AnnotationConfigApplicationContext(SpringConfig.class);
private UserService userService = application.getBean("userService" ,UserService.class);

@Test
public void queryUserList() {
List<User> userList = userService.queryUserList();
for (User user : userList){
System.out.println(user);
}
}

@Test
public void queryUser() {
System.out.println(userService.queryUser(1));
}

@Test
public void updateUser() {
User user = new User(5 ,"張三" ,"123" ,"[user:update]");
boolean flag = false;
if(userService.updateUser(user) > 0){
flag = true;
}
System.out.println(flag);
}

@Test
public void insertUser() {
User user = new User(5 ,"張三" ,"123456789" ,"user:add");
boolean flag = false;
if(userService.insertUser(user) > 0){
flag = true;
}
System.out.println(flag);
}

@Test
public void deleteUser() {
boolean flag = false;
if(userService.deleteUser(5) > 0){
flag = true;
}
System.out.println(flag);
}

}