- 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" 标签 Listbeans = 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)); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)