SpringFramework详解

发布时间 2023-05-30 17:48:44作者: 我若安好,便是晴天

一、什么是Spring Framework

  Spring Framework 为基于 Java 的现代企业应用程序提供了一个全面的编程和配置模型。它是一个开源框架,集成了IOC、DI与AOP容器技术的框架。

  Spring 是应用程序级别的基础支持,专注于企业应用程序的“管道”,因此团队可以只关心应用程序的业务逻辑,而无需关注与特定部署环境之间的联系。

  Spring模块构建在核心容器(Beans, Context和Core)之上,核心容器定义了创建、配置和管理Bean的方式。Spring的设计理念是面向Bean编程,spring框架所有的容器组件都是为Bean对象的管理而服务的,Bean是一个被实例化、组装,并通过 Spring IOC 容器管理的对象。Bean在Spring 中才是真正的主角。

  Spring框架的模块结构如下:

 二、AOP面向切面编程理念

  编程思想到目前经历了从面向过程编程POP,到面向对象编程OOP,再到面向切面编程AOP的阶段。POP旨在按照顺序结构进行进行代码编写,符合人的思维方式。OOP则旨在将计算机代码划分为一个个能够组装在一起小模块,重在重用和扩展,其核心概念是类和对象,其三大特征表现为封装性、继承性、多态性。AOP是OOP的延续补充,它的本质是在不改变原有代码逻辑的情况下新增一些横切功能,通常运用在权限校验、日志记录、事务控制、性能监控、缓存控制等方面。

  在23种设计模式中有一种模式叫代理模式,它的思想是给某一个对象提供一个代理对象,并由代理对象来引用原对象,此时就可以在代理对象中调用原对象的方法,并且在其前后增加一些逻辑来增强功能,从而实现AOP切面编程。代理类其实就是一个中介,代理模式分为两类:

  • 静态代理:为代理类和被代理类抽象一个共同接口,在代理类中引用被代理类,调用者调用代理类就可以间接的使用被代理类。这种代理方式在编译期间就完成了代理,是一对一的代理,故称为静态代理。
/**
 * 代理类和被代理类的抽象
 */
interface Person {
    public void byTicket();
}

/**
 * 代理类
 */
public class ZhangshanProxy implements Person {
    private Zhangshan target;

    public ZhangshanProxy(Zhangshan target) {
        this.target = target;
    }

    @Override
    public void byTicket() {
        System.out.print("先查下票看还有没得...");
        target.byTicket();
        System.out.print("购买成功了...");
    }
}

/**
 * 被代理类
 */
public class Zhangshan implements Person {
    @Override
    public void byTicket() {
        System.out.print("张山买票...");
    }
}

public class StartApp {
    public static void main(String[] args) {
        /*
         * 调用者直接调用代理类,而不直接调用被代理的类
         */
        Person p = new ZhangshanProxy(new Zhangshan());
        p.byTicket();
    }
}
参考代码
  • 动态代理:借助jdk提供的接口和反射机制,一个代理类可以作为多个类的代理,在运行阶段才能确定被代理对象,这种代理称为动态代理。
/**
 * 被代理类的抽象
 */
interface Person {
    public void byTicket();
}

/**
 * 被代理类
 */
public class Zhangshan implements Person {
    @Override
    public void byTicket() {
        System.out.print("张山买票...");
    }
}

/**
 * 代理类:不一定只代理Zhangshan
 */
public class ByTicketProxy implements InvocationHandler {
    private Object target;

    public ByTicketProxy(Object target) {
        this.target = target;
    }
    
    /**
     * InvocationHandler只有一个invoke方法,它是所有被代理对象的所有方法的唯一实现。
     * 也就是说:无论调用被代理对象的任何方法,最终都是调用该invoke方法
     * proxy是代理对象
     * method参数是被调用的方法的反射对象
     * args参数是被调用时传入的参数数组
     * 返回值是当前被调用的方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("先查下票看还有没得...");
        // 反射调用target对象的method方法
        Object res = method.invoke(target, args);
        System.out.print("购买成功了...");
        return res;
    }
}

public class StartApp {
    public static void main(String[] args) {
        /*
         * 1、获取代理类 2、获取代理类的构造器 3、使用构造器创建一个实例 4、调用实例的方法
         */
        Class<?> calss = Proxy.getProxyClass(Person.class.getClassLoader(), Person.class);
        Constructor<?> constructor = calss.getConstructor(InvocationHandler.class);
        InvocationHandler handler = new ByTicketProxy(new Zhangshan());
        Person person = (Person) constructor.newInstance(handler);
        person.byTicket();// 调用目标对象方法时会触发事件处理器的方法,把当前目标方法作为参数传入
    }
}
参考代码

三、IOC设计原则

  IOC是控制反转的英文缩写,是面向对象编程中的一种设计原则,指调用者将创建对象的职责交给第三方组件,本身不直接创建对象,因而不需要直接依赖具体实现类,而是依赖实现类的接口,这种方式可以用来减低类和类之间的耦合度,最常用的实现该原则的方式是依赖注入(DI),还有一种方式叫依赖查找。

  依赖注入是指第三方组件创建类的实例(对象)后,通过调用者的构造函数、属性、方法等注入点将对象注入给调用者(通常调用者使用接口来接收对象)。通过构造函数来注入对象的方式称为构造函数注入,通过属性来注入对象的方式称为属性注入,通过方法参数来注入对象的方式称为方法注入。

  根据前面的介绍,我们可以得出控制反转最直观的感觉就是:调用者都是依赖了一大堆的接口,这些接口就是注入点,在运行时第三方组件将构建一个接口实例对象后赋值给这些接口。因此可以总结到控制反转的核心是依赖抽象而不依赖具体。

  我们把这种专门用于创建实例、管理实例的组件称为IOC容器,在Spring中,BeanFactory工厂类的作用就是创建Bean对象,因此它就是一个IOC容器的实际代表。

