随着时代发展,软件规模与功能都呈几何式增长,开发难度也在不断递增,Spring可以简化开发
,降低企业级开发的复杂性,使开发变得更简单快捷
随着项目规模与功能的增长,遇到的问题就会增多,为了解决问题会引入更多的框架,Spring可以框架整合
,高效整合其他技术,提高企业级应用开发与运行效率
Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能
Spring Framework 是 Spring 生态圈中最基础的项目,是其他项目的根基
Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整
Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的 是4的架构图
在这个代码中,由于业务层代码中需要数据层的对象,导致两层之间的耦合度很高,针对这个问题 Spring 提出了一个解决方案:使用对象时,在程序中不要主动使用 new 产生对象,转换为由外部提供对象
IOC(Inversion of Control)控制反转:控制反转的是对象的创建权
IOC容器
,用来充当IOC思想的 “ 外部 ”Bean对象
DI(Dependency Injection)依赖注入:绑定对象与对象之间的依赖关系
但是现在又有一个问题,当 IOC 容器中创建好 service 和 dao 对象后,因为 service 运行需要依赖 dao 对象,但是 service 对象和 dao 对象没有任何关系,导致程序无法正确运行,而在容器中建立对象与对象之间的绑定关系就要用到 DI
介绍完 Spring 的 IOC 和 DI 的概念后,我们会发现这两个概念的最终目标就是:充分解耦
,具体实现靠:
创建 Maven 项目
pom.xml引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
resources下创建 spring 配置文件(applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="项目下的Dao实现类路径"/>
<bean id="bookService" class="项目下的Service实现类路径"/>
</beans>
注意事项: bean 定义时 id 属性在同一个配置文件中不能重复
使用 Spring 提供的接口完成 IOC 容器的创建
从容器中获取对象进行方法调用
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");//这里的这个xml就是刚才创建的Spring的配置文件
BookDao bookDao = (BookDao) ctx.getBean("bookDao");//通过容器来获取Bean对象
bookDao.save();
}
}
实现依赖注入,必须要基于 IOC 管理 Bean
去除代码中的 new
为属性提供 setter 方法
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
//private BookDao bookDao = new BookDaoImpl();
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
在配置文件中添加依赖注入的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签标示配置bean
id属性表示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.XXX.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
注意: 配置中的两个 bookDao 的含义是不一样的
IOC 中对象的相关配置以及实例化方式和生命周期
注意: 因为 bookDao 和 bookService 一般在项目中是接口,接口不能实现,所以一般是使用它的实现类来进行对象的创建
XML中的配置写法
<bean id="bookService" name="service service4 bookEbi"
class="BookServiceImpl的类路径">
<property name="bookDao" ref="bookDao"/>
bean>
<bean id="bookDao" name="dao" class="BookDaoImpl的类路径"/>
注意:
注意:
bean本质上就是对象,对象在new的时候会使用构造方法完成,那创建bean也是使用构造方法完成的,基于此,Bean 有三种创建方式
Spring底层使用的是类的无参构造方法,而且是通过反射来访问到类中的方法的
这种方式一般是用来兼容早期的一些老系统,需要在Spring的配置文件中加入以下内容:
<bean id="orderDao" class="OrderDaoFactory类路径名" factory-method="getOrderDao"/>
虽然在工厂类中也是直接new对象,但是在工厂的静态方法中,我们除了new 对象外还可以做一些必不可少的业务操作
在Spring的配置文件中加入以下内容:
<bean id="userFactory" class="UserDaoFactory的类路径"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
实例化工厂运行的顺序是:
这种方式在Spring去整合其他框架的时候会被用到
让工厂类(这里以UserDaoFactory为例)实现FactoryBean接口,重写接口的方法:
在Spring的配置文件中进行配置
<bean id="userDao" class="UserDaoFactoryBean类路径"/>
bean 的生命周期:bean 对象,从创建到消亡的完整过程
bean 生命周期控制的是 bean 对象从创建后到销毁前做一些事情
关于 Spring 中对 bean 生命周期控制提供了两种方式:
init-method
属性和destory-method
属性InitializingBean
接口和DisposableBean
接口<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
init-method 属性是初始化方法,而 destroy-method 属性是销毁的方法
但是执行后只会执行初始化的 init 方法而未执行销毁方法destory,为什么?
原因是 Spring 的 IOC 容器是运行在 JVM 中,运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行,main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还未来得及销毁就已经结束了,所以没有调用对应的 destory 方法
ApplicationContext 中没有 close 方法,所以需要将 ApplicationContext 更换为 ClassPathXmlApplicationContext(ClassPathXmlApplicationContext 是 ApplicationContext 的子类) 来调用 close 方法,这样就可以正常的执行容器的销毁了
在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器,调用 registerShutdownHook 方法
注意: RegisterShutdownHook 方法在 ApplicationContext 中也没有,所以依然要使用 ClassPathXmlApplicationContext
close 方法和 RegisterShutdownHook 方法区别:
在实现类中实现 InitializingBean 和 DisposableBean 两个接口之后重写 afterPropertiesSet 方法和 destory 方法
注意: InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为 属性设置之后,对于 servce 层中的实现类方法来说,Dao 层的实现类为它的一个属性,setxxxDao 方法是 Spring 的 IOC 容器为它注入属性的方法,而setxxxDao方法先执行,afterPropertiesSet 方法后执行
总结:
对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置
Spring 中提供了两种注入方式:
在 bean 中定义引用类型属性,并提供可访问的 set 方法
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置中使用 property 标签 ref 属性注入引用类型对象
<bean id="bookService" class="BookServiceImpl类路径">
<property name="实现类中的bookDao的属性" ref="bean中的bookDao对象"/>
bean>
<bean id="bookDao" class="BookDaoImpl类路径"/>
在实现类中声明属性并提供 setter 方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;//声明属性
public void setUserDao(UserDao userDao) {//提供setter方法
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置文件中进行注入配置
<bean id="bookDao" class="BookDaoImpl类路径"/>
<bean id="userDao" class="UserDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径">
<property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/>
<property name="实现类中的userDao属性" ref="bean中的userDao对象"/>
bean>
在实现类中声明对应的简单数据类型的属性,并提供对应的 setter 方法
public class BookDaoImpl implements BookDao {
private String databaseName;//声明属性
private int connectionNum;//声明属性
// 提供 setter 方法
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
在配置问文件中进行注入配置
<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl">
<property name="实现类方法中的参数databaseName(形参)" value="形参中的数据值"/>
<property name="实现类方法中的参数connectionNum(形参)" value="形参中的数据值"/>
bean>
<bean id="userDao" class="com.XXX.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="BookServiceImpl类路径">
<property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/>
<property name="实现类中的userDao属性" ref="bean中的userDao对象"/>
bean>
构造器注入也就是构造方法注入,无非是把实现类中的 setter 方法改为构造器方法
删除实现类中的 setter 方法并提供构造方法
public class BookServiceImpl implements BookService{
private BookDao bookDao;
// 在构造方法中对属性进行设置
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
}
在配置文件中配置构造方法注入
<bean id="bookDao" class="BookDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径">
<constructor-arg name="实现类中构造方法的参数bookDao(形参)" ref="bean中的bookDao对象"/>
bean>
注意:
提供多个属性的构造函数
public class BookServiceImpl implements BookService{
private BookDao bookDao;// 声明引用类型属性
private UserDao userDao;// 声明引用类型属性
// 构造方法注入
public BookServiceImpl(BookDao bookDao,UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
}
配置文件中配置
<bean id="bookDao" class="BookDaoImpl类路径"/>
<bean id="userDao" class="UserDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径">
<constructor-arg name="实现类中构造方法的参数bookDao" ref="bean中的bookDao对象"/>
<constructor-arg name="实现类中构造方法的参数userDao" ref="bean中的userDao对象"/>
bean>
注意: constructor-arg 标签的顺序可以任意
添加多个简单属性并提供构造方法
public class BookDaoImpl implements BookDao {
private String databaseName; //声明简单类型属性
private int connectionNum; //声明简单类型属性
// 通过构造器注入
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
}
配置文件中配置
<bean id="bookDao" class="BookDaoImpl类路径">
<constructor-arg name="实现类中构造方法的参数databaseName" value="参数的值"/>
<constructor-arg name="实现类中构造方法的参数connectionNum" value="参数的值"/>
bean>
<bean id="userDao" class="UserDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径">
<constructor-arg name="实现类中构造方法的参数bookDao" ref="bean中的bookDao对象"/>
<constructor-arg name="实现类中构造方法的参数userDao" ref="bean中的userDao对象"/>
bean>
当构造函数中方法的参数名发生变化后,配置文件中的name属性也需要跟着变 ,两块存在紧耦合
解决方法:
方式一:删除 name 属性,添加 type 属性,按照类型注入
<bean id="bookDao" class="BookDaoImpl类路径">
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
bean>
方式二:删除 type 属性,添加 index 属性,按照索引下标注入,下标从0开始
<bean id="bookDao" class="BookDaoImpl类路径">
<constructor-arg index="1" value="100"/>
<constructor-arg index="0" value="mysql"/>
bean>
介绍完这两种参数的注入方式,具体我们该如果选择?
相对于之前的手动配置比较麻烦,Spring 提供了自动装配的方式
IOC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
自动装配只需要修改 xml 配置文件即可
按照类型注入的配置
<bean class="BookDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径" autowire="byType"/>
注意:
NoUniqueBeanDefinitionExcepetion
如果一个类型在 IOC 中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式:
<bean class="BookDaoImpl类路径"/>
<bean id="bookService" class="BookServiceImpl类路径" autowire="byName"/>
注意:
按照名称注入中的名称是指对象实现类中的 set 方法的名称去掉 set 后剩下的部分的首字母小写的名称
以上图为例,因为 bookDao 是 private 修饰的,外部类无法直接访问,而外部类访问只能通过属性的 set 方法进行访问
而 set 方法生成的默认规则是,set 方法把属性名的首字母大写前面加上 set 形成方法名,所以按照名称注入,其实是和对应的 set 方法有关,但是如果按照标准起名称,属性名和 set 对应的名是一致的
如果按照名称去找对应的 bean 对象,找不到则注入 Null
当某一个类型在 IOC 容器中有多个对象,按照名称注入只找其指定名称对应的 bean 对象,不会报错
数据还有一种类型是集合,而 Spring 当然也支持集合的注入
Spring 中支持的集合类型
Array
<property name="array">
<array>
<value>100value>
<value>200value>
<value>300value>
array>
property>
List
<property name="list">
<list>
<value>caixunkunvalue>
<value>xiaoheizivalue>
<value>ikunvalue>
<value>rapvalue>
list>
property>
Set
<property name="set">
<set>
<value>caixunkunvalue>
<value>xiaoheizivalue>
<value>ikunvalue>
<value>rapvalue>
set>
property>
Map
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
map>
property>
Properties
<property name="properties">
<props>
<prop key="country">chinaprop>
<prop key="province">henanprop>
<prop key="city">kaifengprop>
props>
property>
注意:
对于有些数据写在配置文件中不利于后期维护,那么需要将这些值提取到一个外部的 properties 文件中,然后让 Spring 框架从外部 properties 文件中读取属性值
准备 properties 配置文件
resource 下创建一个 xxx.properties 文件,并添加对应的属性键值对,比如:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
开启 context 命名空间
在 applicationContext.xml(也就是 bean 对象定义的配置文件)中开启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">
beans>
加载 properties 配置文件
<context:property-placeholder location="jdbc.properties"/>
在配置文件中使用context
命名空间下的标签来加载 properties 配置文件
完成属性注入
使用 ${key} 来读取 properties 配置文件中的内容并完成属性注入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
beans>
在实现类中添加对应的属性和属性的设置(setter)方法
完成配置文件的读取与注入
<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="jdbc.properties"/>
<bean id="bookDao" class="BookDaoImpl类路径">
<property name="name" value="${jdbc.driver}"/>
bean>
beans>
注意事项:
问题一:properties 配置文件中,配置键值对的优先级问题
在配置文件中 key 键为 username 这个使用会有问题,假设给其设置的 value 值为 root666,而打印出来的则不是 root666 而是自己电脑的用户名
因为 context:property-placeholer 标签会加载系统的环境变量,而环境变量的值会优先加载
要解决需要在 context:property-placeholer 标签中加一个 system-properties-mode 属性并给他值设置为 NEVER,当然还有就是不使用username
作为属性的key
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
问题二:当有多个 properties 配置文件需要被加载
调整配置文件内容,在 resource 下添加多个配置文件
修改 applicationContext.xml
<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="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
<context:property-placeholder location="*.properties" systemproperties-mode="NEVER"/>
<context:property-placeholder location="classpath:*.properties"
system-properties-mode="NEVER"/>
<context:property-placeholder location="classpath*:*.properties"
system-properties-mode="NEVER"/>
beans>
注意:
方式一:如果配置文件多的话,需要每个都配置
方式二:: *.properties
代表所有以properties
结尾的文件都会被加载,可以解决方式一的问题,但是不标准
方式三:标准的写法,classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径
方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties
配置文件
这里说的核心容器可以简单的理解为ApplicationgContext
容器的创建方法有两种:
ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
ApplicationContext ctx = new FileSystemXmlApplicationContext(“D:\workspace\spring\spring_10_container\s rc\main\resources\applicationContext.xml”);
applicationContext.xml
,所以要把括号内参数修改为绝对路径(也就是从盘符开始写起)方式一:
BookDao bookDao = (BookDao) ctx.getBean(“bookDao”);
这种方式存在的问题就是获取的时候都需要进行类型转换
方式二:
BookDao bookDao = ctx.getBean(“bookDao”, BookDao.class);
这种方式可以解决类型强转问题,但是参数又多加了一个,相对来说没有简化多少
方式三:
BookDao bookDao = ctx.getBean(BookDao.class);
这种方式就类似依赖注入中的按类型注入,必须要确保 IOC 容器中该类型对应的 bean 对象只能有一个。
Bean 标签属性的总结
在 IDEA 中双击shift
,输入 BeanFactory,点击进入 BeanFactory 类,ctrl+h,就能查看到如下结构的层次关系中可以看出,容器类也是从无到有根据需要一层层叠加上来的,BeanFactory 是 IoC 容器的顶层接口,ApplicationContext 接口是 Spring 容器的核心接口,接口提供基础的 bean 操作相关方法,通过其他接口扩展其功能
使用BeanFactory来创建IOC容器的具体实现方式为:
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}
BeanFactory 和 ApplicationContext 区别:
ApplicationContex 要想称为延迟加载,只需要如下配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="BookDaoImpl类路径" lazy-init="true"/>
beans>
依赖注入相关总结:
在 Spring 发展到2.0的时候提供了注解开发,而且通过配置文件的开发发现太过于繁琐,那么接下来是 Spring2.5 的注解开发
步骤:
删除原 XML 配置(将配置文件中的 bean 标签删除掉)
<bean id="bookDao" class="BookDaoImpl实现类"/>
Dao 上添加注解
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
配置 Spring 的注解包扫描
为了让 Spring 框架能够扫描到写在类上的注解,需要在配置文件上进行包扫描
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="com.xxx"/>
beans>
说明:
Service 上添加注解
在 BookServiceImpl 类上也添加@Component
交给 Spring 框架管理
@Component
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
@Component
注解如果不起名称,会有一个默认值就是当前类名首字母小写,所以也可以按照名称获取,如
BookService bookService = (BookService)ctx.getBean("bookServiceImpl");
对于@Component
注解,还衍生出了其他三个注解@Controller
、@Service
、@Repository
通过查看源码会发现:
这三个注解和@Component注解的作用是一样的,方便后期在编写类的时候能很好的区分出这个类是属于表现层、业务层还是数据层的类。
名称 | @Component/@Controller/@Service/@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 spring 管理的 bean |
属性 | value(默认):定义 bean 的id |
上面虽然是通过注解简化了一些操作,但是还是有配置文件,显得繁琐,Spring3.0 提供了纯注解开发,使用 Java 类替代配置文件,开启了 Spring 快速开发
创建配置类
标识该类为配置类
@Configuration
//配置类注解,标识了该类为配置类,用来替代applicationContext.xml
public class SpringConfig {//配置类
}
用注解替换包扫描配置
//在配置类上添加包扫描注解@ComponentScan替换application.xml文件中的
@Configuration
@ComponentScan("com.XXX")
public class SpringConfig {
}
// application.xml文件就可以退休了
至此,纯注解开发方式已完成,主要包括:
Java 类替换 Spring 核心配置文件
@Configuration 注解用于设定当前类为配置类
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据用数组格式
@ComponentScan({com.XXX.service","com.XXX.dao"})
读取 Spring 核心配置文件初始化容器对象切换为读取 Java 配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
名称 | @Configuration |
---|---|
类型 | 类注释 |
位置 | 类定义上方 |
作用 | 设置该类为 Spring 配置类 |
位置 | value(默认值):定义 bean 的id |
名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置 Spring 配置类扫描路径,用于加载使用注解格式定义的 bean |
位置 | value(默认值):扫描路径,此路径可以逐层向下扫描 |
applicationContext.xml 中
的作用是指定扫描包路径,注解为@ComponentScan
@Configuration标识该类为配置类,使用类替换applicationContext.xml文件
ClassPathXmlApplicationContext 是加载XML配置文件
AnnotationConfigApplicationContext 是加载配置类
作用范围主要是指是否是单例模式,而生命周期则是指初始化和销毁的方法定义
使用@Scope
注解
@Repository
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围,可用于设置创建出的 bean 是否是单例对象 |
属性 | value:定义 bean 作用范围,默认值 singleton(单例),可选填 prototype(非单例) |
在 BookDaoImpl 中添加两个方法,init
和destroy
,方法名可以任意
在对应的方法上添加@PostConstruct
和@PreDestroy
注解即可
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@PostConstruct //在构造方法之后执行,替换 init-method
public void init() {
System.out.println("init ...");
}
@PreDestroy //在销毁方法之前执行,替换 destroy-method
public void destroy() {
System.out.println("destroy ...");
}
}
注意:
@PostConstruct
和@PreDestory
注解如果找不到,需要导入以下 Jar 包
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
因为从 JDK9之后 jdk 中的 javax.annotation 包被移除了,这两个注解刚好就在这个包中
名称 | @PostConstruct |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为初始化方法 |
属性 | 无 |
名称 | @PreDestroy |
---|---|
类型 | 方法注解 |
位置 | 方法上 |
作用 | 设置该方法为销毁方法 |
属性 | 无 |
Spring 为了使用注解简化开发,并没有提供构造函器注入、setter注入对应的注解,只提供了自动装配的注解实现。
在 BookServiceImpl 类的 bookDao 属性上添加@Autowired
注解
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:
@Autowired
可以写在属性上,也可以写在 setter 方法上,最简单的处理方式是写在属性上并将setter
方法注释掉@Autowired
是按照类型注入,那么对应 BookDao 接口如果有多个实现类,或导致报错,那么就需要使用按照名称注入名称 | @Autowired |
---|---|
类型 | 属性注解或方法注解或方法形参注解 |
位置 | 属性定义上方或标准 set 方法上方或类 set 方法上方或方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true/false,定义该属性是否允许为null |
当根据类型在容器中找到多个 bean,注入参数的属性名又和容器中bean的名称不一致,这个时候就需要使用到@Qualifier
来指定注入哪个名称的bean对象。
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意: @Qualifier
注解后的值就是需要注入的bean的名称,@Qualifier
不能单独使用,必须和@Autowired
一起使用
名称 | @Qualifier |
---|---|
类型 | 属性注解或方法注解 |
位置 | 属性定义上方或标准 set 方法上方或类 set 方法上方或 |
作用 | 为引用类型属性指定注入的 beanId |
属性 | value:设置注入的 beanId |
简单类型注入的是基本数据类型或者字符串类型
假设在 BookDaoImpl 类中添加一个 name 属性,用其进行简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
数据类型换了,对应的注解也要跟着换,这次使用@Value
注解,将值写入注解的参数中就行了
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("XXXX")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
注意: 数据格式要匹配,如将 “abc” 注入给 int 值,这样程序就会报错。
名称 | @Value |
---|---|
类型 | 属性注解或方法注解 |
位置 | 属性定义上方或标准 set 方法上方或类 set 方法上方或 |
作用 | 为基本数据类型或字符串类型属性设置值 |
属性 | value:要注入的属性值 |
@Value
一般会被用在从 properties 配置文件中读取内容进行使用
接下来进行演示
准备 properties 文件
//jdbc.properties
name=XXXX888
加载 properties 文件
@Configuration
@ComponentScan("com.XXX")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
使用value
读取配置文件中的内容
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
注意:
如果读取的 properties 配置文件有多个,可以使用@PropertySource
的属性来指定多个
@PropertySource({"jdbc.properties","xxx.properties"})
@PropertySource
注解属性中不支持使用通配符*
,运行会报错的
@PropertySource({"*.properties"})
@PropertySourcez
注解属性中可以把classpath:
加上,代表从当前项目的根路径找文件,而且也不可以加上*
@PropertySource({"classpath:jdbc.properties"})
名称 | @PropertySource |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 加载 properties 文件中的属性值 |
属性 | value:设置加载的 properties 文件对应的文件名或文件名组成的数组 |
对于第三方在 jar 包中的类,需要使用@Bean
注解
以对Druid
数据源的管理为例
导入 Druid 对应的 jar 包
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
在配置类中添加一个方法
@Configuration
public class SpringConfig {
public DataSource dataSource(){
//该方法的返回值就是要创建的Bean对象类型
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
在方法上添加@Bean
注解
@Configuration
public class SpringConfig {
@Bean
//@Bean注解的作用是将方法的返回值制作为Spring管理的一个bean对象
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
注意: 不能使用DataSource ds = new DruidDataSource()
接口来实例化,因为 DataSource 接口中没有对应的 setter 方法来设置属性。
名称 | @Bean |
---|---|
类型 | 方法注解 |
位置 | 方法定义上方 |
作用 | 设置该方法的返回值作为 Spring 管理的 bean |
属性 | value:定义 bean 的 id |
如果把所有的第三方 bean 都配置到 Spring 的配置类 SpringConfig 中,虽然可以,但是不利于代码阅读和分类管理,可以按照类别将这些 bean 配置到不同的配置类中
比如:
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
这个配置类如何能被 Spring 配置类加载到,并创建 DataSource 对象在 IOC 容器中
在 Spring 配置类上添加包扫描
@Configuration
@ComponentScan("com.XXX.config")
public class SpringConfig {
}
在 JdbcConfig 上添加配置注解
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
这种方式虽然能够扫描到,但是不能很快的知晓都引入了哪些配置类
去除 JdbcConfig 类上的注解
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
在 Spring 配置类中引入
@Configuration
//@ComponentScan("com.XXX.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
注意:
@Import
参数需要的是一个数组,可以引入多个配置类。
@Import
注解在配置类中只能写一次,下面的方式是不允许的
@Configuration
//@ComponentScan("com.XXX.config")
@Import(JdbcConfig.class)
@Import(Xxx.class)
public class SpringConfig {
}
名称 | @Import |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 导入配置类 |
属性 | value:定义导入的配置类类名,当配置类有多个时使用数组格式一次性导入多个配置类 |
在使用@Bean创建bean对象的时候,如果方法在创建的过程中需要其他资源,这些资源会有两大类,分别是简单数据类型和引用数据类型
以下面 Durid 的管理为例
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
类中提供四个属性
使用@Value
注解引用
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("password")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
但是一般数据库连接的四要素是写在 properties 配置文件中的
@PropertySource
加载 jdbc.properties 配置文件@Value
注解属性的值,将其修改为 ${key},key 就是键值对中的键的值假设在构建 DataSource 对象的时候,需要用到 BookDao 对象,该如何把 BookDao 对象注入进方法内
在 SpringConfig 中扫描 BookDao
//扫描的目的是让Spring能管理到BookDao,也就是说要让IOC容器中有一个bookDao对象
@Configuration
@ComponentScan("com.XXX.dao")
@Import({JdbcConfig.class})
public class SpringConfig {
}
在 JdbcConfig 类的方法上添加参数
@Bean
public DataSource dataSource(BookDao bookDao){//直接将需要的类对象作为参数,写在方法的参数位置
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
//引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。