Spring中的依赖注入(2)

一、背景

实现控制反转(Ioc)的方式有很多种,依赖注入(DI)是实现控制反转的一种方法.

1.有数据层dao包和服务层service包

分别在其中定义了相应的接口.

1.1接口UserDao

在UserDao接口中定义了一个getUserName方法

package dao;

/**
 * @Author Yiang37
 * @Date 2020/4/30 20:16
 * Description:
 */
public interface UserDao {

    void getUserName();
}

它有两个实现类UserDaoImplByMysql与UserDaoImplByOracle

UserDaoImplByMysql代码如下

package dao.impl;

import dao.UserDao;

/**
 * @Class: UserDaoImplByMysql
 * @Author: Yiang37
 * @Date: 2020/4/30 20:19
 * @Description:
 */

public class UserDaoImplByMysql implements UserDao {

    public void getUserName() {
        System.out.println("我是Mysql的getUserName");
    }
}

UserDaoImplByOracle代码如下

package dao.impl;

import dao.UserDao;

/**
 * @Class: UserDaoImpl
 * @Author: Yiang37
 * @Date: 2020/4/30 20:17
 * @Description:
 */
public class UserDaoImplByOracle implements UserDao {
    public void getUserName() {
        System.out.println("我是Oracle的getUserName");
    }
}

1.2接口UserService

在UserService接口中定义了一个outUserName()方法

package service;

/**
 * @Class: UserService
 * @Author: Yiang37
 * @Date: 2020/4/30 20:21
 * @Description:
 */
public interface UserService {
    void outUserName();
}

它有一个实现类UserServiceImpl

UserServiceImpl代码如下

package service.impl;

import dao.UserDao;
import dao.impl.UserDaoImplByMysql;
import service.UserService;

/**
 * @Class: UserService
 * @Author: Yiang37
 * @Date: 2020/4/30 20:21
 * @Description:
 */
public class UserServiceImpl implements UserService {
    
    UserDao userDao = new UserDaoImplByMysql();

    public void outUserName() {
        userDao.getUserName();
    }
}

2.使用service完成操作

import service.UserService;
import service.impl.UserServiceImpl;

/**
 * @Class: Test1
 * @Author: Yiang37
 * @Date: 2020/4/30 20:23
 * @Description:
 */
public class Test1 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.outUserName();
    }
}  

可以看到程序输出

我是Mysql的getUserName

二、分析

1.修改显示的值

在上面的内容中我们看到userService.outUserName()在执行后输出了

我是Mysql的getUserName

为什么会是这个结果?这还得回到UserServiceImpl的代码中

1. public class UserServiceImpl implements UserService {
2.    
3.    UserDao userDao = new UserDaoImplByMysql();
4.
5.    public void outUserName() {
6.        userDao.getUserName();
7.    }
8.  }

我们看到在代码的第6行调用了userDao.getUserName();
而这个userDao是谁呢?
在代码的第3行指定了

 UserDao userDao = new UserDaoImplByMysql();

现在问题来了,我们想修改为UserDaoImplByOracle()来实现?
可以更改上面userDao的具体实现类

 UserDao userDao = new UserDaoImplByOracle();

程序输出了我们想要的结果

我是Oracle的getUserName

通过上面的方式,虽然能达到结果,但是我们得修改掉原来的代码
在调用userDao时,我们就得明确的指明它的实现类.

2.改进

我们是否可以每次为userService动态的指定其中的userDao呢?
修改我们的代码,变成下面这样.

public class UserServiceImpl implements UserService {
    //成员变量UserDao
    private UserDao userDao;

    //UserDao属性的set方法
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void outUserName() {
        userDao.getUserName();
    }
}

现在我们再修改测试类

public class Test1 {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        ((UserServiceImpl)userService).setUserDao(new UserDaoImplByMysql());
        userService.outUserName();
    }
}

注:
UserService userService = new UserServiceImpl();
左侧是UserService类型,是找不到setUserDao()方法的.
所以在setUserDao()方法之前进行了类型转换.
( (UserServiceImpl) userService).xx

当创建时指明类型为UserServiceImpl时才有这个方法
"多态相关知识"

现在我们通过set方法为其注入了UserDaoImplByMysql对象,再次运行.

我是Mysql的getUserName

为其set进不同的值,例如.

((UserServiceImpl)userService).setUserDao(new UserDaoImplByOracle());

同样能成功输出想要的内容

我是Oracle的getUserName

3、对比

一开始是程序主动设置userDao对象,现在变成了程序被动的接收userDao对象.
我们可以看到,通过后者,无需更改UserServiceImpl中的任何内容.
只是在使用通过set注入,即可实现想要的效果.

