- IoC控制反转,全称Inverse of Control,是一种设计理念
- 由代理人创建与管理对象,消费者通过代理人来获得对象
- IoC的目的是降低程序与程序之间直接耦合
解决什么问题?
对象直接饮用导致对象硬性关联,程序难以扩展。比如:顾客想要吃各种各样的苹果,就需要顾客取世界各地购买苹果,非常麻烦。
加入Ioc容器将对象统一管理,让对象关联变为弱耦合。就像上面的场景:如果出现了摊贩,那么顾客就不用跑到各地去买水果了,而是由摊贩购买好之后,顾客根据自己的需求购买响应的苹果。
DI依赖注入- IoC是设计理念,是现代程序设计遵循的标准,是宏伟目标
- DI(Dependency Injection) 是具体技术实现,是微观实现
- DI在Java中利用反射技术是心啊对象注入(Injection)
- Spring可以从狭义与广义两个角度看
- 狭义角度的Spring是指Spring框架(Spring Fremework)
- 广义角度Spring是指Spring生态体系
- Spring框架是企业开发复杂性的恶一站式解决方案
- SPring框架的核心是IoC容器与AOP面向切面编程
- Spring IoC负责创建与管理系统对象,并在此基础上扩展功能
IoC容器是Spring生态的地基,用于统一创建与管理对象依赖
如上:Spring IoC容器将A的依赖B对象注入进来,使用者只需要从中提取就可以了。
Spring IoC容器职责- 对象的控制权交由第三方统一管理(IoC控制反转)
- 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
- 基于配置提高应用程序的可维护性与扩展性
eg: 比如三个孩子 Lily、Andy、Luna分别喜欢吃甜的、酸的、软的苹果。盘子里有三个苹果:红富士、青苹果、金帅。
那孩子们如何获得喜欢的苹果呢?
Apple.class
package com.imooc.spring.ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public Apple() {
//System.out.println("Apple对象已经创建, " + this);
}
public Apple(String title, String color, String origin) {
this.title = title;
this.color = color;
this.origin = origin;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
Child.class
package com.imooc.spring.ioc.entity;
public class Child {
private String name;
private Apple apple;
public Child() {
}
public Child(String name, Apple apple) {
this.name = name;
this.apple = apple;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Apple getApple() {
return apple;
}
public void setApple(Apple apple) {
this.apple = apple;
}
public void eat() {
System.out.println(name + "吃到了" + apple.getOrigin() + "种植的" + apple.getTitle());
}
}
传统方式:
Application.class
public class Application {
public static void main(String[] args) {
Apple apple1 = new Apple("红富士", "红色", "欧洲");
Apple apple2 = new Apple("青苹果", "绿色", "中亚");
Apple apple3 = new Apple("金帅", "黄色", "中国");
Child lily = new Child("莉莉", apple1);
Child andy = new Child("安迪", apple2);
Child luna = new Child("露娜", apple3);
lily.eat();
andy.eat();
luna.eat();
}
}
输出结果:
但是这样会有一个弊端,如果后面需要更改喜欢的苹果的时候,我们就需要在源代码这里修改。在开发过程中,往往源代码涉及到的内容较多,修改复杂且容易影响其他地方,很容易出错。
使用IoC容器方式:
在resources目录下创建applicationontext.xml文件:
SpringApplication.class
public class SpringApplication {
public static void main(String[] args) {
//创建Spring IoC容器,并根据配置文件在容器中实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = context.getBean("sweetApple", Apple.class);
System.out.println(sweetApple.getTitle());
//从IoC容器中提取beanId=lily的对象
Child lily = context.getBean("lily", Child.class);
lily.eat();
Child andy = context.getBean("andy", Child.class);
andy.eat();
Child luna = context.getBean("luna", Child.class);
luna.eat();
}
}
执行结果:
这样做的好处是,我们涉及到修改时,只需要修改applicationContent.xml中的内容即可.
使用XML方式实现Spring IoC上面说的applicationContext.xml就是使用xml文件方式实现Spring IoC的一种。
实现方式:- 基于构造方法对对象实例化
- 基于动态工厂实例化
- 基于工厂实例方法实例化
- ClassPathXmlApplicationContext
- AnnotationConfigApplicationContext
- WebApplicationContext
在 applicationContext.xml文件:
Apple.class 中修改为:
public Apple() {
System.out.println("Apple对象已创建," + this);
}
打印输出:
带参构造方法applicationContext.xml
Apple.class
public Apple(String title, String origin, String color, Float price) {
System.out.println("通过带参构造方法创建对象, " + this);
this.title = title;
this.color = color;
this.origin = origin;
this.price = price;
}
打印输出:
基于工厂实例化对象 静态工厂
public class AppleStaticFactory {
public static Apple createSweetApple(){
//logger.info("")
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
在applicationContext.xml中
测试:
Apple apple4 = context.getBean("apple4", Apple.class);
System.out.println(apple4.getTitle());
输出:
工厂实例
public class AppleFactoryInstance {
public Apple createSweetApple(){
Apple apple = new Apple();
apple.setTitle("红富士");
apple.setOrigin("欧洲");
apple.setColor("红色");
return apple;
}
}
在applicationContext.xml中
然后测试输出即可.
从IoC容器中提取 Bean方式一:
Apple apple = context.getBean("apple", Apple.class);
方式二:
Apple apple = (Apple)context.getBean("apple");
推荐使用方式一
id和name属性相同点- bean id 与 name 都是设置对象在IoC容器中唯一标识
- 两者在同一个配置文件中都不允许出现重复
- 两者允许在多个配置文件中出现重复,新对象覆盖旧对象
- id要求更为严格,一次只能定义一个对象标识(推荐)
- name更为宽松,一次允许定义多个对象标识
- tips:id与name的命名要求有意义,按驼峰命名书写
路径匹配表达式
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
加载多个配置文件:
String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
//初始化IoC容器并实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
路径表达式
对象依赖注入
依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作
- 基于setter方法注入对象
- 基于构造方法注入对象
依赖注入的优势
举例:
applicationContext-dao.xml
applicationContext-serice.xml
BookDao
public interface BookDao {
public void insert();
}
BookDaoImpl
public class BookDaoImpl implements BookDao {
public void insert() {
System.out.println("向MySQL Book表插入一条数据");
}
}
BookService
public class BookService {
private BookDao bookDao ;
public void purchase(){
System.out.println("正在执行图书采购业务方法");
bookDao.insert();
}
public BookDao getBookDao() {
return bookDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
BookShopApplication
public class BookShopApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext-*.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.purchase();
}
}
输出:
优点: 比如数据库由MySQL迁移到Oracle.这时候只需要将BookDao这个Bean的class更改为Oracle即可. 注入集合对象 注入List 注入set注入map 注入Properties
Properties中的key和value只能是String类型
查看容器内对象//获取容器内所有beanId数组
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName:beanNames){
System.out.println(beanName);
System.out.println("类型:" + context.getBean(beanName).getClass().getName());
System.out.println("内容:" + context.getBean(beanName));
}
Computer computer = context.getBean("com.imooc.spring.ioc.entity.Computer", Computer.class);
当有多个相同类的Bean时:
Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#0", Computer.class);
Computer computer1 = context.getBean("com.imooc.spring.ioc.entity.Computer#1", Computer.class);
Bean对像的作用域及生命周期
bean scope属性
- bean scope属性用于决定对象何时被创建与作用范围
- bean scope配置将影响容器内对象的数量
- 默认情况下bean会在IoC容器创建后自动实例化,全局唯一
singleton在容器是单例多线程执行,存在线程安全风险
在单线程下:
当在多线程中:在A用户操作了a.setNum(1)之后,在另一个线程,B用户操了a.setNum(2) .这个时候,A用户打印a.num 就会出现 2,和A用户设置值不同.
prototype多例prototype在容器中多实例,占用更多资源,不存在线程安全问题
singleton和prototype对比 bean生命周期细节调整
- prototype使对象创建与init_method延迟至执行业务
- prototype使对象不再受IoC容器管理,不再触发destroy-method
- 延迟加载lazy-init属性可让对象创建与初始化延迟到执行代码阶段
singleton的初始化:
applicationContext.xml中:
在SpringApplication中添加:
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}
打印输出:
也就是说在IoC容器初始化的时候,为我们创建了bean.
prototype初始化:
applicationContext.xml中:
然后在SpringApplication中测试:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println("======IoC容器已初始化=======");
UserDao userDao1 = context.getBean("userDao", UserDao.class);
}
输出为:
也就是说,在IoC容器创建的时候,并没有为我们初始化bean对像,而是在我们获取对象的时候,才初始化.singleton模式的初始化顺序跟书写顺序一致。
下面会初始化两个bean对象.
在多数情况下,Dao层,server层,control层都是单例的。
实现极简IoC容器目录结构:
Apple类
package com.imooc.spring.ioc.entity;
public class Apple {
private String title;
private String color;
private String origin;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
然后在applicationContext.xml中添加bean的信息:
自定义记载配置文件的方法:
接口:ApplicationContext
package com.imooc.spring.ioc.context;
public interface ApplicationContext {
public Object getBean(String beanId);
}
实现类ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map iocContainer = new HashMap();
public ClassPathXmlApplicationContext() {
try {
String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
// 进行URL解码
filePath = new URLDecoder().decode(filePath, "UTF-8");
SAXReader reader = new SAXReader();
document document = reader.read(new File(filePath));
// 读取 "bean" 标签
List beans = document.getRootElement().selectNodes("bean");
for (Node node : beans) {
Element ele = (Element) node;
String id = ele.attributevalue("id");
String className = ele.attributevalue("class");
Class c = Class.forName(className);
Object obj = c.newInstance();
List properties = ele.selectNodes("property");
for (Node p : properties) {
Element property = (Element) p;
String propName = property.attributevalue("name");
String propValue = property.attributevalue("value");
// set方法
String setMethodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
System.out.println("准备执行" + setMethodName + "方法注入数据");
Method setMethod = c.getMethod(setMethodName, String.class);
setMethod.invoke(obj, propValue);//通过setter方法注入数据
}
iocContainer.put(id, obj);
}
System.out.println(iocContainer);
System.out.println("IOC容器初始化完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public Object getBean(String beanId) {
return iocContainer.get(beanId);
}
}
然后进行测试:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext();
Apple apple = (Apple)context.getBean("sweetApple");
System.out.println(apple);
}
}
执行结果:
使用注解方式实现Spring IoC
基于注解的优势
- 摆脱繁琐的XML形式的bean与依赖注入配置
- 基于“声明式”的原则,更适合轻量级的现代企业应用
- 让代码可读性变得更好,研发人员拥有更好的开发体验
- 组件类型注解 - 声明当前类的功能与职能
- 自动装配注解 - 根据属性特种自动注入对象
- 元数据注解 - 更细化的辅助IoC容器管理对象的注解
- @Compponent : 组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化
- @Controller : 语义注解,说明当前类是MVC应用中的控制器类
- @Service. : 语义注解,说明当前类是Service业务服务类
- @Repository. : 语义注解,说明当前类是用于业务持久层,通常描述对应Dao类
两类自动装配注解
- 按类型装配
- @Autowired : 按容器内对象类型动态注入属性,由Spring机构提供
- @Inject : 基于JSR-330(Dependency Injection for Java)标准,其他同@Autowired,但不支持required属性
- 按名称装配
- @Named : 与@Inject配合使用,JSR-330规范,按属性名自动装配属性
- @Resource :基于JSR-330规范,优先按名称、再按类型智能匹配
@Value 的读取属性文件
@Value("com.imooc")
private String config;
就是相当于在初始化的时候,config的值为"com.inooc",主要用户加载配置文件中的数据: @Value("${config}")
使用Java Config方式实现Spring IoC基于Java Config的优势
- 完全摆脱XML的束缚,使用独立Java类管理对象与依赖
- 注解配置相对分散,利用Java Config可对配置集中管理
- 可以在编译时进行依赖检查,不容易出错
举例:
package com.imooc.spring.ioc;
import com.imooc.spring.ioc.controller.UserController;
import com.imooc.spring.ioc.dao.EmployeeDao;
import com.imooc.spring.ioc.dao.UserDao;
import com.imooc.spring.ioc.service.UserService;
import org.springframework.context.annotation.*;
@Configuration //当前类是一个配置类,用于替代applicationContext.xml
@ComponentScan(basePackages = "com.imooc")
public class Config {
@Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
public UserDao userDao(){
UserDao userDao = new UserDao();
System.out.println("已创建" + userDao);
return userDao;
}
@Bean //Java Config利用方法创建对象,将方法返回对象放入容器,beanId=方法名
@Primary
public UserDao userDao1(){
UserDao userDao = new UserDao();
System.out.println("已创建" + userDao);
return userDao;
}
@Bean
//先按name尝试注入,name不存在则按类型注入
public UserService userService(UserDao udao , EmployeeDao employeeDao){
UserService userService = new UserService();
System.out.println("已创建" + userService);
userService.setUserDao(udao);
System.out.println("调用setUserDao:" + udao);
userService.setEmployeeDao(employeeDao);
return userService;
}
@Bean //
@Scope("prototype")
public UserController userController(UserService userService){
UserController userController = new UserController();
System.out.println("已创建" + userController);
userController.setUserService(userService);
System.out.println("调用setUserService:" + userService);
return userController;
}
}
测试:
public class SpringApplication {
public static void main(String[] args) {
//基于Java Config配置IoC容器的初始化
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
System.out.println("=========================");
String[] ids = context.getBeanDefinitionNames();
for(String id : ids){
System.out.println(id + ":" + context.getBean(id));
}
}
}