四、Spring框架之核心Resource与ResourceLoader

  Resource是抽象的资源描述接口,这些资源可能来自类路径、文件系统、HTTP请求、URL地址、Byte数组、输入流等场景,不同场景对应一个子接口,最终调用相应子接口的实现类即可通过该方式加载一个资源。该接口继承根接口InputStreamSource,接口方法如下:

  其子接口或类如下:

  • ContextResource接口:从封闭的"上下文环境"加载的资源的扩展接口。
  • WritableResource接口:支持写入资源的扩展接口。提供输出流访问器。
  • HttpResource接口:要写入HTTP响应的资源的扩展接口。
  • AbstractResource类:一个封装了常规实现的抽象类,它也有很多子类,用于不同的处理,子类有以下:
  1. DescriptiveResource:包含资源说明但不指向实际可读资源的简单资源实现。
  2. BeanDefinitionResource:表示一个BeanDefinition的资源。
  3. AbstractFileResolvingResource:将URL解析为文件引用的资源的抽象基类,例如 UrlResource 或 ClassPathResource。
  4. InputStreamResource:表示一个从输入流加载的资源。
  5. FileSystemResource:表示一个从文件系统获取到的资源。
  6. VfsResource:表示一个基于Boss VFS的资源实现。
  7. ByteArrayResource:表示一个从字节数组加载的资源。
  8. MultipartFileResource:表示一个从MultipartFile加载的资源。
  9. PathResource:表示一个从给定路径加载的资源。继承关系如下:

   ResourceLoader表示用于加载资源的策略接口(Spring使用了策略模式)。它用于从指定的路径(比如类路径、文件系统)加载资源信息,不同的资源通过不同的实现来进行加载。它有2个方法:

  • getResource:返回指定资源位置的资源句柄。句柄应始终是可重用的资源描述符。
  • getClassLoader:公开此资源加载器使用的类加载器。需要直接访问ClassLoader的客户端可以使用ResourceLoader以统一的方式执行此操作。

它有一个默认实现DefaultResourceLoader和子接口ResourcePatternResolver,继承关系如下:

五、Spring框架之核心BeanFactory

  容器是IOC理论的概念,它是一套IOC框架,用于统一管理对象的生命周期,负责实例化、定位、存储、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装。

  在Spring中BeanFactory是IOC容器的实际代表者,它负责通过读取配置文件中的配置元数据,利用反射机制来对各个对象进行实例化及装配。

  BeanFactory是Spring框架的一个顶层接口,它有很多直接实现或者间接实现类,提供了创建Bean、管理Bean、存储Bean、获取Bean的一系列方法:

它的直接子接口和实现类如下:

  • AutowireCapableBeanFactory接口:提供自动装配Bean的能力。

  • HierarchicalBeanFactory接口:提供层次划分功能,提供了父子容器。

  • ListableBeanFactory接口:提供枚举其所有Bean实例的能力。

  • SimpleJndiBeanFactory类:基于JNDI的简单实现。

  Spring提供了一个默认实现类实现ConfigurableListableBeanFactory 和 BeanDefinitionRegistry 接口的类DefaultListableBeanFactory:一个基于 bean 定义元数据的成熟 Bean 工厂,可通过后处理器进行扩展。

BeanFactory中几个重要的底层子接口或类:

  • FileSystemXmlApplicationContext:用于从文件系统路径的xml文件这加载被定义的Bean。
  • ClassPathXmlApplicationContext:用于从类路径的xml文件中加载被定义的Bean。
  • XmlWebApplicationContext:用于在web 应用程序范围内的xml文件中加载被定义的Bean。
  • ApplicationContext:该接口既是容器,也称为Spring的上下文,功能比BeanFactory更多。
  • AnnotationConfigApplicationContext:用于从配置类(被@Configuration注解的类)来加载被定义的Bean。

   BeanFactory接口的继承和实现关系图如下:

  六、Spring框架之核心BeanDefinitionRegistry(Bean定义注册中心)

  BeanDefinitionRegistry接口提供了注册、移除、获取BeanDefinition的功能,实现类有DefaultListableBeanFactory和GenericApplicationContext。接口方法如下:

  BeanDefinitionRegistry接口的继承和实现关系图如下:

 七、Spring框架之核心BeanDefinition(Bean定义)

  AttributeAccessor接口表示属性的访问器,用于将元数据附加到任意对象,或从任意对象访问元数据。BeanMetadataElement接口提供了一个getResource()方法,用来传输一个可配置的源对象(配置元数据信息)。

  BeanDefinition接口继承了以上两个根接口,用于定义一个Bean,描述了Bean的类信息、属性、行为特征、依赖、配置元数据等。配置元数据是初始化Bean必须的信息数据,SpringIOC可以从xml文件、注解参数、Java配置环境等方式获取Bean元数据。

  配置元数据的属性如下:

  • class:指定用来创建Bean的Bean类。
  • name:指定唯一的Bean标识符。在基于XML的配置元数据中,你可以使用id或name属性来指定Bean标识符。
  • scope:指定作用域。
  • constructor-arg:用于注入依赖参数。
  • property:用于注入依赖属性。
  • autowire:指定自动装配的方式是按类型还是按名称。
  • lazy-init:延迟初始化的Bean,告诉IOC容器在它第一次被请求时,而不是在启动时去创建一个Bean实例。
  • init方法:在Bean的所有必需的属性被容器设置之后回调方法。
  • destory方法:当包含该Bean的容器被销毁时回调方法。

  AbstractBeanDefinition表示一个抽象的Bean定义,它是具体Bean定义的基类。GenericBeanDefinition表示一个通用的Bean定义,AnnotatedGenericBeanDefinition类表示了一个通过注解生成的Bean定义,ScannedGenericBeanDefinition表示一个基于类路径扫描生成的Bean定义。

  BeanDefinition接口继承和实现关系如图:

