IoC 即 Inversion of Control,中文意思是控制反转。
举一个简单的例子,比如我们想要吃一个面包。在有面包店和无面包店两个情况我们会选择哪一种方式来获取面包呢?我们当然可以选择自己买面粉然后自己制作自己喜欢的口味的面包,但是流程就会比较复杂。其实,我们还可告诉面包店家,告知我们要的口味,由面包店来制作。这样,我们不但可以省去制作面包的过程,还可以吃到我们想要的口味。这就是控制反转。
当某个 Java 对象(如想吃面包的我)调用另外一个 Java 对象(被调用者,即被依赖对象,如面包)时,传统模式是用” new 被调用者“ 的代码方式来创建对象。当 Spring 框架出现后,对象的实现不再由调用者创建,而是由 Spring 容器(如面包店)来创建。Spring 容器会负责控制程序之间的关系(我和面包的关系),而不是由调用者(我)的代码程序直接控制。这样,控制权由调用者转移到 Spring 容器,控制权实现了反转,这就是 Spring 的控制反转。
待补。
创建对象的时候默认调用无参构造创建。
<bean id="stu" class="com.hedon.Stu"></bean>写一个工厂类,里面有一个静态方法可以生成 UserDao 类型的对象
public class StaticFatory { public static UserDao createUserDao(){ return new UserDaoImpl(); } }配置
Id :唯一标志class:工厂类全限定类名factory-method:指定生产对象的静态方法 <!--静态工厂方法创建对象--> <bean id="userDao" class="com.factory.StaticFatory" factory-method="createUserDao"></bean>写一个工厂类,里面的有一个方法可以生成对象,但是这个方法不是静态的
public class InstanceFactory { public UserDao createUserDao(){ return new UserDaoImpl(); } }配置
因为实例工厂类中的方法不是静态是,所以需要先创建对象,然后才可以来调用方法
<!--需要先注入实例工厂--> <bean id="instanceFactory" class="com.factory.InstanceFactory"></bean> <!--通过实例工厂来调用它里面的方法来创建对象--> <bean id="userDao2" factory-bean="instanceFactory" factory-method="createUserDao"></bean>△ 重点:需要加入 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" xmlns:p="http://www.springframework.org/schema/p"使用 set 注入
<bean id="stu" class="com.hedon.Stu" p:name="课程名"></bean>这个时候,这里的 “后勤部” 会被 dept.name 的 “保安部” 所覆盖。
这里为了能够使用 dept.name,需要在 Emp 类中写上属性 dept 的 getter 方法。
private Dept dept; //getter public Dept getDept(){ return dept; } //setter public void setDept(Dept dept){ this.dept = dept; } <bean id="emp" class="com.bean.Emp"> <property name="ename" value="小红"></property> <property name="gender" value="女"></property> <!-- 级联赋值 --> <property name="dept" ref="dept"></property> <property name="dept.name" value="保安部"></property> </bean> <bean id="dept" class="com.bean.Dept"> <property name="name" value="后勤部"></property> </bean>如果 list 中放的是对象:
<bean ........> <!--List中放对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <bean id="course1" class="com.atguigu.spring5.Course"> <property name="cname" value="Spring"></property> </bean> <bean id="course2" class="com.atguigu.spring5.Course"> <property name="cname" value="Mybatis"></property> </bean> 映射 map <property name="maps"> <map> <entry key="JAVA" value="java"></entry> <entry key="CPlusPlus" value="C++"></entry> </map> </property> 集合 set <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property>需要加入 util 命名空间:
<?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="book2" class="com.bean.Book"> <property name="bookList" ref="bookList"></property> </bean>创建外部属性文件 jdbc.properties
prop.driverClassName = com.mysql.cj.jdbc.Driver prop.url = jdbc:mysql://localhost:3306/eesy prop.username = root prop.password = rootXML 配置文件中引入 context 命名空间
<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">在 XML 配置文件中引入 jdbc.properties
<context:property-placeholder location="classpath:jdbc.properties"/>配置 Druid 连接池(需要先导入 Druil 相关 jar 包或依赖)
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean>5个相同功能的注解:表示将加上该注解的类扫描并注入到 IoC 容器中。
@Component@Controller@Service@Repository@Mapper1)引入 aop 的 jar 包
2-1)开启组件扫描 —— XML 配置版本
base-package:指定扫描哪个包 <context:component-scan base-package="com.spring5"></context:component-scan> context:exclude-filter:在开启默认过滤器(即扫描所有的注解)的基础上,指定不扫描的注解。 <context:component-scan base-package="com.spring5"> <!--表示不扫描该包下的 @Controller 注解 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> context:include-filter:关闭默认过滤器(即什么都不扫描),指定扫描的注解。 <!--关闭默认过滤器--> <context:component-scan base-package="com.spring5" use-default-filters="false"> <!--扫描 @Controller--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>2-2)开启注解扫描 —— 配置类版本
写一个配置类,加上两个注解:
@Configuration:指明这是一个配置类@ComponentScan(basePackages = {指定要扫描的包}) @Configuration @ComponentScan(basePackages = {"com.spring5.beans","com.spring5.services"}) public class SpringConfig { }3)在类上加注解,这里用 @Component 来演示
value:即配置文件中 bean 标签中的 id 属性,对象的唯一标志。默认为类名首字母小写。 @Component(value = "stu") public class Stu { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Stu{" + "name='" + name + '\'' + '}'; } }4-1)测试 —— XML 版本 ClassPathXmlApplicationContext
@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Stu stu = context.getBean("stu", Stu.class); System.out.println(stu); }4-2)测试 —— 注解版本 AnnotationConfigApplicationContext
@Test public void test(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Stu stu = context.getBean("stu", Stu.class); System.out.println(stu); }5)测试结果
Stu{name='null'}在上面 Stu 实体类中的属性上加上 @Value 进行赋值:
@Component(value = "stu") public class Stu { //① 可以加在属性声明上面 @Value("注入属性") private String name; public String getName() { return name; } //② 也可以加在属性的 set 方法上 public void setName(String name) { this.name = name; } @Override public String toString() { return "Stu{" + "name='" + name + '\'' + '}'; } }再次测试:
Stu{name='注入属性'} Process finished with exit code 0再测试一下:
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Book book = context.getBean("book", Book.class); System.out.println(book); }测试结果:
Book{price=11.1, stu=Stu{name='Stu注入自己的普通属性'}} Process finished with exit code 0在配置文件中定义 bean 的 class 类型就是返回类型。
在配种文件中定义 bean 的 class 类型可以跟返回类型不一样。
创建类 MyBean,把这个类作为工厂 bean,让它实现接口 FactoryBean
//泛型中写这个工厂 bean 要产生的 bean 对象的类型 public class MyBean implements FactoryBean<Course> { //返回的对象 @Override public Course getObject() throws Exception { Course course = new Course(); course.setCname("课程名"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } }注入到 IoC 容器中
<bean id="myBean" class="com.atguigu.spring5.MyBean"></bean>测试
@Test public void testFactoryBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); Course myBean = context.getBean("myBean", Course.class); System.out.println(myBean); }这样一来,我们配置的 bean 类型是 MyBean,但是生成的对象类型是 Course。
<bean>标签中有scope 属性
singleton:单例
设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象。
prototype:多例
设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,在调用 getBean 方法时候创建多实例对象。
request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。
session:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中。
global session:WEB 项目中,应用在 Portlet 环境.如果 没有Portlet 环境那么 globalSession 相当于 session。
之前讲工厂 Bean 的时候实现接口 FactoryBean 中有一个方法叫做 isSingleton(),所以很显然可以通过这个方法来配置单例与多例。
@Override public boolean isSingleton() { return false; }写一个实体类 BeanLife
public class BeanLife { private String name; public void setName(String name) { System.out.println("第2步:为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)"); this.name = name; } public BeanLife() { System.out.println("第1步:通过构造器创建 bean 实例(默认无参数构造)"); } public void initMethod(){ System.out.println("第4步:调用 bean 的初始化的方法(需要进行配置初始化的方法)"); } public void destroyMethod(){ System.out.println("第7步:当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)"); } }配置 BeanLife 及其初始化和销毁方法
<bean id="beanLife" class="com.atguigu.spring5.BeanLife" init-method="initMethod" destroy-method="destroyMethod"> <property name="name" value="生命周期"></property> </bean>加入 Bean 后置处理器,写一个实体类 MyBeanPost 实现接口BeanPostProcessor
public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("第3步:把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("第4步:把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization"); return bean; } }配置 Bean 后置处理器
<bean id="myBeanPost" class="com.atguigu.spring5.MyBeanPost"></bean>测试代码
@Test public void testBeanLift(){ ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); BeanLife beanLift = context.getBean("beanLife", BeanLife.class); System.out.println("第6步:开始使用 bean =》"+beanLift); //需要手动销毁 ((ClassPathXmlApplicationContext)context).close(); }测试结果
第1步:通过构造器创建 bean 实例(默认无参数构造) 第2步:为 bean 的属性设置值和对其他 bean 引用(调用 set 方法) 第3步:把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization 第4步:调用 bean 的初始化的方法(需要进行配置初始化的方法) 第4步:把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization 第6步:开始使用 bean =》com.atguigu.spring5.BeanLife@8e24743 第7步:当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法) Process finished with exit code 0自动装配指的是 Spring 会根据指定装配规则(属性名称或者属性类型)自动将匹配的属性值进行注入。
这里需要注意如果存在两个 bean 是 Dept 类型的,那么就会报错: