前言
Spring框架的核心就是IOC和AOP,本篇文章就讲述一下其中的IOC容器。(这是听尚硅谷课程总结的)
一、IOC概述及底层原理
1.概述和原理
- IOC是控制反转的意思。使用对象时候由主动new对象转换成由外部提供对象,此过程中对象的创建权由程序转移到外部,这种思想叫做控制反转。即把对象创建和对象的调用过程交给spring进行管理。
- 目的:降低耦合度。
- 底层原理:xml配置,反射,工厂模式。
- Spring提供IOC容器两种实现方式(两个接口)
(1)BeanFactory:Spring内部使用的接口,不提倡开发人员使用。特点:加载配置文件时不会创建对象,获取对象时才会创建对象。
(2)ApplicationContext:BeanFactory的子接口,提供了更多更强大的功能,一般由开发人员使用。特点:加载配置文件时会把配置文件里的对象进行创建。
(3)核心:Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的外部
IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为Bean。 - ApplicationContext两个常用的实现类:
(1)FileSystemXmlApplicationContext:绝对路径,从盘符开始算起
(2)ClassPathXmlApplicationContext:相对路径,从项目的src开始算起 - IOC实现:依赖注入,即在容器中建立bean与bean之间的依赖关系的整个过程。
二、思路分析
- IOC管理什么(bean对象)
- 如何告知IOC去管理bean对象(通过配置文件)
- 被管理的对象交给IOC容器,如何获取IOC容器?(接口)
- IOC容器得到后,如何获取bean?(通过ApplicationContext接口的两个实现类的getBean方法获取bean实例创建对象)
三、IOC操作bean管理(基于xml,使用的是IDEA2022.3.3,maven仓库)
首先告诉大家本篇文章的所有使用的spring依赖版本:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>6.0.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.0.9</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.9</version> </dependency> </dependencies>
1.xml实现bean管理
(1)创建对象
在项目的resource资源文件夹创建spring的配置文件,然后添加如下代码:
<bean id="book" class="com.dragon.spring5.Book"></bean>
- id:创建对象的名称
- class:Book类的路径
- 创建对象时默认是执行无参构造函数
(2)注入属性
创建对象后,对象内的属性还没有赋值等等,讲诉三种注入属性得到方式:
- 第一种:set方法注入
这种方法需要类的属性有对应的set方法,如下:
public class Book { private String bname; private String bauthor; public void setBname(String bname) { this.bname = bname; } public void setBauthor(String bauthor) { this.bauthor = bauthor; } public void testbook(){ System.out.println(bname+":"+bauthor); } }
然后在spring配置文件中通过property标签进行属性注入,如下:
<bean id="book" class="com.dragon.spring5.Book"> <property name="bname" value="易筋经"></property> <property name="bauthor" value="达摩老祖"></property> </bean>
然后测试:
<!--加载spring配置文件创建对象--> ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml"); <!--反射--> Book book=context.getBean("book", Book.class); System.out.println(book); book.testbook();
- 第二种:有参构造方法注入
提供有参构造方法:
public class Book { private String bname; private String bauthor; public Book(String bname, String bauthor) { this.bname = bname; this.bauthor = bauthor; } public void testbook(){ System.out.println(bname+":"+bauthor); } }
然后在spring配置文件中,如下:
<bean id="book" class="com.dragon.spring5.Book"> <constructor-arg name="bname" value="易筋经"></constructor-arg> <constructor-arg name="bauthor" value="达摩老祖"></constructor-arg> </bean>
(3)p名称空间注入
set方法、测试的代码跟上诉一样,不再赘诉。
在spring配置文件中添加p名称空间,和配置代码:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!--添加p名称空间--> xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="book" class="com.dragon.spring5.Book" p:bname="九阴神功" p:bauthor="无名氏"></bean></beans>
(4)其他注入
1.注入的属性值是null或者有符号,如下:
<bean id="book" class="com.dragon.spring5.Book"> <property name="bauthor"> <null/> </property> <property name="bname"> <value><![CDATA[<<读者>>]]></value> </property> </bean>
- 有符号:用<![CDATA[带符号的属性值(包括符号)]]>
- 空值:< null />
2.注入外部bean,如下:
spring配置文件:
- ref:引用外部bean的id值
<bean id="userService" class="com.dragon.spring5.service.UserService"> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.dragon.spring5.dao.UserDaoImpl"></bean>
UserService、UserDao、UserDaoImpl、测试类代码:
package com.dragon.spring5.dao; public interface UserDao { public void update(); } ============================================================= package com.dragon.spring5.dao; public class UserDaoImpl implements UserDao{ @Override public void update() { System.out.println("dao update......."); } } ========================================================= package com.dragon.spring5.service; import com.dragon.spring5.dao.UserDao; public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ System.out.println("service add........"); userDao.update(); } } ====================================================== ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml"); UserService userService=context.getBean("userService", UserService.class); userService.add();
3.内部bean注入
不通过ref属性,而是通过嵌套一个bean标签实现
spring配置文件:
<bean id="emp" class="com.dragon.spring5.bean.Emp"> <property name="enanme" value="lucy"></property> <property name="gender" value="女"></property> <property name="dept"> <bean id="dept" class="com.dragon.spring5.bean.Dept"> <property name="dname" value="保安部门"></property> </bean> </property> </bean>
Emp、Dept、测试类代码:
package com.dragon.spring5.bean; public class Emp { private String enanme; private String gender; private Dept dept; public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } public void setEnanme(String enanme) { this.enanme = enanme; } public void setGender(String gender) { this.gender = gender; } public void add(){ System.out.println(enanme+"::"+gender+"::"+dept); } } =================================================================== package com.dragon.spring5.bean; public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } @Override public String toString() { return "Dept{" + "dname='" + dname + '\'' + '}'; } } ========================================================================== ApplicationContext context1=new ClassPathXmlApplicationContext("bean2.xml"); Emp emp=context1.getBean("emp", Emp.class); emp.add();
4.级联赋值
写法一:也就是上面所说的外部bean,通过ref属性来获取外部bean
写法二:
emp类中有ename和dept两个属性,其中dept有dname属性,写法二需要emp提供dept属性的get方法。
<bean id="emp" class="com.dragon.spring5.bean.Emp"> <property name="enanme" value="john"></property> <property name="gender" value="男"></property> <!--写法一--> <property name="dept" ref="dept"></property> <!--写法二--> <property name="dept.dname" value="技术部"></property> </bean> <bean id="dept" class="com.dragon.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
5.注入集合
stu类:
package com.dragon.spring5.collectiontype; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; public class Stu { private String[] courses; private List<String> list; private Map<String,String> maps; private Set<String> sets; private List<Course> courselist; public void setCourselist(List<Course> courselist) { this.courselist = courselist; } public void setSets(Set<String> sets) { this.sets = sets; } public void setCourses(String[] courses) { this.courses = courses; } public void setList(List<String> list) { this.list = list; } public void setMaps(Map<String, String> maps) { this.maps = maps; } public void test(){ System.out.println(Arrays.toString(courses)); System.out.println(list); System.out.println(maps); System.out.println(sets); System.out.println(courselist); } }
Course类:
package com.dragon.spring5.collectiontype; public class Course { private String cname; public void setCname(String cname) { this.cname = cname; } @Override public String toString() { return "Course{" + "cname='" + cname + '\'' + '}'; } }
spring配置文件:
<bean id="stu" class="com.dragon.spring5.collectiontype.Stu"> <property name="courses"> <array> <value>java课程</value> <value>数据库课程</value> </array> </property> <property name="list"> <list> <value>张三</value> <value>小三</value> </list> </property> <property name="maps"> <map> <entry key="JAAVA" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> <!--外部bean注入--> <property name="courselist"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <bean id="course2" class="com.dragon.spring5.collectiontype.Course"> <property name="cname" value="MyBatis框架"></property> </bean>
测试:
ApplicationContext context=new ClassPathXmlApplicationContext("bean3.xml"); Stu stu=context.getBean("stu", Stu.class); stu.test();
集合提取出来注入:
用util标签(注意util标签怎么引入):
Book类:
package com.dragon.spring5.collectiontype; import java.util.List; public class Book { private List<String> list; public void setList(List<String> list) { this.list = list; } public void test(){ System.out.println(list); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:list id="booklist"> <value>易筋经</value> <value>九阳神功</value> <value>九阴真经</value> </util:list> <bean id="book" class="com.dragon.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="booklist"></property> </bean> </beans>
(5)FactoryBean(工厂bean)
普通bean在配置文件中定义的bean类型就是返回类型。而工厂bean在配置文件定义的bean类型可以和返回类型不一样。
下面的MyBean类中实现FactoryBean接口,重写getObject,getObjectType方法。
这里把getObject重写成返回Course对象的方法。
package com.dragon.spring5.factorybean; import com.dragon.spring5.collectiontype.Course; import org.springframework.beans.factory.FactoryBean; public class MyBean implements FactoryBean<Course> { @Override public Course getObject() throws Exception { Course course=new Course(); course.setCname("abc"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } }
spring配置文件:
<bean id="mybean" class="com.dragon.spring5.factorybean.MyBean"></bean>
测试:
注意这里实例化对象不是MyBean类型,是上面getObject方法返回的对象类型。
ApplicationContext context=new ClassPathXmlApplicationContext("bean5.xml"); Course course=context.getBean("mybean", Course.class); System.out.println(course);
2.bean的作用域
- 在Spring中,默认情况下bean创建的是单实例对象:
ApplicationContext context1=new ClassPathXmlApplicationContext("bean4.xml"); Book book1=context1.getBean("book", Book.class); Book book2=context1.getBean("book", Book.class);
可以看出地址一样
- bean有个属性是scope,可以通过设置成singleton或prototype来决定其是单实例还是多实例(1)singleton:默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
(2)prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用getBean方法时创建多实例对象。
<bean id="book" class="com.dragon.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="booklist"></property> </bean>
- scope的值还可以是request、session(大家应该知道这两个值的作用域吧)
3.bean的生命周期
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
下面提供大家测试需要用的类,方便大家理解:
Orders类:
package com.dragon.spring5.bean; public class Orders { private String oname; public Orders() { System.out.println("第一步 执行无参数构造创建bean实例"); } public void setOname(String oname) { this.oname = oname; System.out.println("第二步 调用set方法设置属性值"); } public void initMethod(){ System.out.println("第三步 执行初始化方法"); } public void destoryMethod(){ System.out.println("第五步 bean销毁的方法"); } }
后置处理器:
package com.dragon.spring5.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之前执行的方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之后执行的方法"); return bean; } }
测试:
package com.dragon.spring5.testDemo; import com.dragon.spring5.bean.Orders; import org.springframework.context.support.ClassPathXmlApplicationContext; public class test3 { public static void main(String[] args) { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean6.xml"); Orders orders=context.getBean("orders", Orders.class); System.out.println("第四步 获取创建beam实例对象"); System.out.println(orders); context.close(); } }
spring配置文件:
</bean> <!-- 后置处理器--> <bean id="myBeanPost" class="com.dragon.spring5.bean.MyBeanPost"></bean>
4.xml自动配置属性值
bean标签属性autowire两个常用值:
(1)byName:根据属性名称注入,注入值bean的id值和类属性名称一样
(2)byType:根据属性类型注入
spring配置文件:
(Emp类中有dept属性)
<bean id="emp" class="com.dragon.spring5.autowrite.Emp"> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.dragon.spring5.autowrite.Dept"></bean>
正常情况下属性值需要用property标签注入。
而用autowire,可以自动注入
<bean id="emp" class="com.dragon.spring5.autowrite.Emp" autowire="byName"></bean> <bean id="dept" class="com.dragon.spring5.autowrite.Dept"></bean>
5.外部属性文件操作bean
这里用连接数据库举例:
在resource文件夹内jdbc属性文件(jdbc.properties)
pro.driverClass=com.mysql.jdbc.Driver pro.url=jdbc:mysql://localhost:3306/?(这个根据自己数据库配置) pro.username=root pro.password=root
spring配置文件(注意context配置,这里使用的< context:property-placeholder/ >标签引入属性文件,其中classpath就是配置属性文件的全称):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${pro.driverClass}"></property> <property name="url" value="${pro.url}"></property> <property name="username" value="${pro.username}"></property> <property name="password" value="${pro.password}"></property> </bean> </beans>
四、注解实现bean管理
1.基于注解创建对象
spring提供四种创建对象的注解(下面四个注解说的一般使用在哪层是约定俗成的,其实效果都一样,都可以混用):
- @Component
- @Service:一般用于业务逻辑或Service层
- @Controller:一般用于web层
- @ Repository:一般用于Dao层
步骤:
(1)引入依赖(开头已告知所有maven依赖库)
(2)开启组件扫描:扫描base-package包下所有有注解的类并为其创建对象
<context:component-scan base-package="com.dragon.spring5_1"></context:component-scan>
代表扫描com.dragon.spring5_1下的所有文件,找寻有注解的文件,然后创建实例。
(2)创建类并在类上创建对象注解
@Service(value = "userService") public class UserService { public void add(){ System.out.println("service add......."); } }
测试:
ApplicationContext context=new ClassPathXmlApplicationContext("bean9.xml"); UserService userService=context.getBean("userService",UserService.class); System.out.println(userService); userService.add();
2.组件扫描细节
还有两种用法:
设置不扫描的注解 <context:component-scan base-package="com.dragon.spring5_1"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan> 设置扫描的注解 <context:component-scan base-package="com.dragon.spring5_1" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan>
3.基于注解进行属性注入
- @Autowired:根据属性类型自动装配
- @Qualifier:根据属性名称自动装配,需要和@Autowired一起使用
当遇到一个接口有很多实现类时,只通过@Autowire是无法完成自动装配的,所以需要再使用@Qualifier通过名称来锁定某个类 - @Resource:可以根据类型注入,也可以根据名称注入(不常用)
- @Value:注入属性值
实例:
UserDao、UserDaoImpl、UserService类:
package com.dragon.spring5_1.dao; import org.springframework.stereotype.Repository; public interface UserDao { public void add(); } ================================================== package com.dragon.spring5_1.dao; import org.springframework.stereotype.Repository; @Repository(value = "userDaoImpl1") public class UserDaoImpl implements UserDao{ @Override public void add() { System.out.println("dao add......"); } } ========================================================= package com.dragon.spring5_1.service; import com.dragon.spring5_1.dao.UserDao; 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.Service; import javax.annotation.Resource; @Service(value = "userService") public class UserService { @Value(value = "abc") private String name; @Autowired @Qualifier(value ="userDaoImpl1") private UserDao userDao; public void add(){ System.out.println("service add......."+name); userDao.add(); } }
spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.dragon.spring5_1"></context:component-scan> </beans>
测试:
ApplicationContext context=new ClassPathXmlApplicationContext("bean9.xml"); UserService userService=context.getBean("userService",UserService.class); System.out.println(userService); userService.add();
五、完全注解开发
上面讲诉后,完全注解开发应该是最简洁的。来个实例看一看。
这时要引入配置类来代替spring配置文件,来实现组件扫描不用xml配置,进而实现完全注解开发。
配置类:
package com.dragon.spring5_1.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration//配置类 @ComponentScan(basePackages = {"com.dragon.spring5_1"})//配置扫描的文件 public class SpringConfig { }
测试(UserDao、UserDaoImpl、UserService还是上面基于注解属性注入例子的):
package com.dragon.spring5_1.testDemo; import com.dragon.spring5_1.config.SpringConfig; import com.dragon.spring5_1.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class test2 { public static void main(String[] args) { //注意new的对象变了,变成了AnnotationConfigApplicationContext ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService=context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); } }
总结
以上就是Spring的IOC讲解。