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容器注入