一、基本概念
1.1 IOC定义
- 全称Inversion of Control(控制反转),把创建对象的过程交给Spring进行管理。
- 作用:
- 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
- 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向,改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
1.2 DI定义
- Dependency Injection(依赖注入)。IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。
1.3 底层原理
- xml解析(注解解析)、工厂模式、反射
- xml解析:xml文件存储需要被创建的对象及其属性、配置的一些信息。通过xml解析,Java读入xml装载的信息。
<bean id="user" class="com.du.bean.User"/>
- 工厂模式:通过工厂类创建新的对象,不使用new的方法。
class UserFactory{ public User getUser(){ String className; Class clazz = Class.forName(className); return (User)clazz.newInstance(); } }
- 反射:实现动态地创建对象,可以根据动态变化的类名(xml文件配置等),创建相应类的对象,而无需将类写死在代码中。实现进一步解耦。
- 譬如,JDBC需要操作数据库,但数据库的类型不确定(MySQL、SQL Server等等)。那么,通过反射就可以解决这个问题。将数据库的驱动类型写在配置文件中,代码通过配置文件读入,获取当前使用的数据库驱动名字,然后根据类名创建相应的对象。(各个数据库驱动类拥有相同的接口)。那么,往后如果要修改数据库的类型,只要修改配置文件即可,无需修改代码。
Driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatements=true user=root password=root
public static Connection getConnection() throws Exception { // 导入properties文件 Properties config = new Properties(); config.load(ClassLoader.getSystemClassLoader().getResourceAsStream("config.properties")); Class<?> driverClass = Class.forName(config.getProperty("Driver")); // 底层已经做了,所以不需要自己注册了 // Driver driver = (Driver) driverClass.newInstance(); // DriverManager.registerDriver(driver); String url = config.getProperty("url"); return DriverManager.getConnection(url, config); }
1.4 IOC容器的实现
- IOC思想就是bean的容器,IOC容器底层就是对象工厂。有两种方式(接口):
BeanFactory
、ApplicaitonContext
1.4.1 BeanFactory
- IOC容器的实现,是Spring内部的实现接口,开发人员不使用这个接口。特点是,加载配置文件的时候,不创建对象,只有在获取对象的时候,才创建。
1.4.2 ApplicaitonContext
- BeanFactory的子接口,提供更加强大的功能,一般由开发人员进行使用。加载配置文件时就会把配置文件对象进行创建。
ApplicationContext
的主要实现类ClassPathXmlApplicationContext
:对应类路径下的XML格式的配置文件FileSystemXmlApplicationContext
:对应文件系统中的XML格式的配置文件ConfigurableApplicationContext
:是ApplicationContext
的子接口,包含一些扩展方法,refresh()
和close()
让ApplicationContext
具有启动、关闭和刷新上下文的能力。WebApplicationContext
:专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
1.5 bean管理
- 指两个操作:创建对象、注入属性。
- bean管理操作一般有两种方式实现:基于xml配置的方式实现、基于注解的方式实现。
二、基于xml的操作
2.1 构造对象
id
:唯一标识class
:类全路径(包类路径)- 默认使用无参构造方法完成对象创建
<bean id="user" class="com.du.spring5.bean.User"/>
2.2 依赖注入(DI)
- 就是注入属性
- 使用
set()
方法进行注入:需要保证指定属性存在set()
方法<bean id="user" class="com.du.spring5.bean.User"> <!-- set方法的属性配置 --> <property name="id" value="1"/> <property name="name" value="J"/> </bean>
- 使用有参构造进行注入:需要保证存在指定的有参构造
- 按属性名赋值
<bean id="user1" class="com.du.spring5.bean.User"> <!-- 构造器方法 --> <constructor-arg name="id" value="2"/> <constructor-arg name="name" value="K"/> </bean>
- 按顺序赋值
<bean id="book" class="com.atguigu.spring.bean.Book"> <constructor-arg value= "10010" index ="0"/> <constructor-arg value= "Book01" index ="1"/> <constructor-arg value= "Author01" index ="2"/> <constructor-arg value= "20.2" index ="3"/> </bean >
- 按类型区分不同的重载构造器
<bean id="book" class="com.atguigu.spring.bean.Book" > <constructor-arg value= "10010" index ="0" type="java.lang.Integer" /> <constructor-arg value= "Book01" index ="1" type="java.lang.String" /> <constructor-arg value= "Author01" index ="2" type="java.lang.String" /> <constructor-arg value= "20.2" index ="3" type="java.lang.Double" /> </bean >
- p名称空间注入
- 第一步,添加p名称空间在配置文件中
<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">
- 第二步,进行属性注入,在
bean
标签里面进行操作
<bean id="user2" class="com.du.spring5.bean.User" p:id="3" p:name="L"/>
2.3 字面量
null
值<bean name="user4" class="com.du.spring5.bean.User"> <property name="name"> <!--空值--> <null/> </property> </bean>
- 属性值包含特殊符号
- 方法一:把
<
、>
进行转义<
、>
- 方法二:把带特殊符号内容写到
CDATA
<bean name="user6" class="com.du.spring5.bean.User"> <property name="id" value="6"/> <property name="name"> <!-- 特殊字符的处理方法二 --> <!-- <![CDATA[内容]]> --> <value><![CDATA[<><>]]></value> <!-- 输出<><> --> </property> </bean>
- 方法一:把
2.4 外部注入bean
<!-- 外部注入bean -->
<bean class="com.du.spring5.Dao.Impl.UserDaoImpl" name="userDao"/>
<!-- 注入外部对象 通过ref=id值 -->
<bean class="com.du.spring5.Service.UserService" name="userService">
<property name="userDao" ref="userDao"/>
</bean>
2.5 内部注入bean
<bean class="com.du.spring5.Service.UserService" name="userService2">
<!-- 内部bean -->
<property name="userDao">
<bean class="com.du.spring5.Dao.Impl.UserDaoImpl"/>
</property>
</bean>
2.6 注入集合属性
方法一:在bean
内部属性注入
<bean name="student" class="com.du.spring5.bean.Student">
<!-- 数组注入 -->
<property name="array">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
<value>array4</value>
</array>
</property>
<!--列表注入-->
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
<value>list4</value>
</list>
</property>
<!--map注入-->
<property name="map">
<map>
<entry value="value1" key="key1"/>
<entry value="value2" key="key2"/>
<entry value="value3" key="key3"/>
<entry value="value4" key="key1"/>
</map>
</property>
<!--set注入-->
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
<value>set1</value>
</set>
</property>
</bean>
方法二:定义外部列表
- 首先,引入名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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="outer_list">
<value>out1</value>
<value>out2</value>
<value>out3</value>
<value>out4</value>
</util:list>
<!--将外部定义的列表,注入到属性中-->
<bean name="student2" class="com.du.spring5.bean.Student">
<property name="list" ref="outer_list"/>
</bean>
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
- 在集合里面设置对象类型值
<!--在集合里面设置对象类型值--> <bean class="com.du.spring5.bean.Person" name="john"> <property name="name" value="john"/> </bean> <bean class="com.du.spring5.bean.Person" name="jack"> <property name="name" value="jack"/> </bean> <bean class="com.du.spring5.bean.User1" name="user1"> <property name="persons"> <list> <ref bean="john"/> <ref bean="jack"/> </list> </property> </bean>
2.7 通过工厂创建bean
2.7.1 静态工厂
- 即调用工厂的静态方法创建bean,无需实例化工厂
public class UserStaticFactory {
private static int count = 0;
public static User getUser(String name) {
User user = new User();
user.setId(count++);
user.setName(name);
return user;
}
}
<!-- 通过静态工厂生成新对象 -->
<bean id="user1" class="com.du.spring5.bean.UserStaticFactory" factory-method="getUser">
<constructor-arg value="user1"/>
</bean>
在bean中说明
factory-method
是哪个,就会直接调用该方法生产
2.7.2 实例工厂
- 需要被实例化之后,再调用获取对象的方法。
public class UserInstanceFactory {
private static int count = 0;
public User getUser(String name) {
User user = new User();
user.setId(count++);
user.setName(name);
return user;
}
}
<!--通过实例工厂生成对象-->
<bean id="userInstanceFactory" class="com.du.spring5.bean.UserInstanceFactory"/>
<bean id="user2" class="com.du.spring5.bean.User" factory-bean="userInstanceFactory" factory-method="getUser">
<constructor-arg value="user2"/>
</bean>
首先实例化工厂类,然后在实例化bean的时候,说明它的
factory-bean
是谁,调用的方法是哪个
2.7.3 FactoryBean
- 是Spring的一个接口,为了避免上面需要繁琐地声明而创造的一个接口。实现该接口之后,Spring可以识别该类为工厂类,直接调用该
getObject()
方法实现生产。 - 创建类,实现接口
FactoryBean
- 实现接口里面的方法,在实现的方法中返回的
bean
类型 - 该方法类似于懒汉式,只有在调用的时候,才会生成实例,无论是单实例还是多实例
public class UserFactoryBean implements FactoryBean<User> {
private static int count = 0;
@Override
public User getObject() throws Exception {
User user = new User();
user.setName("name " + count); // 可以通过外部文件的方式指定
user.setId(count++);
return user;
}
@Override
public Class<User> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
调用
getObject
方法,生产对象。getObjectType
方法获取生产对象的Class
对象。isSingleton()
设定是否为单例模式,返回false说明不是单例模式,每次调用都会生成一个bean。
<!-- 通过FactoryBean生成新对象 -->
<bean id="user3" class="com.du.spring5.bean.UserFactoryBean"/>
@Test
public void test5() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
User user1 = context.getBean("user1", User.class);
User user1_2 = context.getBean("user1", User.class);
System.out.println(user1);
System.out.println(user1_2);
System.out.println(user1 == user1_2); // true
User user2 = context.getBean("user2", User.class);
User user2_2 = context.getBean("user2", User.class);
System.out.println(user2);
System.out.println(user2_2);
System.out.println(user2_2 == user2);// true
User user3 = context.getBean("user3", User.class);
User user3_2 = context.getBean("user3", User.class);
System.out.println(user3);
System.out.println(user3_2);
System.out.println(user3_2 == user3);// false
}
2.8 作用域
- 在Spring中,可以在
<bean>
元素的scope属性里设置bean
的作用域,以决定这个bean
是单实例的还是多实例的(默认为单实例的)<!-- 设置bean的作用域--> <!-- 非单例模式--> <bean id="user8" class="com.du.spring5.bean.User" scope="prototype"/> <!-- 单例模式--> <bean id="user9" class="com.du.spring5.bean.User" scope="singleton"/>
singleton
和prototype
区别- 第一,
singleton
单实例,prototype
多实例 - 第二,设置
scope
值是singleton
时候,加载spring配置文件时候就会创建单实例对象;
- 第一,
- 设置
scope
值是prototype
时候,不是在加载 spring 配置文件时候创建对象,在调用getBean()
方法时候创建多实例对象
2.9 配置信息的继承
- 如果两个 bean之间有很多重复的属性,又不想重复定义,那就可以通过
parent
继承bean
<bean id="user" class="com.du.spring5.bean.User">
<property name="name" value="john"/>
<property name="id" value="1"/>
</bean>
<bean id="user1" class="com.du.spring5.bean.User" parent="user">
<property name="id" value="2"/>
</bean>
user1
继承了user
的属性,而自己修改了id
的值
@Test
public void test9() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
User user = context.getBean("user", User.class);
User user1 = context.getBean("user1", User.class);
System.out.println(user); // User{name='john', id=1}
System.out.println(user1); // User{name='john', id=2}
}
- 如果有一个bean只是用来当模板,而不想被实例化,可以通过
abstract
属性设置
<bean id="user" class="com.du.spring5.bean.User" abstract="true">
<property name="name" value="john"/>
<property name="id" value="1"/>
</bean>
abstract="true"
表示这个不能被实例化
2.10 bean之间的依赖
- 有的时候创建一个
bean
的时候需要保证另外一个bean
也被创建,这时我们称前面的bean
对后面的bean
有依赖。
<bean id="emp03" class="com.atguigu.parent.bean.Employee" depends-on="dept">
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<property name="age" value="21"/>
</bean>
一般,
bean
的实例化都是按照顺序来的,但是如果在标签中加入了depends-on
,那么会在该bean
被创建之前,先创建依赖的bean
,无论顺序。
2.11 自动装配
- 手动装配:以
value
或ref
的方式明确指定属性值都是手动装配 - 自动装配:根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入,
autowire
属性常用两个值:byName
:根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同byType
:将类型匹配的bean作为属性注入到另一个bean中。
public class School { private String name; /*省略*/ }
public class Student { private School school; /*省略*/ }
<!-- 自动装配技术 --> <!-- 无需自己配置property,让代码自己通过bean的name或类型,寻找对应的属性 --> <bean class="com.du.spring5.bean.School" id="school"/> <!-- 通过类型配置,自动将里面的属性进行注入 --> <bean class="com.du.spring5.bean.Student" id="student" autowire="byType"/> <!--通过名字配置,自动将里面的属性进行注入--> <bean class="com.du.spring5.bean.Student" id="student2" autowire="byName"/>
2.12 引用外部属性文件
- 可以将一部分信息提取到
bean
配置文件的外部,以properties
格式的属性文件保存起来,同时在bean
的配置文件中引用properties
属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties
属性文件即可 - 首先,创建外部属性文件,
properties
格式文件,写数据库信息prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/mysql prop.userName=root prop.password=root
- 引入
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 ">
- 引入外部属性文件,并且通过
${}
读出里面的信息<!-- 引入外部配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置连接池 --> <!-- 后面就可以直接使用这个数据库连接池了 --> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="driverClassName" value="${prop.driverClass}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.userName}"/> <property name="password" value="${prop.password}"/> </bean>
2.13 SpEL
- Spring Expression Language,Spring表达式语言,简称SpEL。支持运行时查询并可以操作对象图
- 和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样
- SpEL使用
#{…}
作为定界符,所有在大框号中的字符都将被认为是SpEL表达式
2.13.1 使用字面量
- 整数
<property name="count" value="#{5}"/>
- 小数
<property name="frequency" value="#{89.7}"/>
- 科学计数法
<property name="capacity" value="#{1e4}"/>
- String类型的字面量可以使用单引号或者双引号作为字符串的定界符号
<property name=“name” value="#{'Chuck'}"/> <property name='name' value='#{"Chuck"}'/>
- Boolean
<property name="enabled" value="#{false}"/>
2.13.2 引用其他bean
<bean id="emp04" class="com.atguigu.parent.bean.Employee">
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<property name="age" value="21"/>
<property name="detp" value="#{dept}"/>
</bean>
应该和
ref
效果一样
2.13.3 引用其他bean的属性值作为自己某个属性的值
<bean id="emp05" class="com.atguigu.parent.bean.Employee">
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<property name="age" value="21"/>
<property name="deptName" value="#{dept.deptName}"/>
</bean>
2.13.4 调用非静态方法
<!-- 创建一个对象,在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.atguigu.spel.bean.SalaryGenerator"/>
<bean id="employee" class="com.atguigu.spel.bean.Employee">
<!-- 通过对象方法的返回值为属性赋值 -->
<property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>
2.13.5 调用静态方法
<bean id="employee" class="com.atguigu.spel.bean.Employee">
<!-- 在SpEL表达式中调用类的静态方法 -->
<property name="circle" value="#{T(java.lang.Math).PI*20}"/>
</bean>
2.13.6 运算符
<property name="key" value="#{1*2}"/>
三、基于注解的操作
- 相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式
3.1 使用注解标识组件
- 普通组件:
@Component
,标识一个受Spring IOC容器管理的组件 - 持久化层组件:
@Respository
,标识一个受Spring IOC容器管理的持久化层组件 - 业务逻辑层组件:
@Service
,标识一个受Spring IOC容器管理的业务逻辑层组件 - 表述层控制器组件:
@Controller
,标识一个受Spring IOC容器管理的表述层控制器组件 - 组件命名规则
- 默认情况:使用组件的简单类名首字母小写后得到的字符串作为
bean
的id
- 使用组件注解的
value
属性指定bean
的id
- 默认情况:使用组件的简单类名首字母小写后得到的字符串作为
- 在类上面添加创建对象注解
@Component(value="dptm") public class Department { private String name="dptm"; public Department() { } public Department(String name) { this.name = name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Department{" + "name='" + name + '\'' + '}'; } }
在注解中的
value
属性可以忽略不写,默认为类名称,首字母小写。
3.2 扫描组件
3.2.1 导包
- 必须在原有JAR包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar
3.2.2 开启组件扫描
<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">
<!-- 定义组件扫描,将会针对指定路径,扫描有没有已经通过注解配置好的bean对象 -->
<context:component-scan base-package="com.du.spring5"/>
<context:component-scan base-package="com.du.spring5.bean, com.du.spring5.service"/>
base-package
属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。- 当需要扫描多个包时可以使用逗号分隔。
- 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类
<context:component-scan base-package="com.atguigu.component" resource-pattern="autowire/*.class"/>
3.2.3 包含与排除
<context:include-filter>
:子节点表示要包含的目标类。- 注意:通常需要将
use-default-filters
属性设置为false
,禁用默认过滤器,然后扫描的就只是include-filter
中的规则指定的组件了
<!-- 组件扫描细节配置-->
<!-- use-default-filters="false" 表示不使用默认的filter 默认所有对象都会被扫描出来 如今设为false之后,就只包含include-filter里面的内容 -->
<context:component-scan base-package="com.du.spring5" use-default-filters="false">
<!-- 筛选目录内Component的注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
<context:exclude-filter>
:子节点表示要排除在外的目标类。(无需禁用默认过滤器)
<!-- 组件扫描细节配置-->
<!-- use-default-filters="true" 还是使用默认的filter,只是在这基础上,除去Component部分 -->
<context:component-scan base-package="com.du.spring5">
<!-- 筛选目录内Component的注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
- 过滤表达式
类别 示例 说明 annotation
com.atguigu.XxxAnnotation
过滤所有标注了 XxxAnnotation
的类assignable
com.atguigu.BaseXxx
过滤所有 BaseXxx
类的子类。aspectj
com.atguigu.*Service+
所有类名是以 Service
结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。regex
com\.atguigu\.anno\.*
所有 com.atguigu.anno
包下的类。这个规则根据正则表达式匹配到的类名进行过滤custom
com.atguigu.XxxTypeFilter
使用 XxxTypeFilter
类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter
接口
3.3 自动装配
Controller
组件中往往需要用到Service
组件的实例,Service组件中往往需要用到Repository
组件的实例- 在指定要扫描的包时,
<context:component-scan>
元素会自动注册一个bean
的后置处理器:AutowiredAnnotationBeanPostProcessor
的实例。该后置处理器可以自动装配标记了@Autowired
、@Resource
或@Inject
注解的属性
3.3.1 @Autowired注解
- 执行步骤如下:
- 首先,根据类型实现自动装配。若找到一个符合类型的对象,就直接装配。
- 若没找到任何一个符合类型的对象,那就报异常,装配失败。
- 若找到了多个符合类型的对象,则比较对象名和需要被装配的属性名(因为默认属性名就是id),找到那个名字相同的,实现装配
- 若符合类型的对象,名字都不符合,那就异常。
@Autowired
根据属性类型自动装配@Service public class UserService { // 定义dao类型属性 // 不需要添加set方法 // 添加注入属性注解 @Autowired private UserDao userDao; public void add() { System.out.println("service add...."); userDao.add(); } }
- 在
@Autowired
中,可以指定一个参数required
,默认为true
,表示如果标注了,那就必须要被注入,否则就报异常。而如果给它设置为false
,则可以实现找到了就注入,没找到就算了。 - 构造器、普通字段(即使是非
public
)、一切具有参数的方法都可以应用@Autowired
注解。(也就是说,可以在方法上面标注一个@Autowired
,这样就会在创建该对象的时候,顺便运行了该方法/构造器/属性,并注入相应的对象。) - 一般情况下,属性注入有三种方式:字段名上标注、构造器上标注、
setter
上标注。一般来说,建议使用setter
上标注。参考
@Component
public class Phone {
private String name;
public Phone() {
}
public Phone(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Phone{" +
"name='" + name + '\'' +
'}';
}
}
@Component
public class User1 {
@Autowired
private Phone phone;
@Autowired
private User1(Phone phone) {
this.phone = phone;
}
public User1() {
}
public Phone getPhone() {
return phone;
}
@Autowired
public void setPhone(Phone phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User1{" +
"phone=" + phone +
'}';
}
}
- 如果存在一个对象集合/数组需要被
@Autowired
,那就会将所有符合的类型,全部注入到该集合/数组中。若该Map
的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
@Component
public class Phone {
private String name;
public Phone() {
}
public Phone(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Phone{" +
"name='" + name + '\'' +
'}';
}
}
@Component
public class User2 {
private Phone[] phones;
private List<Phone> phoneList;
private Map<String, Phone> phoneMap;
public User2() {
}
public Phone[] getPhones() {
return phones;
}
@Autowired
public void setPhones(Phone[] phones) {
this.phones = phones;
}
public List<Phone> getPhoneList() {
return phoneList;
}
@Autowired
public void setPhoneList(List<Phone> phoneList) {
this.phoneList = phoneList;
}
public Map<String, Phone> getPhoneMap() {
return phoneMap;
}
@Autowired
public void setPhoneMap(Map<String, Phone> phoneMap) {
this.phoneMap = phoneMap;
}
@Override
public String toString() {
return "User2{" +
"phones=" + Arrays.toString(phones) +
", phoneList=" + phoneList +
", phoneMap=" + phoneMap +
'}';
}
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.du.spring5"/>
<bean id="phone1" class="com.du.spring5.bean.Phone">
<property name="name" value="phone1"/>
</bean>
<bean id="phone2" class="com.du.spring5.bean.Phone">
<property name="name" value="phone2"/>
</bean>
</beans>
@Test
public void test5() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
User2 user2 = context.getBean("user2", User2.class);
System.out.println(user2); // User2{phones=[Phone{name='null'}, Phone{name='phone1'}, Phone{name='phone2'}], phoneList=[Phone{name='null'}, Phone{name='phone1'}, Phone{name='phone2'}], phoneMap={phone=Phone{name='null'}, phone1=Phone{name='phone1'}, phone2=Phone{name='phone2'}}}
}
3.3.2 @Qualifier注解
- 当属性名和容器内的对象名不一致,无法实现注入的时候,可以在需要被自动注入的对象上面,再添加一个注解
@Qualifier
。用来指定该对象的id
,用于指定需要被注入对象。 @Qualifier
根据名称进行注入
//@Component(value="user")
@Component// 等效于上方
public class User {
@Value(value = "abc") // 用于注入普通类型属性
private String name;
private int id;
// 自动属性注入,方法:需要被注入的类型属性,首先创建对象注解。然后在属性上面加Autowired
@Autowired // 根据类型注入
@Qualifier(value = "dptm") // 根据名称注入,应用于一个类型有多个不同名称的对象时候。如果类就可以定位,那么该注解可以省略
private Department department;
public User() {
}
public User(String name, int id) {
this.name = name;
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
public void setDepartment(Department department) {
this.department = department;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", department=" + department +
'}';
}
}
3.3.3 @Resource
- 与
@Autowired
很类似,但没有required
属性。此注解是JavaEE的注解,与@Autowired
不同的是,具有更好的拓展性。
3.3.4 @Inject
@Inject
和@Autowired
注解一样也是按类型注入匹配的bean
,但没有reqired
属性
3.4 泛型依赖注入
- 可以为子类注入子类对应的泛型类型的成员变量的引用。
举例
- java bean
public class User { }
public class Book { }
- dao 层
public interface BaseDao<T> { void save(); }
@Repository public class BookDao implements BaseDao<Book> { @Override public void save() { System.out.println("调用了BookDao"); } }
@Repository public class UserDao implements BaseDao<User> { @Override public void save() { System.out.println("调用了UserDao"); } }
- service 层
public class BaseService<T> { BaseDao<T> baseDao; public void save() { baseDao.save(); } public BaseDao<T> getBaseDao() { return baseDao; } @Autowired // 在这里自动注入! public void setBaseDao(BaseDao<T> baseDao) { this.baseDao = baseDao; } }
@Service public class BookService extends BaseService<Book> { }
@Service public class UserService extends BaseService<User> { }
- 调用
public class ServiceTest { @Test public void testService() { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); BookService bookService = context.getBean("bookService", BookService.class); UserService userService = context.getBean("userService", UserService.class); bookService.save(); // 调用了BookDao userService.save(); // 调用了UserDao } }
- 主要原理就是,两个service实现类都继承了
BaseService
,@Autowired
也继承过去了。然而两个service
给dao设置了不同的泛型类型。因此,在自动匹配的过程中,除了匹配类型之外,还匹配了泛型,匹配到完全适合的时候,就自动注入了。 - 因此实现了不同的泛型,自动注入了不同的对象。
四、完全注解开发
- 创建配置类,替代xml配置文件
- 第一步,创建配置类,替代xml配置文件
@Configuration // 表示这是一个配置类,代替xml文件 @ComponentScan(basePackages={"com.du.spring5"}) // 指定扫描的路径 public class SpringConfig { }
- 第二步,使用
@Test public void test3() { // 通过另一个bean工厂的实现类,通过配置类的方式,导入配置。无需使用xml ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 和ClassPathXmlApplicationContext是远方表兄弟 User user = context.getBean("user", User.class); System.out.println(user); }
- 第一步,创建配置类,替代xml配置文件
五、整合多个配置文件
- Spring允许通过
<import>
将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动Spring容器时,仅需要指定这个合并好的配置文件就可以。 import
元素的resource
属性支持Spring的标准的路径资源
六、bean生命周期
6.1 步骤
- 通过构造器创建
bean
实例(无参构造) - 为
bean
的属性设置值和其他bean
引用(调用set
方法) - 调用
bean
的初始化方法(需要进行配置初始化的方法) bean
可以使用了(对象获取到了)- 当容器关闭时候,调用
bean
的销毁的方法(需要进行配置销毁的方法)
6.2 加上后置处理器
- 通过构造器创建
bean
实例(无参构造) - 为
bean
的属性设置值和其他bean
引用(调用set
方法) - 把
bean
实例传递bean
后置处理器的方法postProcessBeforeInitialization()
- 调用
bean
的初始化方法(需要进行配置初始化的方法) - 把
bean
实例传递bean
后置处理器的方法postProcessAfterInitialization()
bean
可以使用了(对象获取到了)- 当容器关闭时候,调用
bean
的销毁的方法(需要进行配置销毁的方法)
6.3 演示bean
生命周期
// bean
public class Person {
private String name;
public Person() {
System.out.println("Constructor");
}
public void initMethod(){
System.out.println("initMethod");
}
public void destroyMethod(){
System.out.println("destroyMethod");
}
public void setName(String name) {
this.name = name;
System.out.println("set");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
// 后置处理器
public class PersonPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization");
return bean;
}
}
<!--bean的生命周期-->
<!-- initMethod 初始化方法 -->
<!-- destroyMethod 销毁方法-->
<!-- 顺序: 构造器构造 -> set方法 -> 初始化方法initMethod -> bean可以使用 -> 销毁bean -->
<bean id="person" class="com.du.spring5.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="john"/>
</bean>
<!-- 配置处理器 -->
<!-- 加上配置器之后,会在initMethod之间,加上postProcessBeforeInitialization和postProcessAfterInitialization方法 -->
<!-- 顺序: 构造器构造 -> set方法 -> postProcessBeforeInitialization —> 初始化方法initMethod -> postProcessAfterInitialization -> bean可以使用 -> 销毁bean -->
<bean id="personPostProcessor" class="com.du.spring5.bean.PersonPostProcessor"/>
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
System.out.println("get bean");
Person person = context.getBean("person", Person.class);
// 接口没有close,只能强转回ClassPathXmlApplicationContext
ClassPathXmlApplicationContext context1 = (ClassPathXmlApplicationContext) context;
context1.close();