1.Spring核心思想
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
- IOC(Inversion of Control)控制反转
在使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。Spring技术对IOC思想进行了实现,提供了一个容器,称为IOC容器,用来充当IOC思想中的"外部"。
- Bean
IOC容器中所存放的一个个对象就叫Bean或Bean对象
- DI(Dependency Injection)依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
在Spring中,使用IOC容器管理bean(IOC),在IOC容器内将有依赖关系的bean进行关系绑定(DI)
最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系,达到充分解耦的目的
2.入门案例
1.创建所需类
创建BookDao,BookDaoImpl,BookService和BookServiceImpl四个类
BookDao
public interface BookDao {
public void save();
}
BookDaoImpl
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save...");
}
}
BookService
public interface BookService {
public void save();
}
BookServiceImpl
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void save() {
System.out.println("book service save...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
2.在pom.xml添加对应依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
3.在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">
<!--
id:bean的id
class:bean的类型,即配置bean的全路径名
-->
<bean id="bookDao" class="com.Dao.impl.BookDaoImpl"/>
<!--
DI:
name:表示配置哪一个具体的属性,
是对应BookServiceImpl里面的那个私有bookDao,利用set方法进行对象注入
ref: 表示参照哪一个bean,是上面一个Bean对象的id
-->
<bean id="bookService" class="com.Service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
4.编写测试类
在com下创建一个Test类
public class Test {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取容器中的Bean对象
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
控制台输出
3.bean的基础配置和实例化方法
3.1bean的基础配置
3.1.1bean的id和class属性
<!--
id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一
class:bean的类型,即配置bean的全路径名
-->
<bean id="" class=""/>
3.1.2bean的name属性
bean的name属性可以定义别名,别名可以有多个,使用逗号,分号,空格进行分隔
<!--bookDao2是bookDao的别名-->
<bean id="bookDao" name="bookDao2" class="com.Dao.impl.BookDaoImpl"/>
<!--bookService2和bookService3都是bookService的别名-->
<bean id="bookService" name="bookService2 bookService3" class="com.Service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao2"/>
</bean>
在测试类中测试
public class Test {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取容器中的Bean对象
BookService bookService = (BookService) ctx.getBean("bookService3");
bookService.save();
}
}
控制台输出
3.1.3bean的scope属性
bean的作用范围是由scope属性来配置的,在不配置scope属性的情况下,Spring创建的对象默认都是单例的
在com包下创建一个TestScope类
public class TestScope {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
控制台输出
scope属性有singleton和prototype两个值
当设置scope="singleton"时
<bean id="bookDao" name="bookDao2" scope="singleton" class="com.Dao.impl.BookDaoImpl"/>
控制台输出
当设置scope="prototype"时
<bean id="bookDao" name="bookDao2" scope="prototype" class="com.Dao.impl.BookDaoImpl"/>
控制台输出
当scope="singleton"(默认)时,所创建的bean对象都是单例的,
当scope="prototype"时,所创建的bean对象为非单例的
3.2bean的三种实例化方法
3.2.1构造方法
在BookDaoImpl中写一个无参构造方法
public BookDaoImpl() {
System.out.println("book dao constructor is running ....");
}
运行Test类
在利用构造方法进行实例化时,无论是public还是private,因为Spring底层用的是反射,所以都能成功创建函数
但不能使用含有参数的构造方法,因为Spring底层调用的类是无参构造方法,当我们创建了一个含参的构造方法时,系统便不在给我们默认创建一个无参构造方法,会导致程序报错
3.2.2静态工厂
创建OrderDao和OrderDaoImpl类
OrderDao
public interface OrderDao {
public void save();
}
OrderDaoImpl
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
创建一个com.factory.OrderDaoFactory类
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
在applicationContext.xml中写入对应的bean
<!--factory-method:工厂内对应的方法名-->
<bean id="orderDao" class="com.factory.OrderDaoFactory" factory-method="getOrderDao"/>
创建一个TestInstanceOrder测试类
public class TestInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
控制台输出
之所以会输出之前创建的那个无参构造方法,是因为Spring 在启动时会把 applicationContext.xml 配置文件中所有的 bean 都实例化并装配好,即使它们没有被调用,也会创建对应的 bean 实例。这是因为 Spring 使用了依赖注入实现 bean 之间的解耦,同时也避免了循环依赖等问题的发生
如果希望避免无用的 bean 实例化,可以考虑使用懒加载(lazy loading)的方式,在需要调用某个 bean 时再进行实例化,可以通过将 bean 的 lazy-init 属性设置为 true 来开启懒加载模式
<bean id="bookDao" name="bookDao2" scope="prototype" lazy-init="true" class="com.Dao.impl.BookDaoImpl"/>
<bean id="bookService" name="bookService2 bookService3" lazy-init="true" class="com.Service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao2"/>
</bean>
<bean id="orderDao" class="com.factory.OrderDaoFactory" lazy-init="true" factory-method="getOrderDao"/>
如果只设置了bookDao,在spring创建bookService的时候也会创建其所依赖的bookDao对象,所以都启用懒加载模式
控制台输出
3.2.3动态工厂(FactoryBean)
- 普通方法
创建UserDao和UserDaoImpl类
UserDao
public interface UserDao {
public void save();
}
UserDaoImpl
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
创建一个com.factory.UserDaoFactory类
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
在applicationContext.xml中写入对应的bean
<!--
第一个userFactory从创建对应的工厂实例化对象
第二个userDao依赖第一个userFactory对象,调用对应方法
-->
<bean id="userFactory" class="com.factory.UserDaoFactory" lazy-init="true"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory" lazy-init="true"/>
创建一个TestInstanceUser测试类
public class TestInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
控制台输出
由于上述过程太过繁琐,Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。
- FactoryBean
创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
// //如果需要创建的对象是非单例则重写isSingleton()方法
// public boolean isSingleton() {
// return false;
// }
}
在applicationContext.xml进行配置
<bean id="userDaoFactoryBean" class="com.factory.UserDaoFactoryBean"/>
创建一个TestFactorBean测试类
public class TestFactorBean {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDaoFactoryBean = (UserDao) ctx.getBean("userDaoFactoryBean");
userDaoFactoryBean.save();
}
}
控制台输出
在具体实践中,构造方法和FactoryBean是使用最多的两种方法,如果需要含参的构造方法,则也需重写无参的构造方法,保证Spring能调用对应的无参构造方法
3.3bean的三种获取方式
获取bean有如下三种获取方式
1.按名称获取,但需进行类型强转
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
2.按名称获取,解决了类型强转的问题
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
3.按类型获取,但必须确保IoC容器中该类型对应的bean只有一个
BookDao bookDao = ctx.getBean(BookDao.class);
3.4bean的生命周期
bean的生命周期是bean对象从创建到销毁的整体过程,可以控制bean创建后到销毁前做的一些事情。
对于bean的整个生命周期
初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
使用 bean
- 执行业务操作
关闭 / 销毁容器
- 执行bean销毁方法
以下具体实现在bean创建之后和bean销毁之前的两个阶段
- bean创建之后,想要添加内容,比如用来初始化需要用到资源
- bean销毁之前,想要添加内容,比如用来释放用到的资源
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void save() {
System.out.println("book service save...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
//查看是先进行setter注入还是初始化
System.out.println("set ...");
this.bookDao = bookDao;
}
@Override
//bean销毁前对应的操作
public void destroy() throws Exception {
System.out.println("service destory");
}
@Override
//bean初始化对应的操作
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
运行Test测试类
会发现没有销毁前对应的操作,这是因为Spring的IOC容器是运行在JVM中运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行 main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了,所以没有调用对应的destroy方法
要解决该问题有如下两种解决办法
- close关闭容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
在执行的业务操作结束后调用close方法
ctx.close();
Test类做如下更改
public class Test {
public static void main(String[] args) {
//获取IOC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取容器中的Bean对象
BookService bookService = (BookService) ctx.getBean("bookService3");
bookService.save();
ctx.close();
}
}
控制台输出
- 注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器
调用ctx的registerShutdownHook()方法
ctx.registerShutdownHook();
Test类做如下更改
public class Test {
public static void main(String[] args) {
//获取IOC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//registerShutdownHook()不用放在业务结束后,JVM退出之前会回调此函数来关闭容器
ctx.registerShutdownHook();
//获取容器中的Bean对象
BookService bookService = (BookService) ctx.getBean("bookService3");
bookService.save();
// ctx.close();
}
}
控制台输出
注:ConfigurableApplicationContext是ApplicationContext的子类
close和registerShutdownHook都可以用来关闭容器,不同的是close()是在调用的时候关闭,registerShutdownHook()是在JVM退出前调用关闭。