2018-12-27 Spring框架学习

Spring框架官网下载教程:https://blog.csdn.net/saszyl/article/details/70148303

1. 面试必备知识点

一. SpringAOP,IOC实现原理

在面试当中,AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器?这些东西都是经常会被问到的。
自己动手实现的 Spring IOC 和 AOP - 上篇

自己动手实现的 Spring IOC 和 AOP - 下篇

当没有IOC,可以使用工厂模式来模拟IOC,即创建一个工厂类,在工厂类当中创建几个静态方法,当调用这几个方法的时候,会返回这几个方法的所要返回的对象。但是创建工厂模式任然需要主动去调用,如果要实现自动去调用需要在配置文件当中配置。

package com.rl.spring.dao;

public interface UserDao {
    
    public void save();
}
package com.rl.spring.dao.impl;

import com.rl.spring.dao.UserDao;

public class UserDaoImpl implements UserDao {

    @Override
    public void save() {
        System.out.println("user被保存了");
    }
}

Spring实际上也是提供了这样的一个工厂,不过去区别是spring提供的工厂比这个工厂要强大的多。

package com.rl.spring.factory;

import com.rl.spring.dao.UserDao;
import com.rl.spring.dao.impl.UserDaoImpl;

public class FactoryBean {
    
    public static UserDao getDao(String name){
        if(name == "userDao"){
            return new UserDaoImpl();
        }
        return null;
    }
    public static UserDao getDao(String name){
        if(name == "userService"){
            return new UserServiceImpl ();
        }
        return null;
    }
}
package com.rl.spring.service;

public interface UserService {
    public void save();
}
package com.rl.spring.service.impl;

import com.rl.spring.dao.UserDao;
import com.rl.spring.dao.impl.UserDaoImpl;
import com.rl.spring.factory.FactoryBean;
import com.rl.spring.service.UserService;

public class UserServiceImpl implements UserService {

    /**
     * UserDaoImpl是我们自己创建的,
     * 我们自己创建DaoImpl();带来了很大的耦合性,项目不好维护,测试成本也高
     * 
     */
    private UserDao userDao = FactoryBean.getDao("userDao");
    
    @Override
    public void save() {
        userDao.save();
    }
}

二.AOP

AOP思想的实现一般都是基于代理模式 ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,JDK动态代理模式只能代理接口而不能代理类。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。通过预编译的方式在运行期通过动态代理实现的一种技术。
如果目标对象的的实现类实现了接口,Spring AOP将会采用JDK动态代理来生成AOP代理类。
如果目标对象的实现类没有实现接口,Spring AOP就会采用CGLIB动态代理来生成AOP代理类。

AOP可以分离系统的业务逻辑和系统服务(日志,安全等)

※静态代理、JDK动态代理、CGLIB动态代理讲解

我们知道AOP思想的实现一般都是基于 代理模式 ,所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。
Spring AOP 入门

带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念:5种类型的通知(Before,After,After-returning,After-throwing,Around);Spring中对AOP的支持:AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理模式,Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理,

※Spring AOP 基于AspectJ注解如何实现AOP

AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易

Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别

※探秘Spring AOP(慕课网视频,很不错)

慕课网视频,讲解的很不错,详细且深入

spring源码剖析(六)AOP实现原理剖析

通过源码分析Spring AOP的原理

IOC:

Spring IOC的初始化过程:

Spring IOC的初始化过程

[Spring框架]Spring IOC的原理及详解。

Spring IOC核心源码学习

比较简短,推荐阅读。

Spring IOC 容器源码分析

强烈推荐,内容详尽,而且便于阅读。

Spring事务管理

可能是最漂亮的Spring事务管理详解

Spring编程式和声明式事务实例讲解

其他

Spring单例与线程安全:

Spring框架中的单例模式(源码解读)

单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时,使用了 多重判断加锁 的单例模式。

Spring源码阅读

阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己字面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。

