Web基礎之Spring IoC

Spring之IoC

概念

  IoC:Inversion of Control,中文一般翻譯爲「控制反轉」,它還有一個別名叫作依賴注入(Dependency Injection)。但實際上依賴注入控制反轉的一種表達方式(還有一種叫依賴查找)。什麼是控制反轉呢,簡單來講就是原本上層建築依賴下層建築,下載經過依賴注入是下層建築依附於上層建築。具體表現就是經過注入的方式,爲高級類(接口)添加依賴,注入方式能夠爲構造方法、set方法和接口注入(用得少,侵入性高)。java

  而Spring就一種是典型的IoC容器(用來管理bean),而且能夠幫助咱們管理注入,省去許多麻煩(感受有點像JVM幫咱們管理內存同樣)mysql

  推薦看一下《Spring揭祕》這本書,講的很是不錯。面試

快速入門

首先導入IoC相關依賴:
spring


spring-context

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

而後是service和dao層的接口及其實現類:
sql


接口及其實現類

public interface UserDao {
    public void save();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("保存用戶信息");
    }
}   

public interface UserService {
    public void register();
}

public class UserServiceImpl implements UserService {
    @Override
    public void register() {
        System.out.println("註冊");
    }
}

再而後是xml配置:
數組


applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/">
    <!-- 
        定義bean
            id:惟一標識符
            class:bean所對應的類 
    -->
    <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean>
    <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
</beans>

經過spring工廠獲取定義的JavaBean:session

//加載配置文件,獲取spring工廠,從容器中獲取dao和service的實現類
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取service
UserService accountService = (UserService) ac.getBean("userService");
System.out.println(accountService);
//從容器中獲取dao
UserDao userDao = (UserDao) ac.getBean("userDao");
System.out.println(userDao);

放一張被轉爛了的圖:app

工廠結構

  能夠看到BeanFactory是工廠的頂層接口,也就是幫助咱們管理bean的,ApplicationContext是其子接口。固然,ApplicationContext除了具備BeanFactory的全部功能以外,還有國際化支持。統一資源加載策略、容器內時間發佈的特性。同時,二者對於bean的建立時機也不同,BeanFactory在須要的時候(調用getbean方法)時建立,ApplicationContext會在讀取配置以後當即建立。ide

  上面給出了ApplicationContext的使用方法,BeanFactory則不太同樣(XmlBeanFactory在3.1以後已過期):函數

Resource resource=new ClassPathResource("applicationContext.xml");  
BeanFactory factory = new DefaultListableBeanFactory();  
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);  
bdr.loadBeanDefinitions(resource);

ApplicationContext 接口的實現類非的三種實現類:

  • ClassPathXmlApplicationContext:它是從類的根路徑下加載配置文件,通常使用這種(類根路徑爲編譯後class)
  • FileSystemXmlApplicationContext:它是從磁盤路徑上加載配置文件,配置文件能夠在磁盤的任意位置。
  • AnnotationConfigApplicationContext: 註解實現類。

bean標籤

bean標籤的屬性:

  • id:給對象在容器中提供的惟一標識,用於獲取對象
  • class:指定類的全限定類名。用於反射建立對象。默認狀況下調用無參構造函數。
  • scope:指定對象的做用範圍。
    值:
    • singleton :默認值,單例
    • prototype :多例
  • request :WEB 項目中,Spring 建立一個 Bean 的對象,將對象存入到 request 域中.
  • session :WEB 項目中,Spring 建立一個 Bean 的對象,將對象存入到 session 域中.
  • global session :WEB 項目中,應用在 Portlet 環境.若是沒有 Portlet 環境那麼 globalSession 至關於 session.
  • init-method:指定類中的初始化方法名稱(生命週期相關)。
  • destroy-method:指定類中銷燬方法名稱(生命週期相關)。

bean的生命週期:

  • init-method: 配置bean建立時的初始化方法。
  • destory-method:配置bean銷燬時的銷燬方法。

在ApplicationContext中:

週期 單例singleton 多例prototype
對象出生 當應用加載,建立容器時,對象就被建立了。 當使用對象時,建立新的對象實例(getBean被調用)
對象存在 只要容器在,對象一直活着 只要對象在使用中,就一直活着
對象死亡 當應用卸載,銷燬容器時,對象就被銷燬了 當對象長時間不用時,被java的垃圾回收器回收了

建立bean的三種方式:

<!-- 默認無參構造,通常用這種 -->
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean>

工廠方式:

//靜態工廠
public class BeansFacotory1 {
    public static Object getBeans(){
        return new UserServiceImpl();
    }
}
//示例工廠
public class BeansFacotory2 {
    public  Object getBeans(){
        return new UserServiceImpl();
    }
}

