1、IOC控制反转
控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现Ioc 的一种方法。没有Ioc的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后将对象的创建转移给第三方(交给Spring容器来创建)!!灵活性更高。
也可以这么理解,早期用户调用的功能是程序员指定的,但是用了控制反转去实现之后,控制权交给用户后,程序员写好功能,用户想调用哪个功能就调用哪个!
作用:降低耦合度(也就是降低模块之间的依赖)
下面会通过举例子说明控制反转带来的好处。
1.1 最原始的业务层调用dao层方法
(1)创建UserDao接口
public interface UserDao {
void save();
}
(2)创建UserDaoImpl实现UserDao
public class userDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存用户数据");
}
}
(3)创建业务层UserService接口
public interface UserService {
void save();
}
(4)创建业务层UserServiceImpl实现UserService(由程序来控制创建对象)
public class UserServiceImpl implements UserService {
//最原始的方法,由程序来控制创建对象
private UserDao userDao=new userDaoImpl();
@Override
public void save() {
userDao.save();
}
}
(5)测试
@Test
public void test11(){
UserService userService=new UserServiceImpl();//用户实际调用的是业务层
userService.save();
}
缺点 :
当用户需求改变,要保存mysql 的数据,那么就需要改变业务方法,这里改变daoImpl的内容即可
public class userDaoMysqlImpl implements UserDao {
@Override
public void save() {
System.out.println("保存Mysql");
}
}
那么这时候我们就需要去ServiceImpl层里面修改private UserDao userDao=new userDaoImpl();为下面代码,也就是程序员需要去改变源代码,那么当用户的需求不断改变时,也意味着源码需要不断的改变,所以这就是耦合度高的缺点!!
//private UserDao userDao=new userDaoImpl();
private UserDao userDao=new userDaoMysqlImpl();
1.2 解决上面耦合度高缺点的常用方法
1.2.1 通过工厂模式
(1)新建静态工厂负责创建UserDao
public class StaticFactory {
public static UserDao getUserDao(){
return new userDaoImpl();
}
}
(2)ServiceImpl改变成:
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Override
public void save() {
userDao = StaticFactory.getUserDao();//工厂模式创建对象
userDao.save();
}
}
(3)其他的dao和service和上面一样,那么如果用户需求像上面一样改变,那么源代码就不需要变动了,但是!!需要改变工厂类的方法。比如用户需求新增保存mysql数据,那么工厂类应该变为:
public class StaticFactory {
public static UserDao getUserDao(){
return new userDaoMysqlImpl();
}
}
(4)测试
@Test
public void test11(){
UserService userService=new UserServiceImpl();//用户实际调用的是业务层
userService.save();
}
通过工厂模式解耦,实则是把Service层和dao层的耦合变成Service-工厂-dao层的耦合,当需求改变,源代码不变,但是工厂类需要变,所以依然还是存在着比较高的耦合度
1.2.2 通过set方法(这种思想就是比较接近IOC思想的解决方法了)
思想:利用set方法实现动态值的注入
(1)UserDaoSeriviceImpl改为:
public class UserServiceImpl implements UserService {
private UserDao userDao;
//set方法实现动态注入对象
public void setUserDao(UserDao userDao){
this.userDao=userDao;
}
@Override
public void save() {
userDao.save();
}
}
(2)测试(只需要用户动态传入对象即可,源代码不需要改变)
下面的代码之所以需要强转类型是因为UserService没有setUserDao这个方法,所以需要强转为实现类调用。而用接口变量指向实现类对象是符合多态思想的。
@Test
public void test11(){
UserService userService=new UserServiceImpl();//用户实际调用的是业务层
((UserServiceImpl) userService).setUserDao(new userDaoImpl());
userService.save();
}
优点:之前我们都是程序主动创建对象(private UserDao userDao=new userDaoImpl())!控制权在程序员手上。但是现在使用set方法,程序不再具有主动性,而是被动的接受对象!!这也就是控制反转(控制权的反转)。这也是IOC的原型。
1.3 Spring解决的思路
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从loc容器中取出需要的对象。控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,Dl)。
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
2. Spring的依赖注入
Spring的依赖注入概念
- 依赖注入(Dependency Injection):它是Spring框架接心IOC的具体实现。组件之间依赖关系由容器在运行期决定,形象的说,既由容器动态的(编译前不会建立,运行时建立)将某个依赖关系注入到组件(在Spring中称为Bean)之中。
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有属性,由容器注入
- 作用:提升组件重用的频率,为系统搭建一个灵活、可扩展的平台。
- 在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法,那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
怎么将UserDao怎样注入到UserService内部呢?
2.1 XML配置方法实现
XML的重点配置
(1)首先我们先了解<bean>标签的属性:
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名
scope属性:Bean 的作用范围,常用是Singleton(默认)和prototype
(2)<property>标签:属性注入
name属性:属性名称
value:注入的普通属性值
ref属性:注入的对象引用值
(3)<constructor-arg>标签:有参构造注入
2.1.1构造方法(不推荐)
Beans类必须提供有参构造方法
(1)构造方法下标赋值
<bean id="dog" class="domain.Dog">
<constructor-arg index="0" value="zhangSan"/>
</bean>
(2)构造方法参数类型赋值(不建议使用)
<bean id="dog" class="domain.Dog">
<constructor-arg type="java.lang.String" value="zhangSan"/>
</bean>
(3)直接通过构造方法参数名设置(推荐)
<bean id="userDao1" class="dao.impl.userDaoImpl" ></bean>
<!--1、构造方法的参数名 2、bean ID-->
<bean id="userService" class="service.impl.UserServiceImpl" >
<constructor-arg name="userDao" ref="userDao1"></constructor-arg>
</bean>
2.1.2 set方法(推荐)
Bean类必须有一个无参构造方法,Beans类必须为属性提供setter方法
(1)普通版本,举个栗子:为service注入dao
<bean id="userDao" class="dao.impl.userDaoImpl" ></bean>
<bean id="userService" class="service.impl.UserServiceImpl">
<!--告诉spring依赖注入(name指的是set方法去掉set之后把第一个字母小写.对象的引用用ref是bean id)-->
<property name="userDao" ref="userDao" />
</bean>
(2)P命名空间注入,本质也是set方法注入,但比起上述的se方法注入更加方便,主要体现在配置文件中,如下:
首先,需要引入P命名空间:
xmlna:p="http://www.apxingframework.org/achema/p"
其次,需要修改注入方式
<bean id="userDao1" class="dao.impl.userDaoImpl" ></bean>
<bean id="userService" class="service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
OK,使用xml配置法实现Bean的依赖注入之后,一旦有需求变化,我们彻底不需要去程序中更改代码,只需要在xml配置文件中进行修改。
2.1.3 测试
@Test
public void test11(){
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//只要一读完配置文件马上就创建对象
Dog dog = (Dog) app.getBean("dog");
Dog dog1 = (Dog) app.getBean("dog");
System.out.println(dog==dog1);//运行结果是true,也就是说Spring容器同一个对象只存放一个实例
}
这里的ApplicationContext是Spring的一个容器,这里顺带讲一下Spring容器的使用
2.2 Spring容器
- Spring容器是Spring的核心,它让JavaEE应用的各种组件不需要以硬编码方式耦合在一起,甚至无须使用工厂模式,因为Spring核心容器就是一个超级大工厂,所有的组件(包括数据源)都被当成Spring核心容器的管理对象,这些组件在Spring中被称为bean。一切bean的实例化,获取,销毁以及bean之间相互关系的处理等都是由Spring容器管理的。Spring容器通过配置文件很好地将bean组织在一起,而不再是由程序代码直接控制,实现了非常优秀的解耦。
- Spring为我们提供了两种核心容器,分别为BeanFactory和ApplicationContext。
- 除非一个内存非常关键的应用,对于大部分JavaEE应用而言,使用ApplicationContext作为Spring容器更为方便。因为ApplicationContext除了提供BeanFactory全部功能外,还提供了以下功能
- 默认预初始化所有singleton Bean
- ApplicationContext适用于单例对象(内存中只有一份实例!!),它在构造核心容器时,创建对象采取的策略是立即加载方式,也就是说,只要一读完配置文件马上就创建对象;只要容器在对象就一直存活着,容器销毁时对象消亡。
- BeanFactory适用于多例对象,它在构造核心容器时,创建对象采取的策略是延迟加载方式,也就是说,什么时候根据id获取对象了,什么时候才真正创建对象。当对象长期不使用并且也没有其他对象引用时,将由java垃圾回收器回收。
- 继承MessageSource接口,因此提供国际化支持
- 资源访问,如URL和文件
- 事件机制
- 可同时加载多个配置文件
- 以声明方式启动并创建Spring容器
- 默认预初始化所有singleton Bean
3. Bean的依赖注入的数据类型
上面的操作,都是注入的引用Bean,处了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。
注入数据的三种数据类型
3.1 普通数据类型
<!--普通属性值用value-->
<bean id="userDao1" class="dao.impl.userDaoImpl">
<property name="username" value="zyk" />
</bean>
<bean id="userService" class="service.impl.UserServiceImpl" >
<constructor-arg name="userDao" ref="userDao1"></constructor-arg>
</bean>
3.2 数组类型
<bean id="userDao1" class="dao.impl.userDaoImpl">
<property name="books">
<array>
<value>水浒传</value>
<value>红楼梦</value>
</array>
</property>
</bean>
3.3 集合数据类型
<property name=""> 这里的name指的是set方法去掉set之后,第一个字母小写的属性
(1)list
这里的list在userdao里面是这样定义的:
public void setStrList(List<String> strList) {
this.strList = strList;
}
<bean id="userDao1" class="dao.impl.userDaoImpl">
<property name="strList">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
</bean>
(2)map
这里的map在userdao里面是这样定义的:
public void setUsermap(Map<String, User> usermap) {
this.usermap = usermap;
}
<bean id="userDao1" class="dao.impl.userDaoImpl">
<property name="usermap">
<map>
<!--1、key取什么都行2、被引用对象必须存在容器当中,所以下面bean id=“user”-->
<entry key="user1" value-ref="user"></entry>
<entry key="user2" value-ref="user1"></entry>
</map>
</property>
</bean>
<bean id="user" class="domain.User">
<property name="name" value="zyk" />
<property name="address" value="22"/>
</bean>
<bean id="user1" class="domain.User">
<property name="name" value="yk" />
<property name="address" value="66"/>
</bean>
(3)properties
这里的properties在userdao里面是这样定义的:
public void setProperties(Properties properties) {
this.properties = properties;
}
<bean id="userDao1" class="dao.impl.userDaoImpl">
<property name="properties">
<props>
<prop key="p1">ppp1</prop>
<prop key="p2">ppp2</prop>
<prop key="p3">ppp3</prop>
</props>
</property>
</bean>
3.4 Bean的生命周期
1、单例模式(Spring默认机制),内存中只有一份实例!!
<bean id="userDao1" class="dao.impl.userDaoImpl" scope="singleton"/>
2、原型模式:每次从容器中get的时候,都会产生一个新对象!!
<bean id="userDao1" class="dao.impl.userDaoImpl" scope="prototype"/>
3、默认为单例模式的时候测试
@Test
public void test11(){
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");//只要一读完配置文件马上就创建对象
Dog dog = (Dog) app.getBean("dog");
Dog dog1 = (Dog) app.getBean("dog");
System.out.println(dog==dog1);//运行结果是true,也就是说Spring容器同一个对象只存放一个实例
}
4、引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。(resource等于其他配置文件的名称)
<import resource="applicationContext-xxx.xml"/>
.