一、核心架构
二、核心概念
(一)IoC/DI概念(达到充分解耦)
(二)IoC容器
(1)快速入门
- 导入坐标
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- resources文件下创建配置文件New>>XML Configuuration File>>Spring Config>>一般命名applicationContext.xml
- 在配置文件配置Bean
<!-- id:自定义的对象名,通过id值获取对应的bean,值要唯一 -->
<!-- class:bean的类型, new哪一个类(注意不要new成接口了,而且要全路径名) -->
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="cn.fn.service.BookServiceImpl"/>
- 初始化IoC容器,通过容器获取Bean
public static void main(String[] args) {
//初始化IoC容器,参数为配置文件名称
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过IoC容器获取Bean,使用方法.getBean("id名"),然后强转为对应的类型
BookService bookService = (BookService) ctx.getBean("bookService");
//获取到了相应的对象,下面直接使用对象就可以了
bookService.save();
}
- 附上BookDaoImpl和BookServiceImpl
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDao save...");
}
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
@Override
public void save() {
System.out.println("BookService save....");
bookDao.save();
}
(三)DI依赖注入
(1)快速入门
在IoC容器的基础上:
- 在上面的例子中,BookServiceImpl里面依然有new对象的形式存在,需要解耦。
- 所以先声明一个变量,而不new这个对象,然后提供一个setBookDao的方法。
public class BookServiceImpl implements BookService {
//不要new,仅声明
private BookDao bookDao;
@Override
public void save() {
System.out.println("BookService save....");
bookDao.save();
}
//提供一个set方法给bookDao设置一个对象
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 在applicationContext.xml中,因为是BookService需要BookDao的对象,所以在BookService里面新增一个
property
标签。
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="cn.fn.service.impl.BookServiceImpl">
<!-- name表示BookServiceImpl里private BookDao bookDao;这个bookDao变量 -->
<!-- ref表示需要的bookDao对象 -->
<property name="bookDao" ref="bookDao"></property>
</bean>
三、Bean
- 对于Bean,可以使用
name="XXXXX"
设置他的别名。可以使用,
;
和
<bean id="bookDao" name="dao bookDao2" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookService" name="bookService023" class="cn.fn.service.impl.BookServiceImpl">
<!-- name表示BookServiceImpl里private BookDao bookDao;这个bookDao变量 -->
<!-- ref表示需要的bookDao对象 -->
<property name="bookDao" ref="bookDao"></property>
</bean>
- Bean的作用范围
public static void main(String[] args) {
//初始化IoC容器,参数为配置文件名称
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService01 = (BookService) ctx.getBean("bookService");
BookService bookService02 = (BookService) ctx.getBean("bookService");
}
默认情况下,这种调用Bean的方式只是共同指向同一个Bean对象,并没有生成两个Bean对象。
需要使用scope="prototype/singleton"
设置范围,singleton
表示单例模式,默认设置;prototype
表示生成不同对象。
eg:<bean id="bookService" class="cn.fn.service.impl.BookServiceImpl" scope="prototype">
(一)Bean实例化三种方式
例子的目录结构
(1)构造方法(常用)
在类中有无参构造方法即可(Spring内部通过反射创建对象)。
(2)静态工厂实例化(早期使用,了解)
(3)实例工厂实例化(早期使用,了解)
实例工厂和第2种方法类似,只是他的工厂方法不是static,所以在使用时需要先new实例工厂(不使用spring的话是这样),再用对象调实例工厂的方法来创建需要的对象。
(4)使用FactoryBean实例化Bean(方式3的变种,常用)
注:此方法默认是单例模式(可以在FactoryBean中改)
- 前期环境:接口>>实现接口类>>factoryBean类(如UserDaoFactoryBean)。
- factory类继承
FactoryBean<T>
接口,T表示工厂new对象的类型,比如UserDao。 - factory类中重写两个方法。
- 配置Bean,class指向这个factory类,其余不用配。
UserDaoFactoryBean
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
@Override
public UserDao getObject() throws Exception {
//返回接口的实现类
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
//反射接口的字节码文件
return UserDao.class;
}
//继承下面这个方法,true表示单例模式(默认),false表示非单例
@Override
public boolean isSingleton() {
return false;
}
}
IoC容器配置文件
<!-- 注意这里虽然这样写,但造出来的是该类中getObject()方法返回的对象 -->
<bean id="userDao" class="cn.fn.dao.factory.UserDaoFactoryBean"/>
使用Bean
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
(二)Bean生命周期
bean生命周期:表示Bean从创建到销毁的整个过程。
bean生命周期控制:在bean创建后–到销毁前做一些事情。
(1)原始写法
- 在接口实现类中(如BookDaoImpl),初始化和销毁的方法名称可以自定义
public class BookDaoImpl implements BookDao {
public void init() {
//做一些初始化操作,如读取文件
System.out.println("init...");
}
@Override
public void save() {
System.out.println("BookDao save...");
}
public void destroy() {
//最后销毁操作,如关闭流等
System.out.println("destroy...");
}
}
- 在Bean配置文件中,
init-method=" "
指定谁是初始化方法,destory-method
指定谁是销毁方法
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
- 在使用方法中,首先ctx的声明类型要是
ClassPathXmlApplicationContext
(因为有close方法)。
然后.registerShutdownHook()
表示向JVM注册标记,等spring运行完再关闭JVM;.close()
表示强制关闭IoC容器。
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
ctx.registerShutdownHook();
// ctx.close();
}
}
(2)spring提供的规范方法
- 在实现类中实现InitializingBean和DisposableBean接口(重写两个接口的方法)
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
@Override
public void save() {
System.out.println("BookService save....");
bookDao.save();
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
//表示销毁方法
@Override
public void destroy() throws Exception {
}
//表示初始化方法
@Override
public void afterPropertiesSet() throws Exception {
}
}
- 在Bean中就不需要
init-method
和destroy-method
来指定了。
(3)总结
四、DI依赖注入
(一)setter注入
setter注入就是在实现类中提供set方法。
(1)简单类型(基本数据类型和String)
比如一个int和String类型的数据,要让IoC容器来传递值。
- 在实现类中定义变量但不给值,并提供设置值的set方法。
- 在配置Bean时,使用property的
value
属性注入需要的值。
BookDaoImpl
public class BookDaoImpl implements BookDao {
//定义变量
private int connectionNum;
private String databaseName;
@Override
public void save() {
System.out.println("BookDao save..." + connectionNum + ":" + databaseName);
}
//提供set方法
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
IoC容器配置文件
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl">
<!-- name是哪一个变量,value表示给什么值 -->
<property name="connectionNum" value="10"/>
<property name="databaseName" value="db1"/>
</bean>
(2)引用类型
引用类型前面说过,就是传递对象过去,使用ref指定需要的bean的id。
(二)构造器注入
构造器注入就是在构造方法里传参。
(1)简单类型
就是在bean的类中写一个构造方法
public class BookDaoImpl implements BookDao {
private int connectionNum;
private String databaseName;
//新建一个构造方法
public BookDaoImpl(int connectionNum, String databaseName) {
this.connectionNum = connectionNum;
this.databaseName = databaseName;
}
@Override
public void save() {
System.out.println("BookDao save..." + connectionNum + ":" + databaseName);
}
}
然后在配置文件中,使用<constructor-arg>
标签替代<property>
标签。注意name代表构造方法的行参名。
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl">
<!-- <property name="connectionNum" value="10"/>-->
<!-- <property name="databaseName" value="db1"/>-->
<constructor-arg name="connectionNum" value="10"/>
<constructor-arg name="databaseName" value="db1"/>
</bean>
(2)引用类型
引用类型和上面一样,只是将value
换成ref
就行。
(三)注入方式怎么选
一句话:spring推荐构造器,但我们自己开发模块还是推荐setter。
(四)扩展(了解)
像上面这样写,耦合度较高,比如属性name
依赖bean类的命名,所以有了下面两个Bean配置中的属性,type
和index
。
(五)依赖自动注入(只支持引用类型,不能用在简单类型上)
自动注入:IoC容器根据bean所依赖的的资源在容器中自动查找并注入到bean中的过程。
自动装配方式:
- 按类型(byType,常用)
- 按名称(byName,耦合高,不推荐)
- 按构造方法(constructor,不推荐)
(1)具体操作
不同方式的共同特点:
- 必须在bean类提供set方法。
- 配置文件中使用
autowire=""
属性。 - 以下面的为例,bookService需要bookDao的依赖,所以在bookService中配置
autowire
属性。
- 按类型方式byType(常用)
在依赖的bean配置文件中,使用autowire="byType"
。
这种方式是通过class属性
去判断,所以两个class属性相同的bean会报错,要避免。
<!-- 以byType方式,class名称相同会报错 -->
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookDao2" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="cn.fn.service.impl.BookServiceImpl" scope="prototype" autowire="byType"/>
- 按名称方式byName
首先,BookServiceImpl
需要一个BookDao
,里面有一个setBookDao()
方法
id="bookDao
这里的属性值要和BookServiceImpl
里setBookDao()
方法set后面的单词相同(bookDao)。
<!-- 以byName方式,class可以相同,但id不能相同 -->
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookDao2" class="cn.fn.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="cn.fn.service.impl.BookServiceImpl" scope="prototype" autowire="byName"/>
(2)总结
(六)如何注入集合
给出以下例子:
BookDaoImpl.java
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void save() {
System.out.println("BookDaoImp遍历所有:");
Stream.of(array).forEach(System.out::print);
System.out.println("List: " + list);
System.out.println("Set: " + set);
System.out.println("Map: " + map);
System.out.println("Properties" + properties);
}
}
xml中
<bean id="bookDao" class="cn.fn.dao.impl.BookDaoImpl">
<!-- 数组 -->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!-- List -->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</list>
</property>
<!-- Set -->
<property name="set">
<set>
<value>chongqing</value>
<value>chengdu</value>
<value>yunnan</value>
<value>yunnan</value>
</set>
</property>
<!-- Map -->
<property name="map">
<map>
<entry key="name" value="zhangsan"/>
<entry key="age" value="18"/>
<entry key="address" value="chongqing"/>
</map>
</property>
<!-- Properties -->
<property name="properties">
<props>
<prop key="className">test001</prop>
<prop key="methodName">read</prop>
</props>
</property>
</bean>
如果要包含引用类型,将各自的<value>
标签改为<ref bean=beanId">
。
(七)案例,使用Druid
五、Spring如何读取Properties文件
- 开辟一个命名空间(复制现有内容,再改)
原内容
添加命名空间(xmlns表示xmlNameSpace),把原有的beans单词换为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
">
- 在
<bean>
标签上,使用该命名空间,并指定配置文件(location
),context即命名空间的名称。
<context:property-placeholder location="jdbc.properties"/>
- 在bean中使用
value=${XXX}
代替原有的value=XXX
。
注意:最好使用jdbc.XXX
的形式,不然可能会报错。
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
(一)注意事项
- 如上文说,使用
username
不使用jdbc.username
会报错,是因为username
这个名称和系统配置冲突了,解决办法:
<!-- 加了一个system-properties-mode属性,表示不加载系统属性 -->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 如果有多个配置文件怎么加?在属性
location
中,可以使用通配符*
加载所有文件,也可以使用,
列举文件
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
或则
<context:property-placeholder location="jdbc001.properties,jdbc002.properties" system-properties-mode="NEVER"/>
- 对于第
2
步的写法,最正规的写法应该加上classPath:
(推荐)
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
- 最后,以上方式只能读取本项目的properties文件,要想读取其他jar包的properties,必须使用
classPath*:*.properties
的格式
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>