八、Spring框架之核心ApplicationEventPublisher、ApplicationEvent、ApplicationListener 、ApplicationEventMulticaster

  要理解这四个接口,我们先谈谈事件驱动模型、观察者模式、发布订阅模式之间的区别和联系。

  事件驱动模型的三要素包括事件源、事件、事件处消费者。事件驱动设计模型主要用于降低系统耦合度,提高系统的可扩展性。

  • 事件源(Event Source):指发生事件的对象,它负责产生事件并向事件处理器传递事件对象。
  • 事件(Event Object):用于封装事件源发生的事件信息,以便事件处理器进行处理。
  • 事件消费者(Event Handler):用于处理特定类型的事件。当事件源产生事件时,它会将事件对象传递给相应的事件处理器进行处理。我们常说的事件处理器(Handler),事件监听器(Listener)都属于消费者。
  • 事件管理器:用于管理、派发事件,是事件和事件处理器的桥梁,一般来说可以在事件源中实现。

  观察者模式是一种经典的事件驱动模型。观察者模式定义了一对多的依赖关系,让一个或者多个观察者对象监视一个被观察对象,如果这个被观察对象发生了状态的变化,则会通知所有的注册观察者执行相应的状态处理。

观察者模式有观察者和目标两种角色,被观察者内部会维护观察的对象集合。UML类图如下:

  在jdk中的java.util包中也封装了观察者模式的Observable类和Observer接口,可通过实现其接口和继承的方式来完成我们的观察者模式。以下是观察者模式的示例代码:

public class ITest {

    public static void main(String[] args) {

        //被观察者
        MySubject subject = new MySubject();

        //注册观察者==添加监听器
        subject.registerObserver(new MyObserver("zhangshan"));
        subject.registerObserver(new MyObserver("lisi"));
        subject.registerObserver(new MyObserver("wangwu"));
        subject.registerObserver(new MyObserver("zhaoliu"));

        //被观察者执行操作==触发事件
        subject.setMessage("hi, happy new year!");
    }
}


/**
 * 观察者接口:观察者将提供一个用于被观察者调用的方法
 */
public interface IObserver {

    /**
     * 观察者方法
     * @param message  被观察者传递的对象 == 事件对象
     */
    public void invoke(Object message);
}

/**
 * 被观察者接口 : 提供注册观察者的方法,提供通知观察者的方法
 */
public interface ISubject {
    /**
     * 注册观察者:事件订阅
     *
     * @param observer
     */
    public void registerObserver(IObserver observer);

    /**
     * 移除观察者:取消订阅
     *
     * @param observer
     */
    public void removeObserver(IObserver observer);

    /**
     * 通知所有观察者:事件触发
     */
    public void sendToObservers();
}


/**
 * 具体观察者
 */
public class MyObserver implements IObserver {

    private String name;

    public MyObserver(String name) {
        this.name = name;
    }

    @Override
    public void invoke(Object message) {
        System.out.println(this.name + " receiver message:" + message);
    }
}


/**
 * 具体被观察者
 */
public class MySubject implements  ISubject {

    /**
     * 观察者集合
     */
    private List<IObserver> observers = new ArrayList();

    private Object message;

    /**
     * 触发通知==事件触发
     * @param message
     */
    public void setMessage(Object message) {
        this.message = message;
        sendToObservers();
    }