当写了新的userDao时,例如.

public class UserDaoImplBySqlServer implements UserDao {
    public void getUserName() {
        System.out.println("我是SqlServer的getUserName");
    }
}

然后在使用时set进不同的内容

((UserServiceImpl)userService).setUserDao(new UserDaoImplBySqlServer());

输出值即可改变

我是SqlServer的getUserName

通过上面的方式,写代码时不用再去管理对象的创建了.
例如UserServiceImpl中的userDao对象我们不用显示的指明,而是可以从外部动态的(被动的)接收.

我还没工作啊我设想一下,controller层调用service层吧.
我们是不是可以直接在controller中写判断之类的,然后根据情况动态的set进去业务对象,实现不同的业务逻辑.
这样控制权就交给了用户,用户来选择执行哪个业务方法?

控制反转,现在是不是反转了.
这就是Ioc的原型.

三、Spring中的Ioc

1.xml文件

创建一个pojo包,里面创建一个User类,包含一个成员变量username.
为其设置上set方法和toString方法.

package pojo;

/**
 * @Class: User
 * @Author: Yiang37
 * @Date: 2020/4/30 22:34
 * @Description:
 */
public class User {

    private String username;

    //set方法
    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                '}';
    }
}

在xml文件中声明一个对应的bean.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- id对应bean的名字 class对应类的位置-->
    <bean id="user" class="pojo.User">
        <!-- name对应成员变量username -->
        <!-- value是我们要赋的值 -->
        <property name="username" value="yang">
        </property>   
    </bean>

</beans>

编写测试类.

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import pojo.User;

/**
 * @Class: Test2
 * @Author: Yiang37
 * @Date: 2020/4/30 22:38
 * @Description:
 */
public class Test2 {
    public static void main(String[] args) {
        // 参数是具体的文件位置
        ApplicationContext context =new FileSystemXmlApplicationContext("./web/WEB-INF/applicationContext.xml");
        //与xml中的bean.id对应
        User tUser = (User) context.getBean("user");
        System.out.println(tUser.toString());
    }
}

运行后,可以看到username成功注入"yang".

User{username='yang'}

tUser对象是谁创建的?
Spring创建的

tUser对象的属性怎么设置的?
Spring容器设置的

总结

    <!--
        Java创建对象
        User user = new User();

        xx类 bean名字 = new xx类()
    -->
    <bean id="bean名字" class="xx类">
        <property name="属性名字" value="需要传入的参数">
        </property>   
    </bean>

    <!-- 
        外层<bean></bean>标签相当于创建类
        内层<property></<property>标签相当于给属性赋值
     -->

控制反转,把控制权转交给了Spring容器(<beans></beans>包裹).
我们使用的时候从Spring容器中拿就可以了,不用我们new对象.
即使用了Spring框架后,对象是由Spring创建的.

Spring通过依赖注入的方式完成了控制反转.
set方式注入

在User中注释掉set方法

public class User {

    private String username;

    //set方法
//    public void setUsername(String username) {
//        this.username = username;
//    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                '}';
    }
}

可以看到xml中报错.

扩展
在xml中注释掉property.

    <bean id="user" class="pojo.User">
<!--        <property name="username" value="yang">-->
<!--        </property>-->
    </bean>

图示位置没有图标显示

现在取消注释

 <bean id="user" class="pojo.User">
        <property name="username" value="yang">
        </property>
 </bean>

图示位置出现Spring标志

点击可以跳转到相应的位置

同理,类前方也会有Spring的标志

看到这些标志就说明已经被Spring托管了.

下面前面提到的UserServiceImpl进行配置

    <bean id="daoImplByMysql" class="dao.impl.UserDaoImplByMysql">
    </bean>

    <bean id="daoImplByOracle" class="dao.impl.UserDaoImplByOracle">
    </bean>

    <bean id="userServiceImpl" class="service.impl.UserServiceImpl">
        <!-- value注入值 ref注入bean,对应beanId即可 -->
        <property name="userDao" ref="daoImplByMysql">
        </property>
     </bean>

测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import service.impl.UserServiceImpl;

/**
 * @Class: Test3
 * @Author: Yiang37
 * @Date: 2020/4/30 23:24
 * @Description:
 */
public class Test3 {
    public static void main(String[] args) {
        //拿到Spring容器
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("./web/WEB-INF/applicationContext.xml");
        UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("userServiceImpl");
        userServiceImpl.outUserName();
    }
}

输出结果

我是Mysql的getUserName

还记得前面是在Java代码中set进去值吗?

((UserServiceImpl)userService).setUserDao(new UserDaoImplBySqlServer());

