IOC (Inversion of Control)
控制反转:对象之间的关系不在由传统的程序来控制,而是由spring容器来统一控制着这些对象的创建、协调、销毁,而对象只需要来完成业务逻辑即可。
1. 环境搭建
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
向容器中注册一个person对象,spring容器将自动创建这个对象
id是对象的唯一标识,class是组件的全类名
-->
<bean id="person01" class="bean.Person">
<!--
为属性赋值:
属性的值获取不是根据属性的名字,而是set方法去掉,set后的属性值。
-->
<property name="id" value="1001"></property>
<property name="name" value="mahao"></property>
<property name="age" value="18"></property>
<property name="birth" value="Sun Sep 15 21:28:04 CST 2019"></property>
<property name="noFiled" value="属性值"></property>
</bean>
</beans>
package chapter1;
import bean.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* spring环境测试:
* <p>
* 1.导入依赖,四个主要依赖
* 2.配置文件,将bean添加容器
* 3.依赖注入,获取对象
* <p>
* 案例:
* 编写配置文件,将Person注入到spring管理中,之后从容器中获取。
*
* @author: mahao
* @date: 2019/9/15
*/
public class Hello {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("chapter1.xml");
Person person01 = (Person) applicationContext.getBean("person01");
System.out.println("first get person01 : " + person01.toString());
person01.setName("lishuo");
Person person02 = (Person) applicationContext.getBean("person01");
System.out.println("second get person01 after setName :" + person02.toString());
System.out.println("==? " + (person01 == person02));
}
/*
验证组件的属性值是通过set方法获取的
属性值
first get person01 : Person{id=1001, name='mahao', age=18, birth=Mon Sep 16 11:28:04 CST 2019}
second get person01 after setName :Person{id=1001, name='lishuo', age=18, birth=Mon Sep 16 11:28:04 CST 2019}
==? true
结果得知:
1.组件的属性是根据setXXX区分的,XXX是属性,而不是属性
2.容器会在初始化的时候,创建对象,可以根据在组件的构造方法中打印日志
3. 同一个组件在容器中是单例的。
*/
}
2. 属性注入
<!--===============属性赋值================-->
<!--
实验3:
通过构造器为bean的属性赋值(index,type属性介绍)(测试)
通过p名称空间为bean赋值
实验4:正确的为各种属性赋值
测试使用null值 、
引用类型赋值(引用其他bean、引用内部bean)(测试)
集合类型赋值(List、Map、Properties)、(测试)
util名称空间创建集合类型的bean
级联属性赋值
-->
<bean id="user01" class="bean.User">
<property name="id" value="1001"></property>
<property name="name" value="mahao"></property>
<property name="age" value="18"></property>
<property name="birth" value="Sun Sep 15 21:28:04 CST 2019"></property>
<!--为对象赋值-->
<property name="parent" ref="person01">
<!-- 为对象属性赋值,可以通过ref引用外部组件,或自定义创建一个用bean标签
<bean class="bean.Person"></bean>
-->
</property>
<!--为set赋值-->
<property name="friends">
<set>
<ref bean="person01"></ref>
<bean class="bean.Person"></bean>
</set>
</property>
<!--为map赋值-->
<property name="maps">
<map>
<entry key="key1" value="v1"></entry>
<entry key="key2" value-ref="person02"></entry>
</map>
</property>
<!--为properties赋值-->
<property name="properties">
<props>
<prop key="p01">aa</prop>
<prop key="p02">bb</prop>
<prop key="p03">cc</prop>
</props>
</property>
<!--级联属性的赋值,为parent的name属性赋值为...-->
<property name="parent.name" value="啊哈哈"></property>
</bean>
<!--====================组件之间的依赖关系=====================-->
<!--
实验6:通过继承实现bean配置信息的重用(测试)
实验7:通过abstract属性创建一个模板bean(acstract=true)
实验8:bean之间的依赖(改变bean的创建顺序)
-->
<!--实现bean信息的重用,通过parent属性,将父类bean设置为模版 abstract="true"-->
<bean id="user03" class="bean.User" parent="person02" >
<property name="name" value="我的名字"></property>
</bean>
3. 工厂创建bean
- 简单工厂设计模式
- 工厂方法设计模式
- 使用spring容器提供的工厂接口
<!--====================工厂方式创建bean==========-->
<!--
实验9:测试bean的作用域,分别创建单实例和多实例的bean★(测试)singleton 和 prototype
实验5:配置通过静态工厂方法创建的bean、实例工厂方法创建的bean、(FactoryBean测试)★
-->
<!--实验5:配置通过静态工厂方法创建的bean、实例工厂方法创建的bean、FactoryBean★ -->
<!-- bean的创建默认就是框架利用反射new出来的bean实例 -->
<!-- 工厂模式;工厂帮我们创建对象;有一个专门帮我们创建对象的类,这个类就是工厂
AirPlane ap = AirPlaneFactory.getAirPlane(String jzName);
静态工厂:工厂本身不用创建对象;通过静态方法调用,对象 = 工厂类.工厂方法名();
实例工厂:工厂本身需要创建对象;
工厂类 工厂对象 = new 工厂类();
工厂对象.getAirPlane("张三");
-->
<!-- ☆ ☆ ☆
1. 通过静态工厂的方法创建bean(就是简单工厂设计模式,根据参数,获取不同的实例对象)
创建的组件实例,就是bean。是通过调用实例工厂的方法,根据传入的参数,创建的对象
指定哪个方法是工厂方法
class:指定静态工厂全类名
factory-method:指定工厂方法
constructor-arg:可以为方法传参
-->
<bean id="mysqlObject" class="chapter1.AsimpleFactory.SQLFactory" factory-method="getSqlObject">
<constructor-arg value="mysql"></constructor-arg>
</bean>
<!--2. 通过实例工厂的方法创建bean(实际是工厂方法设计模式) ☆ ☆ ☆ -->
<!--2.1 先指定工厂的实例-->
<bean id="appleFactory" class="chapter1.BfactoryMethod.AppleFactory"></bean>
<!--2.2 创建实例
id: 创建实例的名字
class: 实例属性
factory_bean: 实例工厂的id
factory-method: 属性创建方法
-->
<bean id="apple01" class="chapter1.BfactoryMethod.Apple"
factory-bean="appleFactory" factory-method="getFruit"></bean>
<!--
3.使用spring提供的接口去创建 ☆ ☆ ☆
由于MyBeanFactory实现了Spring提供的BeanFactory接口,spring知道
创建的对象的类型,以及调用那个方法去创建对象。本质上也是使用的工厂方法设计模式
优势:
1、ioc容器启动的时候不会创建实例
2、FactoryBean;获取的时候的才创建对象
-->
<bean id="user04" class="chapter1.MyBeanFactory"></bean>
测试:
/**
* 测试FactoryBean,创建实例
*
* @author: mahao
* @date: 2019/9/16
*/
public class Test3 {
ApplicationContext context = new ClassPathXmlApplicationContext("chapter1.xml");
/**
* 1.静态工厂创建bean(简单工厂模式):
* SQLObject对象会在容器初始化的时候,把对象创建出来,而不是等到使用对象在创建。
*/
@Test
public void demo1() {
SQLObject sqlObject = (SQLObject) context.getBean("mysqlObject");
sqlObject.getConnect();
}
/**
* 2.实例工厂创建bean(工厂方法模式,将每个产品的创建设计成一个工厂):
*/
@Test
public void demo2() {
Apple apple = (Apple) context.getBean("apple01");
System.out.println(apple);
}
/**
* 使用spring提供的beanfactory
*/
@Test
public void demo3() {
User user04 = (User) context.getBean("user04");
System.out.println(user04);
}
}
/**
* 创建spring提供的bean创建方法
*/
class MyBeanFactory implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
4. spring 生命周期
实例化 > 后置处理器before > init-method > 后置处理器after > 完成 > 销毁
5. 引用外部属性文件 (数据库配置文件)
<!--读取配置文件信息-->
<context:property-placeholder location="classpath:jdbc.properties">
</context:property-placeholder>
location="classpath:jdbc.properties"
中的classpath是指类路径下,对于java项目是指bin目录下,对于web项目是class路径下。
6. 自动装配
①自动装配的概念
[1]手动装配:以value或ref的方式明确指定属性值都是手动装配。
[2]自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
②装配模式
[1]根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
[2]根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
[3]通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
③选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。
<?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-4.0.xsd">
<bean id="parent" class="bean.Person"></bean>
<!-- <bean id="p2" class="bean.Person"></bean>
<bean id="p3" class="bean.Person"></bean>-->
<!--
autowire的取值有几种:
byName: 根据属性声明的值,从spring容器中获取去匹配
byType: 根据属性类型,去注入
constructor:根据构造器参数去注入
-->
<bean id="user" class="bean.User" autowire="byName"></bean>
</beans>
7. 注解自动装配
自动装配和依赖注入的关系
依赖注入的本质就是装配,装配是依赖注入的具体行为。
首先,确定一下装配的概念。《spring实战》中给装配下了一个定义:创建应用对象之间协作关系的行为称为装
配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。如果
一个对象只通过接口来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行
切换。但是这样会存在一个问题,在传统的依赖注入配置中,我们必须要明确要给属性装配哪一个bean的引用,一
旦bean很多,就不好维护了。基于这样的场景,spring使用注解来进行自动装配,解决这个问题。自动装配就是开
发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成。与自动装配配合的还有“自动检
测”,这 个动作会自动识别哪些类需要被配置成bean,进而来进行装配。这样我们就明白了,自动装配是为了将依
赖注入“自动化”的一个简化配置的操作。
spring的自动装配实现是依赖Spring Aop的,需要导入依赖;
注解标识组件:
①普通组件:@Component
标识一个受Spring IOC容器管理的组件
②持久化层组件:@Respository
标识一个受Spring IOC容器管理的持久化层组件
③业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
④表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
⑤组件命名规则
[1]默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
[2]使用组件注解的value属性指定bean的id
@Controller @Service @Responsity @Component四个注解,是对spring组件的标识注解,通过表示,让spring容器扫描到,可以装配到容器中,这些注解将Bean的id设置为类名的首字母小写。四个注解没有明显区分,是为了程序员识别使用了。
扫描组件:
上面被spring注解表示的类,必须有spring进行扫描才可以识别到。
<!--自动装配扫描的基础包,用逗号分隔多个包-->
<context:component-scan base-package="chapter5,chapter3">
<!--
可以设定某些包需要排除扫描
-->
<!--不扫描注解下的组件-->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
组件装配:
上面的注解将组件注册到容器中,通过xml配置,可以通过构造器注入,和set方法注入,完成属性的注入。比如通过构造器参数,指定参数位置和组件bean完成注入,或者用set方法,完成属性的赋值(依赖注入只有这两种方式)。
<bean id="user1" class="chapter2.User" init-method="init" destroy-method="destory">
<constructor-arg name="name" value="名字1"></constructor-arg>
<property name="name" value="名字2"></property>
</bean>
现在可以通过spring的注解,来完成依赖的自动装配。
在指定要扫描的包时,context:component-scan 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记了@Autowired、@Resource或@Inject注解的属性。
自动装配的注解有:
@Autowired @Qualifier @Resource @Inject
三个注解都能实现自动装配bean,也有区别:
/**
* 1.使用spring提供的自动装配功能:
* @Autowired注解的装配方式是,通过依赖的类型,去容器中查找组件
* 1. 找不到? 则会发生错误 org.springframework.beans.factory.UnsatisfiedDependencyException:
* 2.正常找到,则装配bean
* 3.如果找到多个同一类型的组件,比如userservie 和 UserServiceExt?这个将会根据属性变量名去去寻找
* 1.找打了,则会正常装配
* 2. 找不到和变量相同的组件id,则会报错。
* 对于存在这种情况,则使用另一个注解@Qualifiter,通过让指定装载的bean名称
* @Qualifier("UserService")
* @Autowired
* UserService userService;
*
*
*/
@Test
public void demo2() {
UserController userController = (UserController) context.getBean("userController");
userController.doGet();
}
上面是@Autowired注解的使用,对于@Qulifier注解作用只是可以使用组件的id进行获取。
/**
* 注解作用在方法上,将会为参数的属性注入依赖
* @param userService
* @param userDao
*/
@Autowired
public UserController(@Qualifier("UserService") UserService userService,UserDao userDao){
System.out.println(userDao +" "+ userService);
}