1. 程式人生 > >2. spring 應用之IOC

2. spring 應用之IOC

 本文是作者原創,版權歸作者所有.若要轉載,請註明出處

 

我們知道Spring Framework 最重要的功能就是IoC (Inversion of Control ),也叫DI(dependency injection),這不是我說的,是官網這麼說的,截圖如下

spring官網說IoC,也叫DI,是同一個意思.

 

首先複習一下spring的應用

1.用xml方式將物件交給spring管理

首先是測試類

public class UserService  {
    
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

}

然後是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
        https://www.springframework.org/schema/beans/spring-beans.xsd"
    default-autowire="no">

    
    <bean class="com.lusaisai.service.UserService" id="userService" >
        <!--此處name的值與set方法要一致-->
        <property name="userName" value="lusai"></property>
    </bean>
    
</beans>

最後是測試

public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");

      System.out.println(userService.getUserName());
 }

 

 可以看到注入成功

 

 2.依賴注入

首先是UserService依賴的物件UserDao

public interface UserDao {
    void test();
}

然後是它的實現類

public class UserDaoImpl implements UserDao {
    @Override
    public void test() {
        System.out.println("UserDaoImpl");
    }
}

在UserService增加依賴的物件UserDao和set方法,注意,這裡需要UserService的空構造

public class UserService  {

    private String userName;

    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void serviceTest(){
        userDao.test();
    }

}

然後是xml配置

<!--必須有空構造-->
    <bean class="com.lusaisai.service.UserService" id="userService" >
        <property name="userName" value="lusai"></property>
        <!--這裡的ref指定下方bean標籤配置物件的id-->
        <!--此處name的值與UserService的屬性userDao的set方法名要一致-->
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>

然後是測試

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        System.out.println(userService.getUserName());
        userService.serviceTest();

看下結果

 

好了,注入成功

 

3.我們發現一個問題,我們已經在程式碼裡寫了UserDao,還有它的set和get方法,為啥還要在xml裡用配置告訴spring應該如何注入呢,其實spring提供了自動裝配的功能,如下圖

 我們可以看到default-autowire有5個可供選擇

 

 單獨的bean標籤裡也有5個供選擇,這裡截個官網的圖

 

 

下面我們來測試一下,首先是byName,我們把手動裝配註釋掉,新增上default-autowire="byName"

<?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"
default-autowire="byName" >

<!--必須有空構造-->
<bean class="com.lusaisai.service.UserService" id="userService" >
<property name="userName" value="lusai"></property>
<!--這裡的ref指定下方bean標籤配置物件的id-->
<!--此處name的值與UserService的屬性userDao的set方法名要一致-->
<!--<property name="userDao" ref="userDao"></property>-->
</bean>

<bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>

</beans>
UserService中的程式碼沒有改動,測試
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        System.out.println(userService.getUserName());
        UserDao userDao = userService.getUserDao();
        System.out.println(userDao);

看下結果

 

 好的,成功了,我們改一下set方法的名字

public void setUserDao1(UserDao userDao) {
        this.userDao = userDao;
    }

重新執行一次

 

 

 可以看到,userDao沒有注入進來, 我們把方法名復原再次修改屬性名試一下

private String userName;

    private UserDao userDao2;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao2;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao2 = userDao;
    }

重新執行

 

 

 可以看到,注入成功,因此byName的set方法名要和依賴物件的bean標籤的id相同

 

接下來看下byType,我們將default-autowire="byType" 再加上一個同一個類不同名的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"
    default-autowire="byType" >

    <!--必須有空構造-->
    <bean class="com.lusaisai.service.UserService" id="userService" >
        <property name="userName" value="lusai"></property>
        <!--這裡的ref指定下方bean標籤配置物件的id-->
        <!--此處name的值與UserService的屬性userDao的set方法名要一致-->
        <!--<property name="userDao" ref="userDao"></property>-->
    </bean>

    <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>

    <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao2" ></bean>

</beans>

userService程式碼

private String userName;

    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

再執行一下結果

十月 17, 2019 11:53:15 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1499)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1379)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
    at com.lusaisai.test.Test.main(Test.java:16)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1484)
    ... 13 more

報錯資訊,顯示只要一個,但找到2個,我們註釋掉一個

<!--<bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>-->

重新執行,看下結果

 

 

 注入成功了,說明byType的自動個裝配方式中如果存在多個相同型別不同id的bean標籤,則丟擲異常,如果沒有匹配的bean,則不自動裝配

 

最後看下構造方法注入

首先

default-autowire="constructor"

然後在UserService中註釋掉set方法,新增userDao的構造方法

private String userName;

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

,執行,看結果

 

 注入成功了

我們把userDao的構造方法註釋了,再執行看下結果

 

 沒注入成功,但也不報錯

 

好,到此為止,我們的xml配置將物件交給spring管理就講完了總結一下

