Spring基础
Posted xiaoxin-it
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring基础相关的知识,希望对你有一定的参考价值。
什么是Spring?
Spring框架存在的意义就是为了降低耦合度, 根据不同的代码采用不同的方式, 通过IOC来降低主业务逻辑之间的耦合度, 通过AOP来降低系统级服务(如日志、安全、事务等)和主业务逻辑之间的耦合度. 此外还提供了一个Web层的框架Spring MVC.
Spring容器
在介绍Spring容器之前, 我们先介绍什么是bean. 简单来说, 被Spring容器管理的对象就叫bean, 比如Controller/Action, Service, Dao.
<bean id="userControllerId" class="org.a.IOC.UserController"></bean> <bean id="userServiceId" class="org.a.IOC.UserServiceImpl"></bean> <bean id="BookDaoId" class="org.a.IOC.UserDaoImpl"></bean>
Spring容器管理着项目中所有bean对象的实例化与装配, 有两种, 分别是 BeanFactory 和 ApplicationContext. 其中 ApplicationContext 是 BeanFactory 的一个子接口, 补充了以下几个功能:
- 更容易与Spring的AOP特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用程序层特定的上下文,如web应用程序中使用的WebApplicationContext
上述几个功能只需了解就行, 对于两者的区别, 我们需要记住的是:
- BeanFactory 采用延迟加载策略, 在第一次调用getBean()时, 才去读取配置信息, 生成某个bean的实例.
- ApplicationContext 在初始化时就会读取配置信息, 生成所有bean的实例.
- 上面两种特征导致的结果是, 如果配置信息有错, BeanFactory在调用getBean()时才会抛异常, 而 ApplicationContext 在初始化的时候就会抛异常, 帮助我们及时检查配置是否正确.
- 两者都支持BeanPostProcessor和BeanFactoryPostProcessor, 但BeanFactory需要手动注册, 而ApplicationContext是自动注册.
大部分情况下我们使用的都是ApplicationContext, 这篇博客下面的内容也都是基于ApplicationContext.
配置元数据
Spring容器通过读取元数据来获取要实例化、装配的对象. 元数据有三种格式, 分别是XML文件, Java注解和Java代码.
<beans> <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl"> <property name="bookDao" ref="BookDaoId"></property> </bean> <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean> </beans>
上面这段代码就是基于XML文件的元数据, ApplicationContext 的两个实现类 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 用来加载XML格式的元数据. 两者的区别在于 ClassPathXmlApplicationContext 是基于类路径, 而 FileSystemXmlApplicationContext 是基于文件路径.
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); ApplicationContext context = new FileSystemXmlApplicationContext("D:\\springtest\\src\\main\\resources\\spring\\bean.xml");
ApplicationContext的实现类AnnotationConfigApplicationContext用来加载Java注解格式的元数据.
@Configuration @ComponentScan(basePackages = "org.tyshawn") public class AppConfig { } AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
IOC
IoC也称为依赖注入(dependency injection, DI). 这是一个过程, 在这个过程中, 首先通过在对象实例上设置的属性来定义bean之间的依赖关系, 然后Spring容器在创建bean时注入这些依赖项(这个注入过程也叫做装配). 依赖注入有两种, 分别是基于构造方法的注入和基于Setter方法的注入.
基于构造方法注入
public interface IBookDao { void insert(); } public class BookDaoImpl implements IBookDao { @Override public void insert() { System.out.println("add book"); } } public interface IBookService { void addBook(); } public class BookServiceImpl implements IBookService { private IBookDao bookDao; public BookServiceImpl(IBookDao bookDao) { this.bookDao = bookDao; } @Override public void addBook() { this.bookDao.insert(); } } <beans> <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl"> <constructor-arg ref="BookDaoId"/> </bean> <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean> </beans> public class Test{ public static void main(String[] args) throws ParseException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); IBookService bookService = (IBookService) context.getBean("BookServiceId"); bookService.addBook(); } }
基于Setter方法的注入
public interface IBookDao { void insert(); } public class BookDaoImpl implements IBookDao { @Override public void insert() { System.out.println("add book"); } } public interface IBookService { void addBook(); } public class BookServiceImpl implements IBookService { private IBookDao bookDao; public void setBookDao(IBookDao bookDao) { this.bookDao = bookDao; } @Override public void addBook() { this.bookDao.insert(); } } <beans> <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl"> <property name="bookDao" ref="BookDaoId"></property> </bean> <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean> </beans> public class Test{ public static void main(String[] args) throws ParseException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); IBookService bookService = (IBookService) context.getBean("BookServiceId"); bookService.addBook(); } }
Bean的作用域
Bean的作用域有六种, 其中后四种只支持Web应用.
作用域 | 描述 |
---|---|
singleton | 默认. bean在每一个Spring容器内只有一个实例 |
prototype | 每次从Spring容器中获取到的bean都是一个新的实例 |
request | bean在每一个 HTTP Request 中只有一个实例, 只支持Web应用 |
session | bean在每一个 HTTP Session 中只有一个实例, 只支持Web应用 |
application | bean在每一个 ServletContext 中只有一个实例, 只支持Web应用 |
websocket | bean在每一个 WebSocket 中只有一个实例, 只支持Web应用 |
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/> <bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/> <bean id="loginAction" class="com.something.LoginAction" scope="request"/> <bean id="userPreferences" class="com.something.UserPreferences" scope="session"/> <bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
singleton和application的区别
(1) 在作用域为singleton时, bean在每一个Spring容器内只有一个实例, 而应用可以有多个容器.
(2) 在作用域为application时, bean在整个应用中只有一个实例.
(3) 作用域application只支持Web应用.
具有多例bean依赖的单例bean
一个bean的作用域是 singleton, 而它的属性的作用域是 prototype, 如下所示:
<bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl" scope="singleton"> <constructor-arg ref="BookDaoId"/> </bean> <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl" scope="prototype"></bean>
我们想要的效果是, 每次获取BookServiceId时都是同一个bean, 而它的属性BookDaoId都是一个新的bean. 但这种情况是不可能的, 因为BookServiceId只会实例化, 装载一次. 要想达到我们期望的效果, 需要使用方法注入:
方法注入
Spring框架通过使用来自CGLIB库的字节码生成器来动态生成覆盖该方法的子类来实现此方法注入.
public class BookServiceImpl implements IBookService { private IBookDao bookDao; public IBookDao getBookDao() { return bookDao; } @Override public void addBook() { IBookDao bookDao = getBookDao(); System.out.println(bookDao); } } <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl" scope="singleton"> <lookup-method name="getBookDao" bean="BookDaoId"></lookup-method> </bean> <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl" scope="prototype"></bean> public class Test{ public static void main(String[] args) throws ParseException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); BookServiceImpl bookService1 = (BookServiceImpl) context.getBean("BookServiceId"); BookServiceImpl bookService2 = (BookServiceImpl) context.getBean("BookServiceId"); bookService1.addBook(); bookService2.addBook(); } } org.tyshawn.dao.Impl.BookDaoImpl@6121c9d6 org.tyshawn.dao.Impl.BookDaoImpl@87f383f
Bean的生命周期
生命周期回调
(1) 初始化回调
在Spring容器将bean实例化, 设置属性值之后将会执行初始化回调. 初始化回调有两种设置方式:
方式一(推荐) <bean id="exampleInitBean1" class="org.tyshawn.example.ExampleBean" init-method="init"/> public class ExampleBean { public void init() { System.out.println("do some initialization work."); } } 方式二(不推荐) <bean id="exampleInitBean2" class="org.tyshawn.example.AnotherExampleBean"/> public class AnotherExampleBean implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("do some initialization work."); } } public class Test{ public static void main(String[] args) throws ParseException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); ExampleBean example1 = (ExampleBean) context.getBean("exampleInitBean1"); AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleInitBean2"); System.out.println(example1); System.out.println(example2); } } do some initialization work. do some initialization work. org.tyshawn.example.ExampleBean@4eb7f003 org.tyshawn.example.AnotherExampleBean@eafc191
(2) 销毁回调
当bean被销毁之前, 将会执行销毁回调. 销毁回调有两种设置方式:
方式一(推荐) <bean id="exampleDestoryBean1" class="org.tyshawn.example.ExampleBean" destroy-method="destory"/> public class ExampleBean { public void destroy() { System.out.println("do some destruction work."); } } 方式二(不推荐) <bean id="exampleDestoryBean2" class="org.tyshawn.example.AnotherExampleBean"/> public class AnotherExampleBean implements DisposableBean { @Override public void destroy() { System.out.println("do some destruction work."); } } public class Test{ public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); ExampleBean example1 = (ExampleBean) context.getBean("exampleDestoryBean1"); AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleDestoryBean2"); //当容器被关闭时, 容器内的bean就被销毁了 context.registerShutdownHook(); } } do some destruction work. do some destruction work.
(3) 初始化回调 / 销毁回调的两种方式同时配置
当 初始化回调 / 销毁回调的两种方式同时配置时会出现什么结果呢?
<bean id="exampleDestoryBean2" class="org.tyshawn.example.AnotherExampleBean" destroy-method="cleanup"/> public class AnotherExampleBean implements DisposableBean { @Override public void destroy() { System.out.println("do some destruction work."); } public void cleanup() { System.out.println("do some cleanup work."); } } public class Test{ public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleDestoryBean2"); context.registerShutdownHook(); } } do some destruction work. do some cleanup work.
结果是两种方式都执行, 但 DisposableBean / InitializingBean 在前, destroy-method / init-method 在后.
(4) 启动和关闭回调
如果Spring容器中的bean实现了 Lifecycle 接口, 当Spring容器启动时, 将会调用这些bean的start()方法, 当Spring容器关闭时, 将会调用这些bean的stop()方法.
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
在很多情况下, start()方法和stop()方法的调用顺序是重要的, 如果两个bean存在依赖关系, 比如 a 依赖 b (b是a的属性), 这时 a 先调用start()方法, b 先调用stop()方法. 但如果我们不知道依赖关系, 却想让 a 在 b 之前调用start()方法, 这时我们就可以用 SmartLifecycle 接口代替 Lifecycle 接口.
public interface Phased { int getPhase(); } public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }
SmartLifecycle接口的方法介绍:
-
当 isAutoStartup() 返回true, Spring容器启动时会调用bean的 start() 方法
-
当 isRunning() 返回true, Spring容器销毁时会调用bean的 stop(Runnable runnable) 方法
-
getPhase() 返回的是优先级, 当有多个bean时, 返回值大的先执行start()方法, 销毁时顺序相反. 容器内没有实现SmartLifecycle接口, 而实现了Lifecycle接口的bean返回值是0. 负数代表最高优先级.
BeanPostProcessor
如果我们想在Spring容器完成bean的初始化前后加一些定制逻辑, 我们可以向容器注册一个或多个定制BeanPostProcessor实现. 当有多个BeanPostProcessor定制时, 我们同时要实现Ordered接口.
bean的生命周期
bean在Spring容器中的生命周期是怎么样的呢?
(1) 实例化
(2) 设置属性值
(3) 调用BeanNameAware的setBeanName()方法
(4) 调用BeanFactoryAware的setBeanFactory()方法
(5) 调用ApplicationContext的setApplicationContext()方法
(6) 调用BeanPostProcessor的postProcessBeforeInitialization()方法
(7) 调用InitializingBean的afterPropertiesSet()方法
(8) 调用xml配置的初始化方法
(9) 调用BeanPostProcessor的postProcessAfterInitialization()方法
(10) 容器启动.
(11) bean可以使用了.
(12) 容器关闭.
(13) 调用DisposableBean的destory()方法
(14) 调用xml配置的销毁方法
<bean id="exampleBean" class="org.tyshawn.example.ExampleBean" init-method="init" destroy-method="dest"/> <bean class="org.tyshawn.example.CustomBeanPostProcessor"/> public class CustomBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("调用BeanPostProcessor的postProcessBeforeInitialization()方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("调用BeanPostProcessor的postProcessAfterInitialization()方法"); return bean; } } public class ExampleBean implements InitializingBean, DisposableBean, SmartLifecycle, BeanNameAware, BeanFactoryAware, ApplicationContextAware { private boolean isRunning = false; public void init() { System.out.println("调用xml配置的初始化方法"); } public void dest() { System.out.println("调用xml配置的销毁方法"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("调用InitializingBean的afterPropertiesSet()方法"); } @Override public void destroy() { System.out.println("调用DisposableBean的destory()方法"); } @Override public boolean isAutoStartup() { return true; } @Override public void stop(Runnable runnable) { runnable.run(); System.out.println("容器关闭."); } @Override public void start() { isRunning = true; System.out.println("容器启动."); } @Override public void stop() { } @Override public boolean isRunning() { return isRunning; } @Override public int getPhase() { return -1; } @Override public void setBeanName(String beanName) { System.out.println("调用BeanNameAware的setBeanName()方法"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("调用BeanFactoryAware的setBeanFactory()方法"); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("调用ApplicationContext的setApplicationContext()方法"); } public void sayHello() { System.out.println("bean可以使用了."); } } public class Test{ public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml"); ExampleBean example = (ExampleBean) context.getBean("exampleBean"); example.sayHello(); context.registerShutdownHook(); } } 调用BeanNameAware的setBeanName()方法 调用BeanFactoryAware的setBeanFactory()方法 调用ApplicationContext的setApplicationContext()方法 调用BeanPostProcessor的postProcessBeforeInitialization()方法 调用InitializingBean的afterPropertiesSet()方法 调用xml配置的初始化方法 调用BeanPostProcessor的postProcessAfterInitialization()方法 容器启动. bean可以使用了. 容器关闭. 调用DisposableBean的destory()方法 调用xml配置的销毁方法
FactoryBean
面试中常常问到FactoryBean和BeanFactory的区别, 我们已经知道BeanFactory是一个Spring容器, 应用中所有bean的实例都存储在其中, 那FactoryBean是什么呢?
FactoryBean是一个生成bean的工厂. 一般情况下我们都是用XML文件来配置bean, 但如果我们有复杂的初始化逻辑, 相对于冗长的XML, 用Java代码可以更好地表达, 这时我们就可以创建自己的FactoryBean, 在该类中编写复杂的初始化逻辑, 然后将定制的FactoryBean插入容器中.
以上是关于Spring基础的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot:thymeleaf 没有正确渲染片段
What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段
[vscode]--HTML代码片段(基础版,reactvuejquery)
spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段
Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]
解决spring-boot启动中碰到的问题:Cannot determine embedded database driver class for database type NONE(转)(代码片段