Spring源码阅读

  • spring-core
  • spring-aop
  • spring-context
  • spring-task
  • spring-transaction
  • spring-mvc
  • guava-cache

2.Spring当中bean的生命周期及作用域?

在spring当中将所有纳入spring管理的对象皆称为bean,简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。

一.bean的作用域

在bean当中可以灵活的设置对象的作用域,Spring Framework支持五种作用域,分别阐述如下表。当然你也可以在class里面设置bean的作用域

2018-12-27 Spring框架学习_第1张图片
图片.png

五种作用域中,request、session 和 global session 三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。

二. singleton——唯一 bean 实例

当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。 singleton 是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,但我们可以指定Bean节点的 lazy-init=”true” 来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ,可以这样配置:


也可以通过 @Scope 注解(它可以显示指定bean的作用范围。)的方式

@Service
@Scope("singleton")
public class ServiceImpl{

}

2. prototype——每次请求都会创建一个新的 bean 实例

当一个bean的作用域为 prototype,表示一个 bean 定义对应多个对象实例。 prototype 作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。 在 XML 中将 bean 定义成 prototype ,可以这样配置:

  
 或者
 

通过 @Scope 注解的方式实现。

@Service
@Scope("prototype")
public class ServiceImpl{

}

3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效

request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。 在 XML 中将 bean 定义成 request ,可以这样配置:


#4. session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效

session只适用于Web程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。


5. globalSession

global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。


3.bean的生命周期

Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。

有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情,比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。

1.实现InitializingBean和DisposableBean接口

实现了这2个接口之后,无需再bean当中定义init-method和destroy-method方法,bean容器会自动去调用afterProperties()与destory()方法,如果没有实现这2个接口,要调用init-method和destroy-method方法,必须要在bean当中定义。


2018-12-27 Spring框架学习_第2张图片
图片.png

2018-12-27 Spring框架学习_第3张图片
图片.png

2018-12-27 Spring框架学习_第4张图片
图片.png

图片.png

需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。

这种方式比较推荐,因为可以自己创建方法,无需将Bean的实现直接依赖于spring的框架。

3.使用@PostConstruct和@PreDestroy注解

除了xml配置的方式,Spring 也支持用 @PostConstruct和 @PreDestroy注解来指定 init 和 destroy 方法。这两个注解均在javax.annotation 包中。为了注解可以生效,需要在配置文件中定义

org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config

例子如下:

public class GiraffeService {
    @PostConstruct
    public void initPostConstruct(){
        System.out.println("执行PostConstruct注解标注的方法");
    }
    @PreDestroy
    public void preDestroy(){
        System.out.println("执行preDestroy注解标注的方法");
    }
}

配置文件:


4.实现*Aware接口 在Bean中使用Spring框架的一些对象

有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作,比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为Aware的接口。*

这些接口均继承于org.springframework.beans.factory.Aware标记接口,并提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。 网上说,这些接口是利用观察者模式实现的,类似于servlet listeners,目前还不明白,不过这也不在本文的讨论范围内。 介绍一些重要的Aware接口:

  • ApplicationContextAware: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
  • BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域。
  • BeanNameAware:获得Bean在配置文件中定义的名字。
  • ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件。
  • ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。
  • ServletConfigAware: 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。
public class GiraffeService implements   ApplicationContextAware,
        ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
        BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
         @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" +  beanFactory.isSingleton("giraffeService"));
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("执行setBeanName:: Bean Name defined in context="
                + s);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("执行setApplicationContext:: Bean Definition Names="
                + Arrays.toString(applicationContext.getBeanDefinitionNames()));
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("执行setApplicationEventPublisher");
    }
    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("执行setEnvironment");
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
        System.out.println("执行setResourceLoader:: Resource File Name="
                + resource.getFilename());
    }
    @Override
    public void setImportMetadata(AnnotationMetadata annotationMetadata) {
        System.out.println("执行setImportMetadata");
    }
}