    @Override
    public void registerObserver(IObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(IObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void sendToObservers() {
        for (IObserver item : observers) {
            item.invoke(this.message);
        }
    }
}
简单的观察者模式代码

  发布订阅模式也是一种事件驱动模型,可以理解为它是观察者模式的增强,在23种设计模式中也隶属于观察者模式,它通过事件通道来解耦,避免了订阅者和发布者之间的直接依赖,它有三个角色发布者、调度中心、订阅者。

  • 发布者Publisher:发布事件到调度中心。
  • 调度中心(通道):统一调度触发订阅者注册到调度中心的事件。
  • 订阅者Subscriber:把自己想要订阅的事件注册到调度中心。

  与观察者模式一样,在jdk包中也封装了基于事件驱动模型的EventListener接口和EventObject类,可以通过实现其接口来完成我们自己的事件驱动编程。除此之外Spring也提供了对事件驱动编程的支持。

  理解了事件驱动模型,我们再来看Spring的以下四个接口:

  • ApplicationEventPublisher接口:定义了事件发布功能的相关方法,用作ApplicationContext的祖级接口。因此ApplicationContext也是一个事件发布器。
  • ApplicationEvent接口:表示一个容器级别的事件,是所有应用程序事件的基类,实现有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStopedEvent、PayloadApplicationEvent等。

  • ApplicationListener接口:表示一个容器级别的事件监听器,它是一个泛型接口,实现类有ScheduledAnnotationBeanPostProcessor、ResourceUrlProvider、GenericApplicationListenerAdapter、ApplicationListenerMethodAdapter等。

  • ApplicationEventMulticaster接口:应用程序事件多播接口,它是Spring框架事件驱动模型的调度中心角色,实现该接口的对象可以管理多个ApplicationListener,并将Events对象发布给ApplicationListener。该接口可以通过注入执行器实现监听器的异步调用。Spring的默认实现有AbstractApplicationEventMulticaster、SimpleApplicationEventMulticaster。
     1、SimpleApplicationEventMulticaster:应用程序事件组播器接口的简单实现。将所有事件多播到所有已注册的侦听器,让侦听器忽略它们不感兴趣的事件。侦听器通常会对传入的事件对象执行相应的检查实例。默认情况下,将调用线程中调用所有侦听器。指定备用任务执行器,以使侦听器在不同的线程中执行。

     2、ApplicationEventMulticaster:ApplicationEventMulticaster接口的抽象实现,提供基本的侦听器注册工具。默认情况下不允许同一侦听器的多个实例,因为它将侦听器保留在链接的 Set 中。用于保存 ApplicationListener 对象的集合类可以通过 “collectionClass” bean 属性进行重写。

九、Spring框架之核心BeanPostProcessor

  BeanPostProcessor是一个接口,称为Bean的后置处理器,起到扩展处理Bean的作用。可以通过实现该接口的类来对Bean进行初始化前和初始化后的自定义处理,在每一个Bean的初始化前后都会调用实现类的如下方法。接口提供如下方法:

  • postProcessBeforeInitialization:参数为Bean对象和Bean名称,在Bean生命周期的init-method方法之前执行,返回一个原始对象或者被自定义处理的对象。
  • postProcessAfterInitialization:参数为Bean对象和Bean名称,在Bean生命周期的init-method方法之后执行,返回一个原始对象或者被自定义处理的对象。
/**
 * bean的后置处理器类
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * *
     * 该重写方法将在生命周期的init-method方法之前执行
     *
     * @param bean     bean对象
     * @param beanName bean名称
     * @return bean对象  这个对象可以是原始对象也可以是被修改后的bean对象
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor后置处理器在init-method之前执行了...");
        return bean;
    }

    /**
     * *
     * 该重写方法将在生命周期的init-method方法之后执行
     *
     * @param bean     bean对象
     * @param beanName bean名称
     * @return bean对象 这个对象可以是原始对象也可以是被修改后的bean对象
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor后置处理器在init-method之后执行了...");
        return bean;
    }
}
参考代码

   Spring内置Bean后置处理器的继承关系如图,具体实现类的作用参考其接口的方法:

 十、Spring框架之核心BeanFactoryPostProcessor

   BeanFactoryPostProcessor接口称为Bean工厂后置处理器,用于在加载BeanDefinition信息以后,对BeanDefinition信息进行自定义处理。它与BeanPostProcessor的区别是一个处理Bean,一个处理BeanDefinition。

   BeanFactoryPostProcessor接口有一个方法postProcessBeanFactory,参数为beanFactory,可在实现类的该方法中通过该参数获取BeanDefinition信息,并对其进行加工处理。

十一、Spring中Bean的创建过程

   通过前面的学习,我们已经知道ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext这几个底层的实现类它们同时有三个身份Bean工厂、事件发布器、资源加载器,而且实现了生命周期接口。我们以ClassPathXmlApplicationContext类进行Bean创建,Bean对象是如何一步一步被创建的。首先我们创建一个IOC容器。代码如下:

 /**
     * 从xml配置文件中读取信息,来管理和实例化bean对象
     */
    public static void newBeanByconfigxml() {
        //ApplicationContex接口有多个实现,此处通过ClassPathXmlApplicationContext对象来加载配置文件
        //实例化Spring容器,把容器中管理的bean实例创建并存储到Map集合中
        //注意:创建对象时会调用默认的无参构造函数,如果在对象中定义了有参构造函数,则默认的无参构造函数不会自定生成,需要手动创建一个默认的无参构造函数
        //在非web应用程序中初始化IOC是直接在main方法实例化如下对象,在web网站中应该在web应用程序被服务器加载时就实例化IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context_config.xml");

        //打印出map集合中被创建的bean,先获取所有bean的名称
        String[] beans = ctx.getBeanDefinitionNames();
        for (String bean : beans) {
            System.out.println(bean + " of Type :: " + ctx.getBean(bean).getClass());
        }
}
参考代码

 调试代码得出如下结论:

  1、在ClassPathXmlApplicationContext的构造函数中存在如下代码:首先会设置此应用程序上下文的配置文件位置,其次调用父类AbstractApplicationContext的refresh()方法。

   2、父类AbstractApplicationContext的refresh()方法依次调用如下方法:

  • 调用prepareRefresh:初始化上下文环境中的任何占位符属性、验证必填环境变量是否可以解析、存储预刷新ApplicationListeners。
  • 调用prepareBeanFactory:准备Bean工厂以便在此上下文中使用。主要包含准备类加载器、表达式解析器、配置文件处理器、Bean后置处理器,注册一些特殊Bean和系统级别Bean。
  • 调用postProcessBeanFactory:对Bean工厂进行一些后处理。
  • 调用invokeBeanFactoryPostProcessors:调用所有注册的BeanFactoryPostProcessor,该方法将根据@Bean/@Import注解的类和方法、@Configuration指定的配置类、@ComponentScan注解配置的扫描路径,使用Bean扫描器扫描所有BeanDefinition,存入BeanDefinitionMap集合中。
  • 调用registerBeanPostProcessors:注册拦截Bean创建的Bean后置处理器,这些后置处理器在Bean的初始化前后会执行对应的逻辑。
  • 调用initMessageSource:初始化此上下文的消息源。
  •  调用initApplicationEventMulticaster:初始化此上下文的事件多播。
  • 调用onRefresh:初始化特定上下文子类中的其他特殊Bean,SERVLET类型的应用在此方法中会调用子类ServletWebServerApplicationContext.onRefresh方法进行Web服务器的创建(一般是tomcat服务器)。
  • 调用registerListeners:检查并注册监听器到initApplicationEventMulticaster中。
  • 调用finishBeanFactoryInitialization:实例化所有剩余的(non-lazy-init)单例Bean,包含创建对象、初始化属性、注册销毁等过程,最终生成Bean对象并放入单例缓存池中。
  • 调用finishRefresh:清理资源缓存、初始化此上下文的生命周期处理器并调用其onRefresh方法、发布上下文刷新完成事件。
  • 调用resetCommonCaches:清理Spring核心中的常见不再需要的缓存资源。

  前面提到IOC容器也实现了事件发布器的接口,我们可以在实例化后给其添加ApplicationEvent事件,

  当一个 Bean 被加载到 Spring 容器时,它就具有了生命,而 Spring 容器在保证一个 Bean 能够使用之前,会进行很多工作。了解Bean生命周期及其回调钩子和容器在实例化的过程中发布的事件,有利于我们在创建Bean的过程中注入我们自己的逻辑。

  总体来说,Bean的生命周期分为三大块,即生产阶段、使用阶段、销毁阶段。

  • 生产阶段步骤:1、准备IOC容器、环境配置。2、加载Bean的定义(BeanDefinition)。3、实例化Bean。4、初始化Bean的属性。5、添加Bean到缓存池。
  • 使用阶段步骤:1、根据类型、名称或者Id查找Bean。2、调用Bean方法或者属性。
  • 销毁阶段步骤:1、调用销毁前置处理器。2、调用销毁时执行方法。

Spring容器中Bean的生命周期流程如图所示。

十二、普通Bean和工厂Bean

  Spring中Bean的类型有两种,一种是普通Bean,一种是工厂Bean。比如前面context_config.xml中的id=zhangshan的Bean就是一个普通bean,它指向的class就是Bean类型本身。另一个id=zhangwuji的Bean则是一个工厂Bean,它指向的class是一个可以实例化普通Bean的工厂,该工厂需要实现FactoryBean接口,并重写其方法使其返回普通Bean对象。

十三、Bean的作用域

  Bean的作用域用于约束Bean在Spring框架中的存在形式,在xml方式的Bean定义中使用属性scope来指定,可选项有:

  • singleton:在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值。
  • prototype:每次从容器中调用Bean时,都返回一个新的实例。
  • request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境。
  • session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境。
  • global-session:一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境

十四、Spring的注解说明

  • @Component:作用于类上,标识该类是一个会被Spring容器进行管理的组件,会被Spring包扫描工具进行扫描并实例化后存到IOC容器中。它有一个默认属性value标识组件Bean的id/name,如果不指定值则Spring将该类的类名首字母小写后作为其值。比如UserDaoImpl类的默认组件name为userDaoImpl。
  • @Respository:标识类是一个持久层组件,作用和使用方式与@Component相同。
  • @Service:标识类是一个服务层(业务层)组件,作用和使用方式与@Component相同。
  • @Controller:标识类是一个表现层组件,作用和使用方式与@Component相同。
  • @Configuration:标识一个类为配置类,交给Spring容器管理,通常结合方法级注解@Bean来使用。配置类的作用是用于代替beans.xml对bean的元数据进行描述,作为Bean定义的源。被@Configuration标记的类在实例化时会被拦截器生成的代理类替换(proxyBeanMethods默认为true),这个代理类会优先从容器单例池中获取Bean进行注入,因此可以保证配置类内部依赖对象为单例。该注解有两个属性value、proxyBeanMethods,value用于指定生成的Bean的名称,proxyBeanMethods用于指定配置类是否应该被代理类代理,前面提到代理类可以保证内部依赖对象为单例,如果设置为false,则每次调用@Bean标注的方法都会执行方法体返回全新对象。
  • @Bean:作用于方法上,标识该方法是一个method Bean,告知Spring该方法会返回一个对象,这个对象需要作为Bean组件保存到IOC组件集合中,默认方法名称作为Bean的id/name,以单实例存在。该注解还可以作用于自定义注解上形成组合注解。 该注解有如下属性:
  1. value:生成的Bean对象的名称。
  2. name:生成的Bean对象的名称。
  3. autowire:已弃用,表示在遇到对该对象的依赖时是否自动注入。
  4. autowireCandidate:表示是否作为自动注入Bean的候选者,默认为true,如果为false则该Bean不能被自动注入给其他Bean。
  5. initMethod:Bean在初始化期间要在Bean实例上调用的方法。
  6. destroyMethod:Bean在销毁期间要在Bean实例上调用的方法。
  • @Import:作用于类上,Spring会将指定类型的组件实例化后保存到容器中,默认组件名字是导入类的全类名。也可以指定ImportSelector实现类,它会以实现类selectImports返回的Class名称实例化Bean存放到容器中。还可以通过指定ImportBeanDefinitionRegistrar实现类,调用实线类的自定义代码完成Bean的注册。
  • @Conditional:按条件装配Bean的根注解,它有很多个派生注解表示不同的装配场景,只有满足条件时才装配Bean,注解条件说明如下。
  1. @ConditionalOnExpression:当表达式为true时。
  2. @ConditionalOnNotWebApplication:当项目不是web应用时。
  3. @ConditionalOnWebApplication:当项目是Web项目时。
  4. @ConditionalOnBean:当容器中有指定Bean时。
  5. @ConditionalOnMissingBean:当容器里没有指定Bean时。
  6. @ConditionalOnClass:当类路径下有指定类时。
  7. @ConditionalOnMissingClass:当类路径下没有指定类时。
  8. @ConditionalOnProperty:当指定的属性有指定的值时。
  9. @ConditionalOnJava:当JVM版本为指定的版本范围时。
  10. @ConditionalOnResource:当类路径下有指定的资源时。
  11. @ConditionalOnJndi:当JNDI存在时。
  12. @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个或者有多个但是指定了首选的Bean时。
  13. @ConditionalOnRepositoryType:当特定类型的spring Data JPA启用时。
  14. @ConditionalOnWarDeployment:当程序部署为传统的war程序时。
  15. @ConditionalOnCloudPlatform:当指定的云平台处于活动状态时。
  16. @ConditionalOnEnabledResourceChain:当启用了 Spring 资源处理链时。
  • @ImportResource:将类路径下的classpath:beans.xml资源文件中定义的Bean导入到容器中。
  • @PropertyResource:作用于类上,用于将指定的*.properties/*.yml配置文件中定义的键值绑定到类的属性上。它与@ConfigurationProperties的区别是前者查找的是指定配置文件,而后者查找的是全局配置文件。
  • @ConfigurationProperties:作用于类上,用于将application.properties/application.yml全局配置文件中的定义的键值绑定到类的属性上。它有一个属性prefix用于指定要绑定键值的前缀。通常被ConfigurationProperties注解的类也需要增加@Component注解来交由容器管理,不然就会失去使用意义。
  • @EnableConfigurationProperties:在类上引入另一个目标类,表示启用目标类的属性绑定功能,并将目标类注册到容器中。相当于@Component+@ConfigurationProperties同时作用到目标类。
  • @ComponentScan:作用于类上,用于配置组件扫描路径,配置后Spring将根据指定规则进行组件注册,它有如下属性:
  1. value:指定包扫描路径,如果不指定,则默认为此配类所在的包。
  2. basePackages:指定包扫描路径,如果不指定,则默认为此配类所在的包。
  3. basePackagesClasses:指定具体扫描的类。
  4. nameGenerator:配置beanName生成器。
  5. scopeResolver:注册自定义的Scope解析类。
  6. scopedProxy:是否为检测到的组件生成代理。
  7. resourcePattern:配置要扫描的资源的正则表达式。
  8. useDefaultFilters:是否使用默认过滤器。默认为true,表示扫描@Component/Respository/Service/Controller等注解的类。
  9. includeFilters:配置要包含的扫描的过滤器,Spring将扫面满足配置条件的类型。
  10. excludeFilters:配置要排除的扫描的过滤器,Spring将不会扫面满足配置条件的类型。
  11. lazyInit:是否开启懒加载,默认不开启。
  • @Value:可作用于方法、属性、方法参数、注解上,用于绑定环境变量、配置属性对应的值。${user.pwd}格式用于从环境变量、配置属性中取值;#{beanName}格式用于从容器中注入一个Bean对象;也可以直接输入字符串或者表达式。
  • @Scope:用于配置Bean的作用范围,它与@Bean或者@Component等注解配合使用。其属性scopeName用于指定作用范围,proxyMode用于指定是否应将组件配置为作用域代理。
  • @Autowired:可作用于构造方法、普通方法、属性、属性方法、方法参数、注解上,Spring容器会为添加了这个注解的目标进行自动注入。它有一个属性required用于指示作用的目标是否不能为空,默认为true,如果为false,则没有注入成功时会报错。其次还有@Resource、@Inject注解,与@Autowird使用方式差不多,但推荐使用@Autowird。
  • @Lazy:可作用于构造方法、普通方法、属性、属性方法、方法参数上,表示指定组件是否应进行延迟初始化(需要时才初始化该组件)。
  • @Lookup:作用于方法上(官方称为方法注入),它将根据指定Bean名称或者方法返回值类型返回一个Bean对象,也就是说目标方法被拦截了,调用目标方法时最终返回的不是方法体执行的结果。
  • @Primary:用于指定一个Bean为主Bean,当通过类型找到多个Bean的时候,指定了该注解的Bean会被选择。

十五、如何使用Spring创建Bean

  Spring创建Bean和初始化Bean的实现方式有基于xml配置Bean元数据的方式和基于组件扫描的方式。

  • 基于xml配置Bean元数据的方式创建Bean的步骤:

  1.创建xml配置文件:在src/resources目录下创建context_config.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">
  <!--  该文件为Spring配置文件, 通过以下节点配置Bean,容器工厂将通过这些配置来动态生成Bean对象-->

    <!-- 一个最简单的bean配置,该配置将通过默认无参构造函数来创建实例,value指定的值为实例属性的默认初始值,spring是通过属性的setter方法来注入值的【属性注入】-->
   <bean id="zhangshan"  class="com.zqh.entity.User">
       <property name="id" value="1"/>
       <property name="userName" value="zhangshan"/>
       <property name="realName" value="张山"/>
       <property name="deptId" value="1"/>
       <property name="postId" value="1"/>
       <property name="phone" value="15000000000"/>
       <property name="remark" value="我是通过简单的属性注入的"/>
       <!-- 赋值null专用写法-->
       <!-- <constructor-arg type="java.lang.Integer" > <null/></constructor-arg> -->
   </bean>

    <!--  带参数的构造方式: constructor-args属性用于配置参数的value,可通过 index、name、type属性来指定,spring是通过传入参数值来注入的【构造函数注入】
     1、如果使用type指定,当参数类型一样时将按照顺序依次赋值,不一样时将按照type对应赋值
     2、如果使用name指定,则按照name名称对应赋值
     3、如果使用index执行,则按照index下标对应赋值
     4、可以使用混合方式指定,但不推荐
     -->
    <bean  name="lisi" class="com.zqh.entity.User">
        <constructor-arg name="userName"  value="lisi"/>
        <constructor-arg name="realName"  value="李四"/>
    </bean>
    <bean  name="wangwu" class="com.zqh.entity.User">
        <constructor-arg index="0"  value="wangwu"/>
        <constructor-arg index="1"  value="王五"/>
    </bean>
    <bean  name="zhaoliu" class="com.zqh.entity.User">
        <constructor-arg type="java.lang.Integer" value="4"/>
        <constructor-arg type="java.lang.String" value="zhaoliu"/>
        <constructor-arg type="java.lang.String" value="赵六"/>
    </bean>

    <!-- property的参数: name表示属性名称,value表示属性值,ref表示引用另一个bean实例
      property使用ref注入对象时:  name表示类中带有setter方法的属性, ref表示引用spring容器中key=wanglaoshi的bean实例
    -->
    <bean  name="xiaomin" class="com.zqh.entity.User">
        <property name="deptDes" ref="dept"/>
        <property name="userName" value="xiaomin"/>
        <property name="realName" value="小明"/>
    </bean>
    <bean  name="dept" class="com.zqh.entity.Dept">
        <property name="deptName" value="研发四部"/>
        <property name="id" value="4"/>
        <property name="parentId" value="1"/>
    </bean>

    <!-- 自动装配 autowire:根据bean的类型或名称进行装配,自动装配会装配bean的所有属性.如果未装配成功则为null。
      1、byName:根据 bean的名字和当前bean的 setter风格的属性名进行自动装配,若有匹配的,则进行自动装配,若没有匹配的,则不装配。【必须将目标Bean 的名称和属性名设置的完全相同。】
      2、byType:通过当前bean的属性的类型来自动查找相应的类型并装配。【若IOC容器中有多个与目标Bean类型一致的Bean.在这种情况下,Spring将无法判定哪个Bean最合适该属性,所以不能执行自动装配】
    -->
    <bean name="damin" class="com.zqh.entity.User" autowire="byType"/> <!-- 该对象中属性deptDes的类型Dept存在对应的bean,被装配 -->
    <bean name="dashuai" class="com.zqh.entity.User" autowire="byName"/> <!-- 该对象中属性deptDes的名称没有对应的bean,不被装配 -->

    <!--  bean的继承:使用子bean的parent属性可以指定要继承的父bean:
      1、Spring允许继承bean的配置,被继承的bean称为父bean,继承这个父Bean 的 Bean称为子Bean。
      2、子Bean从父Bean中继承配置,包括Bean的属性配置。
      3、子Bean也可以覆盖从父Bean继承过来的配置。
      4、父Bean可以作为配置模板,也可以作为Bean实例,若只想把父Bean作为模板,可以设置<bean>的abstract属性为true,这样Spring将不会实例化这个Bean。
      5、并不是<bean>元素里的所有属性都会被继承,比如: autowire,abstract等。
      6、可以忽略父Bean的class属性,让子Bean指定自己的类,而共享相同的属性配置,但此时abstrac必须设为true。
    -->
    <bean name="zhangshan1" class="com.zqh.entity.User" parent="zhangshan" /> <!--  继承了名为zhanshan的父bean的配置-->

    <!--  bean的作用域:通过bean的scope属性可以指定bean的作用域:
      1、prototype:每次需要bean时,容器都会产生一个新的bean。
      2、request:用于域对象的request(不常用)。
      3、session:用于域对象的session(不常用)。
      4、singleton:默认值,单例模式-容器初始化时创建的bean实例,将应用在整个生命周期中。
    -->
    <bean name="zhangshan2" class="com.zqh.entity.User" autowire="byName" scope="prototype"/>

    <!-- 使用spring表达式语言 spel 为bean进行动态赋值 :使用 #{}作为界定符号,除了基本el运算,还可以引用bean、属性和方法等 -->
    <bean  name="zhangsanfeng" class="com.zqh.entity.User">
        <property name="deptDes" ref="dept"/>
        <property name="userName" value='#{ "zhangshanfeng" }'/> <!--赋值一个字符串 -->
        <property name="postId" value='#{108}'/><!--赋值一个数字 -->
    </bean>

    <!-- SpringIOC容器可以管理bean的生命周期,Spring在bean生命周期的特定点执行方法:
      IOD管理bean的过程:
      1、调用构造器或者工厂方法创建bean实例
      2、设置bean的属性默认值和其他bean的引用
      3、调用bean的初始化方法:init-method
      4、bean的使用
      5、关闭容器时销毁bean,同时会调用destroy-method方法
     -->
    <bean name="zhangjunbao" class="com.zqh.entity.User" init-method="init" destroy-method="close"><!--指定生命周期方法 -->
        <property name="deptDes" ref="dept"/>
        <property name="userName" value="zhangjunbao"/>
        <property name="realName" value="张君宝"/>
    </bean>

   <!--   bean的后置处理器:后置处理器用于在bean的生命周期特定阶段对bean执行特定的操作,声明一个后置处理器的方式是实现BeanPostProcessor接口,并具体提供如下方法的实现:
            1、  Object postProcessBeforeInitialization(Object bean, String beanName )
            2、  Object postProcessAfterInitialization(Object bean, String beanName)
         第一个方法将在init-method之前被调用,第二个方法将在init-method之后被调用,
    -->
    <!--配置bean的后置处理器:不需要配置id,IOC 容器自动识别是一个BeanPostProcessor后置处理器 -->
    <bean class="com.zqh.MyBeanPostProcessor"> </bean>

    <!--  通过静态工厂方法来配置bean:
        class属性:指向静态工厂方法的全类名
        factory-method:指向静态工厂方法的名字,如果方法需要参数,则通过constructor-arg注入
    -->
    <bean id="lijiachen" class="com.zqh.UserStaticBeanFactory" factory-method="getUser">
        <constructor-arg name="name"  value="lijiachen"/>
    </bean>

    <!--  通过实例工厂方法来配置bean:首先要配置实例工厂bean,其次通过factory-bean指定实例工厂bean,factory-method指定实例工厂的方法-->
          <!--配置工厂bean实例 -->
    <bean id="factory" class="com.zqh.UserInsBeanFactory" ></bean>

    <!--实例方法配置bean,需指定实例工厂bean和对应的方法
        factory-bean:指向实例工厂的bean
        factory-method:指向实例工厂方法的名字,如果方法需要参数,则通过constructor-arg注入
        -->
    <bean id="liyifeng" factory-bean="factory" factory-method="getUser">
        <constructor-arg name="name"  value="liyifeng"/>
    </bean>

    <!--  通过Spring自带的FactoryBean来配置bean: 只需要通过class指定FactoryBean实例类即可-->
    <bean id="zhangwuji" class="com.zqh.UserFactoryBean" ></bean>

</beans>
参考代码

  2.实例化IOC容器:即实例化一个Bean工厂,前面提到BeanFactory的实现类中包含了一个ClassPathXmlApplicationContext的工厂类,该类的作用就是用于从指定的xml配置文件中创建Bean。

 /**
  1、在非web应用程序中初始化IOC是直接在main方法实例化如下对象,在web网站中应该在web应用程序被服务器加载时就实例化IOC容器。
  2、ApplicationContex接口有多个实现,此处通过ClassPathXmlApplicationContext对象来创建Bean。
  3、创建对象时会调用默认的无参构造函数,如果在对象中定义了有参构造函数,则默认的无参构造函数不会自定生成,需要手动创建一个默认的无参构造函数。
  */
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context_config.xml");

  //打印出map集合中被创建的bean信息
        String[] beans = ctx.getBeanDefinitionNames();
        for (String bean : beans) {
            System.out.println(bean + " of Type :: " + ctx.getBean(bean).getClass());
        }

   //获取bean对象
  User zhangshan = (User) ctx.getBean("zhangshan");
参考代码
  • 基于组件扫描的方式创建Bean的步骤:

  1.创建xml配置文件:与前者不同的是配置文件内容不包含Bean的元数据信息,而是配置了包的扫描信息,内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<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-4.0.xsd">

    <!--导入属性文件,导入该配置需要引入context命名空间,引入方式是配置:
      xmlns:context = "http://www.springframework.org/schema/context"
       xsi:schemaLocation = http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
     -->

  <!-- 指定Spring IOC容器扫描的包,这些包下的被@Component、@Controller、@Service、@Repository注解了的bean会被IOC自动扫描并配置:
      base-package:指定被扫描的包,多个包时使用英文逗号分割
      resource-pattern:正则匹配,只扫面在base-package指定的包下被匹配的bean文件
      use-default-filters: 默认值true,指示是否使用默认的过滤规则,如果为false,则使用include-filter、exclude-filter指定的filter
   -->
    <context:component-scan base-package="com.zqh"  use-default-filters="false">

        <!--  指示要被包含的目标类型,component-scan下可以拥有多个该子节点:expression表示过滤器表达式,type属性表示过滤器表达式的类型,有如下几种:
          1、annotation:目标类是否标注了某个注解来进行过滤
          2、assignable:目标类是否继承或扩展了某个特定类来进行过滤
          3、aspectj:目标类是否满足指定命名规则或扩展自该命名规则的类来进行过滤
          4、regex:目标类的包名是否满足指定正则规则来进行过滤
          5、custom:通过指定的filter过滤器来进行过滤,该过滤器必须实现 org.springframework.core.type.filter.TypeFilter 接口
        -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>

        <!--  指示要排除的目标类,component-scan下可以拥有多个该子节点:属性与include-filter属性一致 -->
        <!--  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> -->
</context:component-scan>




</beans>
参考代码

   2.给需要被IOC管理的类添加注解@Component、@Respository、@Service、@Controller等。

  3.实例化IOC容器:与前者的方式相同。

 十六、Spring框架总结

  1、Bean的元数据信息有哪些?

  答:id、name、class、scope、constructor-arg、parent、depends-on、property、autowire、lazy-init、init、destory。

  2、注册一个Bean的方式有哪些?

  答:使用xml配置Bean、@ComponentScan+@Component包扫描、@Import导入、@Configuration+@bean配置类、@FactoryBean工厂Bean、BeanFactoryPostProcessor。

  3、如何使用已注册的Bean?

  答:使用@Autowired自动注入Bean到构造方法、普通方法、属性、属性方法、方法参数、注解上。

  4、Spring容器在实例化的过程中发布了哪些事件?

答:ApplicationStartingEvent、
  ApplicationEnvironmentPreparedEvent、
  ApplicationContextInitializedEvent、
  ApplicationPreparedEvent、
  ApplicationStartedEvent、
  AvailabilityChangeEvent、
  ApplicationReadyEvent、
  AvailabilityChangeEvent、
  ApplicationFailedEvent。

  5、Bean的生命周期过程中有哪些可以供我们回调的方法?

  答:初始化后调用(@PostConstruct注解标注的方法、init-method方法、InitializingBean接口方法)、销毁前调用(@PreDestroy注解标注的方法、destroy-method方法、DisposableBean接口方法)。

  Spring允许混合使用上述三种方式来指定对应的回调方法。当同一个bean使用三种方式指定了同一个方法作为初始化后的回调方法或销毁前的回调方法时,对应的回调方法只会被执行一次。当使用两种或三种方式指定的回调方法不是同一个方法时,Spring将依次调用这些指定的回调方法。