配置方式:

<!-- 靜態工廠方法建立對象
     class:工廠類的全限定名
     factory-method:工廠的靜態方法
    -->
<bean id="userService" class="com.bilibili.utils.BeansFacotory1" factory-method="getBeans"></bean>

<!-- 實例工廠方法建立對象-->
<!-- 首先配置工廠類的實例 -->
<bean id="beansFactory2" class="com.bilibili.utils.BeansFacotory2"></bean>
<!-- factory-bean:配置工廠類實例對象
     factory-method:工廠類中用於建立對象的方法
    -->
<bean id="userService" factory-bean="beansFactory2" factory-method="getBeans"></bean>

小聲BB:工廠都有了還要你spring幹啥

依賴注入

面試官:爲何使用spring?
應聘者:由於方便?
面試官:什麼?

讓spring來管理bean的確方便😂

構造方法注入

構造方法注入須要存在有參構造:

public class UserServiceImpl implements UserService {
    private String userName;
    private int age;
    private UserDao userDao;

    public UserServiceImpl(String userName, int age, UserDao userDao) {
        this.userName = userName;
        this.age = age;
        this.userDao = userDao;
    }
}

在xml中使用constructor-arg標籤進行注入:

<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl">
    <!--
        以下3個屬性是用來指定給象中的哪一個具體屬性賦值
        index: 經過下標來指定構造方法中的屬性
        name:  經過參數名來指定構造方法中的屬性
        type: 經過參數的類型(全限定名)來指定構造方法中的屬性

        以下2個屬性是用來指定給對象中的屬性賦什麼值
        value:  賦值基本類型的值  例如:string,int,double...
        ref :  被spring管理的其餘bean類型。必須是xml中配置的bean
     -->
    <constructor-arg name="userName" value="王者榮耀"/>
    <constructor-arg name="age" value="18" />
    <constructor-arg name="userDao" ref="userDao" />
</bean>
<bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>

set方法注入

通常使用這種,比構造方法更靈活。

<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl">
    <!--
     property:set方法注入屬性
     name:set方法的名字後面的內容,小寫開頭
        例如:setUserName -  userName
        底層: userName - UserName - setUserName
     value:基本屬性類型的值  例如 String int...
     ref:被spring管理的bean類型的值
     -->
    <property name="userName" value="嗚啦啦"/>
    <property name="age" value="20"/>
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>

命名空間方式注入