BeanPostProcessor

上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程, Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行。

例子如下:

public class CustomerBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName);
        return bean;
    }
}

要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中


总结

所以。。。结合第一节控制台输出的内容,Spring Bean的生命周期是这样纸的:

  • Bean容器找到配置文件中 Spring Bean 的定义。
  • Bean容器利用Java Reflection API创建一个Bean的实例。
  • 如果涉及到一些属性值 利用set方法设置一些属性值。
  • 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
  • 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  • 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  • 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
  • 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
  • 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
  • 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
  • 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。

用图表示一下(图来源:http://www.jianshu.com/p/d00539babca5):%EF%BC%9A)

image

与之比较类似的中文版本:

2018-12-27 Spring框架学习_第5张图片
image

其实很多时候我们并不会真的去实现上面说描述的那些接口,那么下面我们就除去那些接口,针对bean的单例和非单例来描述下bean的生命周期:
单例管理的对象

当scope=”singleton”,即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。如下配置:

如果想对所有的默认单例bean都应用延迟初始化,可以在根节点beans设置default-lazy-init属性为true,如下所示:

默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。在创建对象的时候先调用构造器,然后调用 init-method 属性值中所指定的方法。对象在被销毁的时候,会调用 destroy-method 属性值中所指定的方法(例如调用Container.destroy()方法的时候)。写一个测试类,代码如下:

public class LifeBean {
    private String name;  

    public LifeBean(){  
        System.out.println("LifeBean()构造函数");  
    }  
    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        System.out.println("setName()");  
        this.name = name;  
    }  

    public void init(){  
        System.out.println("this is init of lifeBean");  
    }  

    public void destory(){  
        System.out.println("this is destory of lifeBean " + this);  
    }  
}

life.xml配置如下:


测试代码:

public class LifeTest {
    @Test 
    public void test() {
        AbstractApplicationContext container = 
        new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life");
        System.out.println(life1);
        container.close();
    }
}

运行结果:

LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@573f2bb1
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

非单例管理的对象

当scope=”prototype”时,容器也会延迟初始化 bean,Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用getBean方法时)。在第一次请求每一个 prototype 的bean 时,Spring容器都会调用其构造器创建这个对象,然后调用init-method属性值中所指定的方法。对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,这个类型的对象有很多个,Spring容器一旦把这个对象交给你之后,就不再管理这个对象了。destory方法在关闭容器或者异常退出的时候才会执行。
为了测试prototype bean的生命周期life.xml配置如下:


测试程序:

public class LifeTest {
    @Test 
    public void test() {、
        AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml");
        LifeBean life1 = (LifeBean)container.getBean("life_singleton");
        System.out.println(life1);

        LifeBean life3 = (LifeBean)container.getBean("life_prototype");
        System.out.println(life3);
        container.close();
    }
}

运行结果:

LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@573f2bb1
LifeBean()构造函数
this is init of lifeBean
com.bean.LifeBean@5ae9a829
……
this is destory of lifeBean com.bean.LifeBean@573f2bb1

可以发现,对于作用域为 prototype 的 bean ,其destroy方法并没有被调用。如果 bean 的 scope 设为prototype时,当容器关闭时,destroy 方法不会被调用。对于 prototype 作用域的 bean,有一点非常重要,那就是 Spring不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。 不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用)。谈及prototype作用域的bean时,在某些方面你可以将Spring容器的角色看作是Java new操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。

总结:Spring容器可以管理singleton 作用域下 bean 的生命周期,在此域下,spring可以精准的知道bean是什么时候创建的,什么时候完成初始化的,什么时候销毁的。但是对于prototype 作用域下的bean,Spring只负责创建,当容器创建bean的实例之后,该对象就会交给客户端代码管理,Spring容器不在跟踪任何生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。

你可能感兴趣的:(2018-12-27 Spring框架学习)