Java之Spring5:IOC容器 | 您所在的位置:网站首页 › factorybeanpostprocessor › Java之Spring5:IOC容器 |
IOC容器什么是IOC容器IOC容器中的Bean小试牛刀搭建Spring5环境写一个HelloWorldIOC底层原理IOC的Bean管理基于 xml 方式创建对象基于 xml 方式的属性注入set方法注入构造器注入基于XML方式注入其他类型的属性注入null注入特殊符号注入引用数据类型使用外部bean使用内部bean级联赋值注入集合类型设置集合属性对象类型的值提取集合类型属性的注入部分普通bean和工厂beanbean的作用域bean的生命周期XML的自动装配外部属性文件基于注解方式创建对象基于注解方式注入属性@Autowired@Qualifier@Resource@Value完全注解开发
什么是IOC容器
IOC全称Inversion of Control,直译为控制反转,它是具有依赖注入功能的容器,负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。在Spring中BeanFactory是IOC容器的实际代表者。使用IOC的目的是为了降低耦合度。 IOC容器中的BeanBean就是由Spring容器初始化、装配及管理的对象。那IOC怎样确定如何实例化Bean、管理Bean之间的依赖关系以及管理Bean呢?先让我们来做个简单的例子。 小试牛刀 搭建Spring5环境打开Inteillj然后new一个Java项目: 导入相关的jar包: 写一个HelloWorld写一个简单的Person类: package com.jackma.spring5;public class Person {public void sayHello(){System.out.println("Hello World");} }创建一个xml文件,用来告诉Spring的IOC容器应该如何创建并组装Bean,其中id是一个Bean的唯一标识,class表示类全路径: 然后写一个单元测试方法: public class MySpringTest {@Testpublic void test(){ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");Person person = context.getBean("person", Person.class);person.sayHello();} }测试: IOC底层原理先来看个实例:假设有两个类,UserService和UserDao,UserDao中有一个add()方法,我们要在UserService调用它,那么使用原始的方式是这样实现的: public class UserDao {public void add(){System.out.println("add.......");} } public class UserService {public void execute(){UserDao userDao = new UserDao();userDao.add();} }但原始的方法会存在一个问题,即耦合度太高了,UserService和UserDao关系过于紧密,解决方法是提供一个工厂类,把new userDao的工作交给工厂类,实现解耦: public class UserFactory {public static UserDao getuserDao(){return new userDao();} } public class UserService {public void execute(){UserDao userDao = UserFactory.getuserDao();userDao.add();} }Spring的IOC的底层原理使用到了xml解析、工厂模式和反射: 第一步先配置xml文件: 第二步是Spring会根据User类和userDao类提供一个UserFactory类,且类中根据反射来实现创建userDao对象: public class UserFactory {public static UserDao getuserDao(){String classValue = class属性值; // 该值通过xml解析得到Class clazz = Class.forName(classValue);return (UserDao)clazz.newInstance();} } IOC的Bean管理Bean管理指的是Spring创建对象和Spring注入属性两个操作,实现方式有两种: ①:基于 xml 配置文件方式实现 ②:基于注解方式实现 基于 xml 方式创建对象像上面的HelloWorld例子使用的就是 xml 方式创建对象,在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建,且创建对象时候,默认也是执行无参数构造方法完成对象创建。 基于 xml 方式的属性注入基于 xml 方式的属性注入有两种方式:set方法注入和构造器注入,还是拿User类来举例 。 set方法注入先在User类中添加两个属性,然后记得要提供对应的set方法: public class User {private int id;private String name;public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}public void add(){System.out.println("add......");} }然后在配置文件中添加bean: 然后测试: @Testpublic void test1(){ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");User user = context.getBean("user", User.class);System.out.println(user);user.add();} 构造器注入要在User类中添加构造器方法: package com.jackma.spring5;public class User {private int id;private String name;public User() {}public User(int id, String name) {this.id = id;this.name = name;}// public void setId(int id) { // this.id = id; // } // // public void setName(String name) { // this.name = name; // }@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}public void add(){System.out.println("add......");} }测试代码同上,结果: 基于XML方式注入其他类型的属性 注入null使用set方法注入为上面的name属性注入null,那么就要修改xml文件(注意User类要提供set方法): // 就是这个测试代码同上,结果: 注入特殊符号使用set方法注入为上面的name属性注入含有特殊符号的数据,那么就要修改xml文件,使用,这样它就会将内容之间输出: // 就是这个测试代码同上,结果: 注入引用数据类型看一个案例:有一个公司Company,有一个部门类Dept和一个员工类Emp,Emp中有一个Dept类型的属性,用来表示员工的部门: Dept: package com.jackma.spring5.company; // 部门类 public class Dept {private String dname;public void setDname(String dname) {this.dname = dname;}@Overridepublic String toString() {return "Dept{" +"dname='" + dname + '\'' +'}';} }Emp: package com.jackma.spring5.company;// 员工类 public class Emp {private String ename;private String gender;//员工属于某一个部门,使用对象形式表示private Dept dept;public void setDept(Dept dept) {this.dept = dept;}public void setEname(String ename) {this.ename = ename;}public void setGender(String gender) {this.gender = gender;}public Dept getDept() {return dept;}@Overridepublic String toString() {return "Emp{" +"ename='" + ename + '\'' +", gender='" + gender + '\'' +", dept=" + dept +'}';} } 使用外部beanxml中添加外部bean: // 外部bean测试: @Testpublic void test3(){// 测试内部beanApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");Emp emp = context.getBean("emp", Emp.class);System.out.println(emp);} 使用内部beanxml中添加内部bean: // 内部bean测试代码同上,结果: 级联赋值先关联(外部bean或者内部bean),后赋值:先使用外部bean关联,然后赋值为首席执行官 测试代码同上,结果: 注入集合类型注入集合类型包括:数组、List、Set、Map。使用Stu类来演示: 第一步是添加属性,并提供对应set方法: package com.jackma.spring5.collectiontype;import java.util.List; import java.util.Map; import java.util.Set;public class Stu{private String[] course;private List list;private Set set;private Map maps;public void setCourse(String[] course) {this.course = course;}public void setList(List list) {this.list = list;}public void setSet(Set set) {this.set = set;}public void setMaps(Map maps) {this.maps = maps;}public void test(){// 测试类System.out.println(Arrays.toString(course));System.out.println(list);System.out.println(set);System.out.println(maps);} }第二步是在Spring配置文件中配置bean: JavaSEJavaWebJavaEE语文数学英语早上中午晚上测试: 设置集合属性对象类型的值先创建一个类Course表示一个学生学的课程 package com.jackma.spring5.collectiontype;public class Course {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Course{" +"name='" + name + '\'' +'}';} }然后Stu类中添加一个存放Course类型数据的属性List,然后提供set方法: // 学生所学的多门课程private List list2;public void setList2(List list2) {this.list2 = list2;// 用来测试public void test1(){System.out.println(list2);} }现在我们要对List中Course类型的对象进行赋值,xml文件配置: 测试: @Testpublic void test5(){// 测试设置集合属性对象类型的值ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");Stu stu = context.getBean("stu", Stu.class);stu.test1();} 提取集合类型属性的注入部分先写一个book类: package com.jackma.spring5.collectiontype;import java.util.List; import java.util.Set;public class Book {private List book;private List book2;public void setBook(List book) {this.book = book;}public void setBook2(List book2) {this.book2 = book2;}public void test(){System.out.println("bean1" + book);}public void test2(){System.out.println("bean2:" + book2);}}步骤: 在Spring配置文件中引入名称空间util: 使用util标签完成提取list集合类型属性的提取(相当于是一个公共的,可以注入到多个List类型的bean中),下面把提取出来的booklist注入到list和list2两个bean中: 语文数学英语测试: @Testpublic void test5(){// 测试设置集合属性对象类型的值ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");Book book = context.getBean("list:", Book.class);book.test();Book book2 = context.getBean("list2", Book.class);book2.test2();} 普通bean和工厂bean什么是普通 bean?前面我们在配置文件中定义的bean就是普通bean,其创建的类型就是返回类型: 而工厂 bean就是在配置文件中定义bean的类型可以和返回类型不一样,下面来举个例子: 先新建一个包factorybean,然后在包里新建一个类Mybean: package com.jackma.spring5.factorybean;public class Mybean {}改之前先看一下使用普通bean的结果会是什么样子,创建xml文件(注意这里写的类型是Mybean): 测试和结果: @Testpublic void test6(){// 测试工厂beanApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");Mybean mybean = context.getBean("myBean", Mybean.class);System.out.println(mybean);}可以看到这里是一个Mybean类型的,接下来把它改成Coursr类型的。 让这个类作为工厂bean,实现接口FactoryBean,并实现 接口里面的方法,在实现的方法中定义返回的bean类型: package com.jackma.spring5.factorybean;import com.jackma.spring5.collectiontype.Course; import org.springframework.beans.factory.FactoryBean;public class Mybean implements FactoryBean {// 定义返回bean的类型@Overridepublic Course getObject() throws Exception {Course course = new Course();course.setName("newbean");return course;}@Overridepublic Class getObjectType() {return null;}@Overridepublic boolean isSingleton() {return false;} }修改测试代码: @Testpublic void test6(){// 测试工厂beanApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");Course course = context.getBean("myBean", Course.class);System.out.println(course);} bean的作用域 在Spring里,我们可以设置bean是单实例还是多实例。默认为单实例。 拿前面那个Book类来测试: // 测试单实例多实例ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");Book book1 = context.getBean("list", Book.class);Book book2 = context.getBean("list", Book.class);System.out.print("book1 == book2 ? ");System.out.println(book1 == book2);System.out.println(book1);System.out.println(book2);可以看到两个book的地址相同,就说明是单实例,那么如何改成多实例?bean 标签里面有属性scope来实现: 可以看到有两个值,其中singleton表示是单实例对象,是默认值;而prototype表示是多实例对象。那么修改后结果就变成: 注意事项:设置scope值是 singleton时候,加载spring配置文件时候就会创建单实例对象。 设置scope值是prototype时候,不是在加载spring配置文件时候创建对象,而是在调用getBean方法时候创建多实例对象。 bean的生命周期什么是生命周期?就是从对象创建到对象销毁的过程,bean的生命周期如下: 通过构造器创建bean实例(无参数构造)为bean的属性设置值和对其他bean引用(调用 set 方法)调用bean的初始化的方法(需要进行配置初始化的方法)获取到了创建的bean实例对象(对象获取到了)当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)创建一个Orders类来演示bean的生命周期: package com.jackma.spring5.bean;public class Orders {private String oname;public Orders() {System.out.println("第一步,执行了无参构造函数,创建实例");}public void setOname(String oname) {this.oname = oname;System.out.println("第二步,set注入属性");}public void initMethod(){System.out.println("第三步,调用bean的初始化的方法");}public void destroyMethod(){System.out.println("第五步,销毁bean");} }需要在配置文件中配置bean的初始化的方法和bean的销毁的方法init-method和destroy-method: bean的销毁的方法还需要自行调用: @Testpublic void test7(){// 测试生命周期ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");Orders orders = context.getBean("orders", Orders.class);System.out.println("第四步,获取到了创建的bean实例对象:");System.out.println(orders);// 手动让bean实例销毁context.close();}以上是bean基本的生命周期,总共有5步,但如果添加了bean的后置处理器之后,还会有两个步骤,即生命周期变成7步: 通过构造器创建bean实例(无参数构造)为bean的属性设置值和对其他bean引用(调用 set 方法)把bean实例传递bean后置处理器的方法postProcessBeforeInitialization调用bean的初始化的方法(需要进行配置初始化的方法)bean实例传递bean后置处理器的方法postProcessAfterInitialization获取到了创建的bean实例对象(对象获取到了)当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)怎么添加bean的后置处理器?首先要创建一个类来实现BeanPostProcessor接口: package com.jackma.spring5.bean;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor;public class MyBeanPost implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("在初始化之前执行的方法");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("在初始化之后执行的方法");return bean;} }然后在配置文件中配置后置处理器: 测试代码同上,结果: XML的自动装配什么是自动装配?根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。举例: 创建一个包autowire,创建一个Emp类和一个Dept类:Emp: package com.jackma.spring5.autowire;public class Emp{private Dept dept;public void setDept(Dept dept) {this.dept = dept;}@Overridepublic String toString() {return "Emp{" +"dept=" + dept +'}';}public void test(){System.out.println(this);} }Dept: package com.jackma.spring5.autowire;// 部门类 public class Dept {@Overridepublic String toString() {return "Dept{}";} } 没设置自动装载前的xml文件是这样的:测试和结果: @Testpublic void test8(){// 测试自动装载ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");Emp emp = context.getBean("emp", Emp.class);emp.test();}下面来配置自动装载,配置文件中的bean标签有一个属性autowire,可以用来配置自动装配: autowire 属性常用两个值:byName根据属性名称注入 ,注入值bean的id值和类属性名称一样;byType根据属性类型注入(如果使用byType,那么相同类型的bean不能定义多个,不然就不知道是哪一个了): 测试和结果: 外部属性文件应用场景:假设要创建一个数据库的连接池,按照以往的方法创建,假如我们的用户名或者密码之类的信息要修改,那么同时就要对xml文件中的bean进行修改: -->-->-->-->改进方法是创建一个properties外部属性文件,然后把信息注入到xml文件中,这样在修改信息时只需要修改properties文件即可: 首先提供properties文件: 然后需要在xml文件中引入context名称空间: 基于注解方式创建对象Spring针对Bean管理中创建对象提供的注解: @Component@Service@Controller@Repository注解方式创建对象的步骤: 引入AOP依赖。 开启组件扫描:扫描类中的注解,需要用到context,如果扫描多个包,多个包使用逗号隔开,或者直接写扫描包的上层目录。 新建一个类,在类上面添加创建对象注解 package com.jackma.spring5.service;import org.springframework.stereotype.Component;// 注解里的value相当于bean里的id值,也可以不写,若不写则默认为首字母小写的类名 @Component(value = "userService") // public class UserService {public void add(){System.out.println("Service add......");} }测试和结果: @Testpublic void test9(){// 测试自动装载ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");UserService userservice = context.getBean("userService", UserService.class);userservice.add();}开启组件扫描时默认扫描包中所有的类,但也可以进行配置,使得它扫描指定的类: context标签中有属性use-default-filters,true表示使用默认的扫描,而false表示不使用默认的扫描,然后在下面自己设置扫描的方法: context:include-filter:设置扫描哪些内容;context:exclude-filter:设置哪些内容不进行扫描,type表示根据什么来扫描,这里我设置成按照注解来扫描,然后expression选择注解的类型,我在这是扫描注解类型是Component的,不扫描Controller类型的。 此外,type的类型还有: 基于注解方式注入属性和上面的创建对象一样,Spring针对Bean管理中注入属性提供的注解有: @Autowired:根据属性类型进行自动装配@Qualifier:根据名称进行注入@Resource:可以根据类型注入,可以根据名称注入@Value:注入普通类型属性实例:有一个ManService类,一个ManDaoImpl类实现ManDAO接口和它里面的upDate()方法,现在要在ManService类中调用这个方法: 步骤: 在Man类和ManDaoImpl类添加创建对象的注解在service中注入dao对象,在service类添加dao类型属性,在属性上面使用注解,不用提供set方法 @AutowiredManDao 接口: package com.jackma.spring.dao;public interface ManDao {public void upDate(); }接口实现类ManDaoImpl : package com.jackma.spring.dao;import org.springframework.stereotype.Repository;@Repository(value = "manDaoImpl") public class ManDaoImpl implements ManDao {@Overridepublic void upDate() {System.out.println("dao upDate......");} }ManService : package com.jackma.spring;import com.jackma.spring.dao.ManDao; import com.jackma.spring.dao.ManDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository;@Repository(value = "manService") public class ManService {// 定义DAO类型属性,添加注入属性注解// 不用提供set方法@Autowired // 根据类型注入ManDao manDao = new ManDaoImpl();public void add(){System.out.println("service add......");manDao.upDate();} }测试: @Testpublic void test10(){// 测试注解注入属性ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");ManService manService = context.getBean("manService", ManService.class);manService.add();} @Qualifier这个@Qualifier 注解的使用,和上面@Autowired 一起使用。因为万一ManDao有多个实现类,而仅仅使用@Autowired的话它不知道是哪个实现类,因此可以使用@Qualifier来指定特定名字的实现类。 package com.jackma.spring;import com.jackma.spring.dao.ManDao; import com.jackma.spring.dao.ManDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Repository;@Repository(value = "manService") public class ManService {// 定义DAO类型属性,添加注入属性注解// 不用提供set方法@Autowired // 根据类型注入@Qualifier(value = "manDaoImpl")ManDao manDao = new ManDaoImpl();public void add(){System.out.println("service add......");manDao.upDate();} } @Resource结合了上两个注解的效果: package com.jackma.spring;import com.jackma.spring.dao.ManDao; import com.jackma.spring.dao.ManDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Repository;import javax.annotation.Resource;@Repository(value = "manService") public class ManService {// 定义DAO类型属性,添加注入属性注解// 不用提供set方法 // @Autowired // 根据类型注入 // @Qualifier(value = "userDaoImpl") // @Resource // 根据类型注入@Resource(name = "manDaoImpl") // 根据名称注入ManDao manDao = new ManDaoImpl();public void add(){System.out.println("service add......");manDao.upDate();} } @Value注入普通类型属性: package com.jackma.spring;import com.jackma.spring.dao.ManDao; import com.jackma.spring.dao.ManDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository;import javax.annotation.Resource;@Repository(value = "manService") public class ManService {// 定义DAO类型属性,添加注入属性注解// 不用提供set方法 // @Autowired // 根据类型注入 // @Qualifier(value = "userDaoImpl") // @Resource // 根据类型注入@Resource(name = "manDaoImpl") // 根据名称注入ManDao manDao = new ManDaoImpl();@Value("马保国")private String name;public void add(){System.out.println("service add......");manDao.upDate();System.out.println("name:" + name);} }测试代码相同: 完全注解开发完全注解开发,即使用一个配置类来替代 xml 配置文件。 创建配置类,替代 xml 配置文件: package com.jackma.spring;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration;@Configuration @ComponentScan(basePackages = "com.jackma.spring") public class SpringConfig {}测试: @Testpublic void test11(){// 测试纯注解开发// 加载配置类ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);ManService man = context.getBean(ManService.class);man.add();} |
CopyRight 2018-2019 实验室设备网 版权所有 |