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(转)(代码片段