其實也是set注入,只不過能夠少些一些標籤,沒什麼用。(由於可讀性不強

<!-- 須要在beans標籤中添加命名空間:xmlns:p="http://www.springframework.org/schema/p" -->
<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"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
    <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"
    p:userName="嗚啦啦" p:age="18" p:userDao-ref="userDao"></bean>
</beans>

注入集合屬性

先來UserServiceImpl實現類:


UserServiceImpl

public class UserServiceImpl implements UserService {
    private String[] myArray;
    private List<String> myList;
    private Map<String,String> myMap;
    private Set<String> mySet;

    public String[] getMyArray() {
        return myArray;
    }

    public void setMyArray(String[] myArray) {
        this.myArray = myArray;
    }

    public List<String> getMyList() {
        return myList;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public Map<String, String> getMyMap() {
        return myMap;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public Set<String> getMySet() {
        return mySet;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
}

而後是xml使用特定標籤中注入:

<!--
注入集合屬性:
使用set方法注入集合屬性:
   array:通常用來設置數組
   list:通常用來設置list集合
   map:通常用來設置map集合
-->
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl">
    <property name="myArray">
        <array>
            <value>a</value>
            <value>b</value>
            <value>c</value>
        </array>
    </property>
    <property name="myList">
        <list>
            <value>aa</value>
            <value>bb</value>
            <value>cc</value>
        </list>
    </property>
    <property name="myMap">
        <map>
            <entry key="key1" value="value1"></entry>
            <entry key="key2" value="value2"></entry>
        </map>
    </property>
    <property name="mySet">
        <set>
            <value>aaa</value>
            <value>bbb</value>
            <value>ccc</value>
        </set>
    </property>
</bean>

bean除了使用xml進行注入,還可使用註解進行注入,只不過像JdbcTemplate這種依賴中的類(暫時)就只能使用xml文件來配置注入(context標籤須要給beans根標籤添加命名空間):


使用xml注入JdbcTemplate

<!-- 加載外部jdbc.properties配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 構造器方式注入數據源 -->
    <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!-- 靜態方法配置dataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
    <property name="driverClassName" value="${jdbc.driverClass}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

jdbc.properties文件

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root

基於註解的IoC

使用註解方式進行注入時須要給beans標籤添加命名空間:

<?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
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
<!-- ↑須要添加context命名空間 -->

    <!-- 配置註解方式掃描的包:在指定的包下進行掃描,若是發現類上面有註解,讓其裝配到容器中 -->
    <context:component-scan base-package="com.bilibili"/>
</beans>

聲明bean的註解:

  • @Component("beanName"):至關於xml配置的<bean><bean/>標籤,註解的value屬性值至關於bean標籤的id屬性,若是不指定value屬性,默認bean的id就是類名,首字母小寫(Component:組件)
    下面三個註解和Component做用同樣,只不過更加語義化
    • @Controller:通常用於表現層
    • @Service : 通常用於業務層
    • @Respository : 通常用於持久層

注入相關注解:

  • @Autowired:自動裝配,標註在須要注入的屬性上。當使用該註解注入屬性時,set方法能夠省略,當有多個相同類型的時候,bean的id必需要和屬性的名字一致,才能注入成功,不然報錯
  • @Qualifier:須要結合@Autowired註解一塊兒使用,在自動注入的基礎上,能夠給屬性注入指定id的bean
  • @Resource:直接注入指定id的bean
  • @Value註解用來給基本類型的屬性注入值。可使用${key}從外部properties配置文件中引入值,須要注意properties配置文件須要在applicationContext.xml中引入

做用範圍註解:

  • @Scope:註解和<bean>標籤的scope屬性的做用一致。值能夠爲prototypesingleton(默認)

生命週期註解:

  • @PostConstruct:聲明這個方法是初始化方法,對象被建立的時候調用一次。
  • @PreDestroy:聲明這個方式是銷燬方法,對象被銷燬的時候調用一次。

xml方式和註解方式對比:

\ xml 註解
bean定義 <bean id="" class="" .../> @Component
衍生:
@Controller
@Service
@Respository
bean名稱 經過idname屬性指定 經過上面三個註解的value屬性指定
bean注入 property或p命名空間 @Autowired按類型注入
@Qualifier配合@Autowired指定
@Resource的name屬性,按名稱注入
bean做用範圍
生命週期
init-method
destroy-method
scope
@PostConstruct
@PreDestroy
@Scope

純註解配置

上面說到像JdbcTemplate這種依賴中的類(暫時)就只能使用xml文件來配置注入,固然也可使用純註解進行配置。

主配置:

//聲明當前類是一個spring的配置類,用來替代xml配置文件
//獲取容器時須要使用AnnotationApplicationContext(@Configuration標註的類.class)
@Configuration
//用於配置容器初始化時須要掃描的包
//和xml配置中<context:component-scan base-package="com.bilibili"/>做用一致
@ComponentScan("com.bilibili")
//導入其餘配置類
@Import(JdbcConfig.class)
public class SpringConfig {
    //標註這個方法的返回值做爲一個bean而且交給spring容器管理,value屬性就是bean的id
    @Bean("jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

外部配置:

//引入外部文件,和<context:property-placeholder location="classpath:jdbc.properties"/>做用同樣
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
    //使用value註解引用外部變量,這樣就不用寫死配置了。
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driverClass}")
    private String driverClass;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    //標註這個方法的返回值做爲一個bean而且交給spring容器管理,value屬性就是bean的id
    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

測試類:

public class SpringConfigTest {

    @Test
    public void getJdbcTemplate() {
        //使用AnnotationConfigApplicationContext實現類來獲取工廠
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        JdbcTemplate jdbcTemplate = (JdbcTemplate)ac.getBean("jdbcTemplate");
        System.out.println("jdbcTemplate = " + jdbcTemplate);
    }
}

在Junit測試類中注入

在每一個單元測試類中,咱們都須要獲取Spring容器,而後獲取要測試的類:

@Before
public void setUp() throws Exception {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    accountService = (AccountService) ac.getBean("accountServiceImpl");
}

那麼能不能直接在測試類中注入要測試的bean呢?

固然是能夠。
首先添加依賴:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<!-- spring5及以上版本要求junit的版本必須是4.12及以上。  -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>

而後給測試類配置註解:

//配置spring的單元測試運行器,自動建立spring容器
@RunWith(SpringJUnit4ClassRunner.class)
//配置容器建立時依賴的配置
//xml文件方式,直接給value賦值(注意前綴classpath:)
@ContextConfiguration("classpath:applicationContext.xml")
//純註解方式,給classes屬性賦值
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceImplTest {
    //依賴注入
    @Resource(name = "accountService")
    private AccountService accountService;
}

而後就能夠愉快地在測試類中使用注入的依賴了。依賴少的時候好像並無方便多少😅

相關文章
相關標籤/搜索