Spring之IOC(一)
1、Spring之IOC(一)
1.1、简介
-
2002,首次推出了Spring框架的雏形:interface21框架!
-
Spring框架以interfance21框架为基础,经过重新设计,并不断丰富器内涵,于2004年3月24日,发布了1.0正式版。
-
Spring框架是一个开放源代码的J2EE应用程序框架,由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson/1423612)发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)
-
Spring理念:使用现有技术更加容易使用,本身就是一个大杂烩,整合了现有的技术框架!
- SSH:Struct2+Spring+Hibernate
- SSM: SpringMVC+Sping+Mybatis
-
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
-
官方仓库地址(依赖):https://mvnrepository.com/tags/spring
-
GitHub 源码: https://github.com/spring-projects/spring-framework
-
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency>
1.2、优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 控制反转(IOC),面向切面编程(AOP)
- 支持(生命式)事务的处理,对框架整合的支持!
- 适用范围:任何 Java 应用
- Spring 的根本使命:简化 Java 开发
总结:Spring就是一个轻量级的控制反转(IOC)和面向切片编程(AOP)的框架!
1.3、组成
1、SpringCore spring的核心功能:IOC容器,解决对象的创建及依赖关系
2、SpringWeb :spring对Web模块的支持
3、SpringDAO :spring对JDBC的支持
4、SpringORM :spring对ORM的支持
5、SpringAOP :切面编程
6、SpringEE: spring对JavaEE其他模块的支持
- Data Access/Integration层包含有JDBC、ORM、OXM、JMS和Transaction模块。
- Web层包含了Web、Web-Servlet、WebSocket、Web-Porlet模块。
- AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现。
- Core Container(核心容器):包含有Beans、Core、Context和SpEL模块。
- Test模块支持使用JUnit和TestNG对Spring组件进行测试。
1.4 拓展
- Spring Boot
- 一个快速开发的脚手架。
- 基于Spring Boot可以快速的开发单个微服务。
- 约定大于配置!
- Spring Cloud
- Spring Cloud 是基于SpringBoot实现的。
大多数公司都在使用Spring Boot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!具有承上启下的作用。
弊端:发展太久,违背了原来的理念!配置十分繁琐,人称“地狱配置”!
2、IOC理论
原来写法:例子
创建一个Manven项目导入SpaingMVC的依赖
Dao层
-
UserDao接口
package com.study.dao; //定义UserDao接口 public interface UserDao { void getUser(); }
-
UserDaoImpl实现类
package com.study.dao; //实现UserDao接口的实现类 public class UserDaoImpl implements UserDao{ @Override public void getUser() { System.out.println("默认获取用户的数据!"); } }
Service层
-
UserService 业务接口
package com.study.service; public interface UserService { void getUser(); }
-
UserServiceImpl 业务实现类
使用组合方式业务层Service调用Dao层(把Dao引入Service层),但这是固定写死的(程序控制创建对象)
package com.study.service; import com.study.dao.UserDao; import com.study.dao.UserDaoImpl; public class UserServiceImpl implements UserService{ //使用组合方式业务层Service调用Dao层(把Dao引入Service层),但这是固定写死的(程序控制创建对象) private UserDao userDao = new UserDaoImpl(); public void getUser() { userDao.getUser(); } }
测试类Test
-
Test类
import com.study.dao.UserDaoImpl; import com.study.service.UserService; import com.study.service.UserServiceImpl; //测试类 public class MyTest { public static void main(String[] args) { //调用真实的业务(用户实际调用的是业务层,dao层不需要接触) UserService userService = new UserServiceImpl(); //输出dao层的真正执行方法不会执行Service层,Service层只做的是调用dao层去查询结果 userService.getUser(); } } /*考虑如果用户需要增加多个需求如何处理?*/
输出:默认获取用户的数据
考虑如果用户需要增加多个需求如何处理?
Dao层在加入MySql,Oracle需求
-
UserDaoMysqlImpl实现类
-
package com.study.dao; //增加MySql获取数据需求 public class UserDaoMysqlImpl implements UserDao{ @Override public void getUser() { System.out.println("MySql获取用户数据!"); } }
-
UserDaoOracleImpl 实现类
package com.study.dao; //增加Oracle获取数据需求 public class UserDaoOracleImpl implements UserDao{ @Override public void getUser() { System.out.println("Oracle获取用户数据!"); } }
此时Service层中增加MySql、业务需求是就需要更改Service调用Dao层(这是就会修改service层的代码)
-
package com.study.service; import com.study.dao.UserDao; import com.study.dao.UserDaoImpl; import com.study.dao.UserDaoMysqlImpl; import com.study.dao.UserDaoOracleImpl; public class UserServiceImpl implements UserService{ //使用组合方式业务层Service调用Dao层(把Dao引入Service层),但这是固定写死的(程序控制创建对象) //private UserDao userDao = new UserDaoImpl(); //增加MySql业务需求是就需要更改Service调用Dao层(这是就会修改service层的代码) private UserDao userDao = new UserDaoMysqlImpl(); //增加Oracle业务需求又要改变service层 //private UserDao userDao = new UserDaoOracleImpl(); public void getUser() { userDao.getUser(); } } }
这时就会出现由于客户的需求修改原有的代码的问题(程序适应不了用户的变更)
如何解决这一问题!思路:程序不动让客户端增调用(设计模式==接口思想)
利用set进行动态实现值得注入!
-
package com.study.service; import com.study.dao.UserDao; import com.study.dao.UserDaoImpl; import com.study.dao.UserDaoMysqlImpl; import com.study.dao.UserDaoOracleImpl; public class UserServiceImpl implements UserService{ /*这时就会出现由于客户的需求修改原有的代码的问题(程序适应不了用户的变更)*/ /*如何解决这一问题!思路:程序不动让客户端增调用(设计模式==接口思想)*/ private UserDao userDao; //利用set进行动态实现值得注入! public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void getUser() { userDao.getUser(); } }
在Test类用户端创建对象
-
import com.study.dao.UserDaoImpl; import com.study.dao.UserDaoMysqlImpl; import com.study.dao.UserDaoOracleImpl; import com.study.service.UserService; import com.study.service.UserServiceImpl; //测试类 public class MyTest { public static void main(String[] args) { //调用真实的业务(用户实际调用的是业务层,dao层不需要接触) UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDao(new UserDaoOracleImpl()); //输出dao层的真正执行方法不会执行Service层,Service层只做的是调用dao层去查询结果 userService.getUser(); } }
输出:MySql获取用户数据!
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求修改源代码!如果程序代码量十分大,修改一次的成本代价昂贵!
主观性发生改变:
- 之前,程序是主动创建对象!控制权在程序员手上!
- 使用set注入后,程序不再具有主动性,而是变成了被动的接受对象!这样就控制反转思想!
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了,系统的耦合性大大降低了,可以更加专注的在业务的实现上(直接在Dao层实现业务横向扩展,不用再管service层,在用户端直接创建对象调用)!这是IOC的原型
IoC:Inverse of Control(控制反转)
-
读作“反转控制”,更好理解,不是什么技术,而是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
-
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;
谁控制谁?
- 当然是IoC 容器控制了对象;
控制什么?
-
那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
-
正控:若要使用某个对象,需要自己去负责对象的创建
-
反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架;反转则是由容器来帮忙创建及注入依赖对象。
为何是反转?
- 因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
哪些方面反转了?
- 依赖对象的获取被反转了。
3、IOC本质
控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC 的一种方法,也有人认为DI只是IOC的另一种说法。没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IOC是Spring框架的核心内容,使用多种方式完美的实现IOC,可以使用XML配置,也可以使用注解,新版本的Sping也可以零配置实现IOC。
作用:
- 把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
- IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
- IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
4、DI依赖注入
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
目的:
- 并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。
谁依赖于谁:
- 当然是应用程序依赖于IoC容器;
为什么需要依赖:
- 应用程序需要IoC容器来提供对象需要的外部资源;
谁注入谁:
- 很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
注入了什么:
- 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器,程序使用时再从IOC容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,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"> <!--使用Spring来创建对象,在Spring这些都成为Bean 类型 变量名 = new 类型(); Hello hello = new Hello() id = 变量名 class = new的对象 property 相当于给对象中的属性设置一个值 —>对应的是set方法 没有set对应的方法是会报错的 在配置文件加载时,容器中的所有对象已经初始化了 --> <bean id="helloSpring" class="com.study.dao.HelloSpring"> <property name="name" value="Spring"></property> </bean> </beans>
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式,在Sping中实现控制反转的是IOC容器,其实实现方法是依赖注入(Dependency Injection,DI)。
5、IOC创建对象案例:
- 1、通过无参构造方法创建对象,默认的!
package com.study.dao; public class HelloSpring { private String name; //使用无参构造 public HelloSpring() { System.out.println("我是无参构造"); } //使用有参构造 public HelloSpring(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "HellO!{"+ "name'"+name+'\''+ '}'; } }
public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象! ApplicationContext context = new FileSystemXmlApplicationContext("spring-02-helloSpring/src/beans.xml"); //我们的对象现在都在Spring中管理了,我们要是用,直接去里面取出来就可了! HelloSpring hs= context.getBean("helloSpring", HelloSpring.class); System.out.println(hs.toString()); } }
beans.xml配置如上
-
2、有参构造方法创建对象
- 第一种下标赋值
<bean id="helloSpring" class="com.study.dao.HelloSpring"> <!--<property name="name" value="Spring"></property>--> <!--有参构造使用第一种下标赋值--> <constructor-arg index="0" value="yyb"></constructor-arg> </bean>
- 第二种使用通过类型(不建议使用)
<bean id="helloSpring" class="com.study.dao.HelloSpring"> <!--有参构造使用第二种类型赋值--> <constructor-arg type="java.lang.String" value="yyb2"></constructor-arg> </bean>
- 第三种通过参数名赋值
<bean id="helloSpring" class="com.study.dao.HelloSpring"> <!--有参构造使用第三种通过参数名赋值--> <constructor-arg name="name" value="yyb3"></constructor-arg> </bean>
-
3.工厂创建对象
- 工厂类:非静态方法创建对象
1、 添加创建工厂类
package com.study.dao; //创建工厂类 public class ObjectFactory { //实例方法创建对象 public HelloSpring getInstance(){ return new HelloSpring("调用实例方法"); } public static HelloSpring getStaticInstance(){ return new HelloSpring("调用静态方法"); } }
</bean> <!--工厂类先创建对象--> <bean id="factory" class="com.study.dao.ObjectFactory"></bean> <!--再创建HelloSpring类对象--> <bean id="hellospring" factory-bean="factory" factory-method="getInstance"></bean> </beans>
public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象! ApplicationContext context = new FileSystemXmlApplicationContext("spring-02-helloSpring/src/beans.xml"); //我们的对象现在都在Spring中管理了,我们要是用,直接去里面取出来就可了! HelloSpring hs= context.getBean("helloSpring2", HelloSpring.class); System.out.println(hs.toString()); } }
输出:HellO!{name'调用实例方法'}
-
工厂类静态方法创建对象
<beans> <!--工厂静态方法--> <bean id="staticmethod" class="com.study.dao.ObjectFactory" factory-method="getStaticInstance"> </bean> </beans>
public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象! ApplicationContext context = new FileSystemXmlApplicationContext("spring-02-helloSpring/src/beans.xml"); //我们的对象现在都在Spring中管理了,我们要是用,直接去里面取出来就可了! HelloSpring hs= context.getBean("staticmethod", HelloSpring.class); System.out.println(hs.toString()); } }
输出:HellO!
注意点:
-
使用Spring来创建对象,在Spring这些都成为Bean
-
类型 变量名 = new 类型();
Hello hello = new Hello() -
id = 变量名
class = new的对象 -
property 相当于给对象中的属性设置一个值 —>对应的是set方法
-
没有set对应的方法是会报错的
-
在配置文件加载时,容器中的所有对象已经初始化了