现在都不用在Java代码写这个东西了,交给Spring后,直接修改xml文件,指明ref后,即可达到目的.
我们只管写代码,完了在xml做好配置就好了.

Spring的Ioc:对象由Spring来创建、管理、装配.

2.Ioc如何创建对象

在上文中,我们构建bean后,通过property为其set进去了值.

    <bean id="user" class="pojo.User">
        <property name="username" value="yang">
        </property>
    </bean>

即我们了解到<bean></bean>标签可以完成对象的创建.
现在是先构造,再进行property为其set进去值.
那么是否和类一样,存在构造函数的方式呢?
注释掉<property></property>标签,现在我们研究一下SpringBean如何实现构造函数.
现在情况如下.

  • 类User
public class User {

    private String username;
    private int age;
    
    @Override
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

注意此时我们关注的是构造函数,所以没有写出username属性的set方法.

  • xml文件
    <bean id="user" class="pojo.User">
    </bean>
  • 测试类
public class Test2 {
    public static void main(String[] args) {
        ApplicationContext context =new FileSystemXmlApplicationContext("./web/WEB-INF/applicationContext.xml");
        User tUser = (User) context.getBean("user");
        System.out.println(tUser.toString());
    }
}

显示写出无参构造函数

public class User {

    private String username;
    private int age;

    public User() {
        System.out.println("我是无参构造函数");
    }
    
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

运行测试类,输出.

我是无参构造函数
User{username='null', age=0}

得知,默认执行无参构造函数.
查阅Spring文档,在1.4.1节可以找到有参构造的三种书写方式.
增加有参构造函数.

public class User {

    private String username;
    private int age;

    public User() {
        System.out.println("我是无参构造函数");
    }

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

(1)构造函数参数类型匹配
xml文件

    <bean id="user" class="pojo.User">
        //上面构造形参第一个是String类型,第二个是int,这里我特意把int放在了前面.
        //是根据参数类型匹配
        <constructor-arg type="int" value="22"/>
        <constructor-arg type="java.lang.String" value="yang"/>
    </bean>

指明了构造函数,无参函数"我是无参构造函数"不再输出,结果如下.

User{username='yang', age=22}  

(2)构造函数参数索引(0开始)
xml文件

    <bean id="user" class="pojo.User">
        //构造函数参数是String类型,结果还能自动转换.
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>

结果

User{username='7500000', age=42}  

尝试把第二个形参值,即下标1处的int赋值为字符串"abc",很智能,直接报错.

(3)构造函数参数名称
xml文件

    <bean id="user" class="pojo.User">
        <constructor-arg name="username" value="666"/>
        <constructor-arg name="age" value="42"/>
    </bean>

确实智能,直接提示参数名字了.

结果

User{username='666', age=42}

个人觉得用参数名字比较方便吧(参数类型可能重复,参数下标也不好数.)
在注册bean的时候就会给我们创建对象

    <bean id="user" class="pojo.User">
        <constructor-arg name="username" value="666"/>
        <constructor-arg name="age" value="42"/>
    </bean>
    
    //增加一个无参函数构建的bean
    <bean id="otherUser" class="pojo.User">
    </bean>

测试类用的bean是user,这个otherUser没有用.

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext context =new FileSystemXmlApplicationContext("./web/WEB-INF/applicationContext.xml");
        //拿到的bean名字叫user,名字叫otherUser的bean没使用.
        User tUser = (User) context.getBean("user");
        System.out.println(tUser.toString());
    }
}

结果第一行显示otherUser对象也会被创建.

我是无参构造函数
User{username='666', age=42}

无论是否使用,Spring容器在启动时就会创建完成注册的bean对象,等需要用了自己取就好.
例如:不管你家有没有打野位置,野怪都会生成,想打了去打就好了.
修改测试类,获取同一个bean.

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext context =new FileSystemXmlApplicationContext("./web/WEB-INF/applicationContext.xml");
        User tUser = (User) context.getBean("user");
        User tUser2 = (User) context.getBean("user");

        System.out.println(tUser.toString());
        System.out.println(tUser2.toString());

        System.out.println(tUser == tUser2);
    }
}

结果显示true,说明这个bean的实例只有一份.
总结

<beans></beans>标签包裹着我们的<bean></bean>标签.
beans就相当于Spring容器,bean就是容器里的东西.

无论用不用,Spring容器在启动时就会将注册的bean创建,需要了获取即可.

Sring实现控制反转有无参构造后set方式注入直接通过构造函数注入这两种常见方式(依赖注入).

3.依赖注入

  • 依赖:bean对象的创建依赖于bean容器
  • 注入:bean对象的属性通过bean容器注入
posted @ 2020-04-30 21:31  羊37  阅读(318)  评论(0编辑  收藏  举报