可以看出,no和Default是不自動裝配的,byName和byType是通過set方法自動裝配的,同時要確保有空構造存在,我猜底層是用newInstance()實現的具體原始碼後面再看

byName是根據set方法名自動裝配的,set方法名要和bean標籤的id相對應,否則,注入不成功,但不會報錯

byType是根據型別裝配的,如果存在多個該屬性型別的bean標籤,則丟擲異常,如果沒有匹配的bean,則不自動裝配

constructor是根據構造方法來裝配的,如果容器中沒有一個建構函式引數型別的bean,則不自動裝配

 

可以看到這種xml格式其實是非常麻煩的,實際專案中我們一般通過註解來將物件交給spring管理

只需要將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
         http://www.springframework.org/schema/context/spring-context-3.0.xsd"
      >

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

</beans>

我們來看例子,注意上面的spring配置檔案中一個bean標籤都沒有了,加入了@Component註解,

這裡也可以用@Repository,@Service,@Controller,可以將物件注入到spring容器進行管理,效果是一樣的

@Component
public class UserDaoImpl implements UserDao {
    @Override
    public void test() {
        System.out.println("UserDaoImpl");
    }
}

這裡在依賴的物件上加入了@autowire註解,我猜底層是通過filed.set呼叫的,所以不需要set方法,這個以後看原始碼了再講

@Component
public class UserService  {

    private String userName="lusai";

    @Autowired
    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

}

當然也可以將註解加在set方法上,如下

@Component
public class UserService {

private String userName="lusai";


private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName1) {
this.userName = userName1;
}

public UserDao getUserDao() {
return userDao;
}

測試一下

public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        System.out.println(userService.getUserName());
        UserDao userDao = userService.getUserDao();
        System.out.println(userDao);

    }

看下結果

 

 可以看到,注入成功了

如果我們再給UserDao介面的另一個實現類也交給spring管理,會不會報錯呢?如下

@Component
public class UserDaoImpl2 implements UserDao{
    @Override
    public void test() {
        System.out.println("UserDaoImpl2");
    }
}

執行一下看結果

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:668)
    ... 15 more

果然報錯了,說明同一個介面,最好不要寫多個實現類,那我非要寫多個實現類怎麼辦呢

我們可以用@Resource,並指定它的name屬性的別名為類名的首字母小寫,如下

@Component
public class UserService  {

    private String userName="lusai";


    @Resource(name = "userDaoImpl2")
    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

}

執行一下,看看結果

 

 成功了

 

總結:先來張網上的圖

 

 @Autowired與@Resource都可以用來裝配bean. 都可以寫在欄位上,或寫在setter方法上。兩者如果都寫在欄位上,那麼就不需要再寫setter方法

@Autowired預設按型別裝配(這個註解是屬業spring的),需要匯入包org.springframework.beans.factory.annotation.Autowired 預設按照型別來進行裝配

@Resource(這個註解屬於java的),需要匯入包javax.annotation.Resource。預設按照名稱進行裝配,名稱可以通過name屬性進行指定

 

最後,現在流行用javaconfig而不是xml來配置spring,這裡貼一下javaconfig的程式碼

第一種:相當於寫bean標籤,如下,這裡使用@bean註解 將UserService 和UserDaoImpl物件交給spring管理

@Configuration
public class SpringConfig {

    @Bean
    public UserService userService(){
        return new UserService();
    }

    @Bean
    public UserDao userDao(){
        return new UserDaoImpl();
    }

}

這裡注入依賴

public class UserService  {

    private String userName="lusai";

    @Autowired
    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

}

這是測試demo

public static void main(String[] args) {

        AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService) annotationContext.getBean("userService");
        System.out.println(userService.getUserName());
        UserDao userDao = userService.getUserDao();
        System.out.println(userDao);

    }

看下結果

 

 

第二種:相當於掃描包,把上面的兩個@bean註解註釋了,開啟掃描

@Configuration
@ComponentScan("com.lusaisai")
public class SpringConfig {

    /*@Bean
    public UserService userService(){
        return new UserService();
    }

    @Bean
    public UserDao userDao(){
        return new UserDaoImpl();
    }*/

}

在UserService 和UserDaoImpl物件上加上@Component,將他們交給spring管理,如下

@Component
public class UserDaoImpl implements UserDao {
    @Override
    public void test() {
        System.out.println("UserDaoImpl");
    }
}
@Component
public class UserService  {

    private String userName="lusai";

    @Autowired
    private UserDao userDao;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName1) {
        this.userName = userName1;
    }

    public UserDao getUserDao() {
        return userDao;
    }

}

重新測試

 

 可以看到,注入成功了.

最後 @Autowired與@Resource 和xml中的 default-autowire="byName" 和byType是不是相同的原理的呢

先說結論:兩者不是同一套邏輯,這裡就後面看原始碼的時候再來解釋吧

&n