Spring学习总结

来源:互联网 发布:爱卓金蝶erp软件 编辑:程序博客网 时间:2024/05/16 14:46

标签(空格分隔): Java Spring


1. 背景

  • Spring印象深刻
    之前一直用c++进行分布式存储系统和分布式计算系统的架构和开发,在负责云平台时,主要采用Java进行开发,包括对云存储进行升级迭代,对接腾讯云和网宿科技CDN提供一站式的图片存储、处理和CDN服务,也参与过基于docker+kubenetes进行二次开发的业务编排系统,为容器化应用提供资源调度、集群管理、持续集成、服务发现等功能,开始接触基于Spring的Web开发。当时利用Spring+MyBatis+Shiro实现用户权限管理,包括用户权限管理、组管理、基于权限的项目/集群/部署管理,Spring可以帮助快速开发,尤其是和MyBatis的结合大大简化了开发过程,不需过多关注和数据库的交互,有更多的时间花在业务逻辑的开发中。

  • 路漫漫其修远兮
    目前从事web后台研发,服务差不多都是基于Spring框架开发的,虽然Spring上手很容易,但还是有必要系统性的学习一下,不能简单的停留在使用层面,要从原理上深入学习,尤其是学习理解其设计思路。

2. Spring基本知识

srping图标
Spring是一个轻量级的开源Java开发框架,由于其良好的设计和分层架构,使得Spring很容易和其他开源框架或是应用服务器相结合,尤其是近些年Spring框架在企业级应用开发中占据了绝对主导地位。
Spring框架的核心功能包括:控制反转(IoC,或者说依赖注入DI)、AOP、事务管理、数据访问、事件消息机制、web应用开发等。下面将对这些功能进行详细介绍。

2.1 控制反转IoC

  • 控制反转(IoC,是Inversion of Control的缩写)是面向对象编程中的一个重要的法则,目的是促进低耦合。IoC是Spring框架的核心,Spring框架的奠基人Martin Fowler给其取了个易于理解的名字—依赖注入(DI,Dependency Injection)。

一个实用的应用程序是由一系列对象互相组合、共同作用来完成复杂的业务逻辑,也就是说很多对象并不是孤立的,它需要引用或者说依赖其他对象来完成某些功能。一个对象在创建时,它所依赖的对象需要传递给它,即需要将依赖注入到这个对象中。控制反转指的是依赖注入的过程被反转了,对象依赖的其他对象不是由这个对象在创建时自己主动去创建或是查找,而是在需要的时候由外界主动将对象传递给它。在Spring中这个过程是由IoC容器来完成的,后文在介绍对象的配置和生命周期管理时将详细介绍。

控制反转的一个核心点就是实现需要依赖抽象,以便解耦。IoC和设计模式中的工厂模式比较类似。比如下面的例子,我们有一个加密管理类,这个类负责对数据进行加密,由于实现依赖具体的加密接口,一旦需要更换新的加密算法,就需要修改代码。为了支持不同的加密算法,比如md5,sha1等,一个更好的方式就是采用工厂模式。
例1:

class EncryptManager {    EncryptInterface a;    public EncryptManager() {        a = new EncryptInterfaceImpl();    }    // 对数据进行加密    public String encrypt(String raw) {        a.encrypt(raw);    }}

例2:

// 加密管理类class EncryptManager {    EncryptInterface a;    public EncryptManager(String option) {        a = EncryptFactory.create(option);    }    // 对数据进行加密    public String encrypt(String raw) {        a.encrypt(raw);    }}// 工厂方法,生成具体加密实现类class EncryptFactory {    public static EncryptInterface create(String option) {        if ("md5".equals(option)) {            return new MD5EncryptImpl();        } else if ("sha1".equals(option)) {            return new SHA1EncryptImpl();        }        // ...    }}

经过修改后,我们的业务逻辑处理类EncryptManager不需要因为更换新的加密算法而修改代码,采用工厂模式看起来在一定程度上缓解了这种耦合,但代码的耦合本质上并没有变化,只是转移到工厂类中统一管理了。而通过IoC方式可以彻底解决这种耦合,方式是将耦合从代码中移除,统一放到XML配置文件中。通过IoC容器在需要的时候根据依赖关系,将需要的接口实现注入到需要它的业务逻辑类中,从而实现应用程序的配置和应用程序代码隔离,通过配置文件完成应用程序组件间相互关系的配置。

  • 依赖注入的三种实现方式:
    • 接口注入(Interface Injection)是指在接口中定义要注入的信息,并通过接口实现完成注入,此种方式很少使用。
    • Set方法注入(Setter Injection) 是指通过setter方法对依赖对象进行注入,通过<property name="xx" value="xx"/>标签实现,如果属性依赖其他对象,则可以<property name="name" value="chinsf"/>
    • 构造器注入(Construct Injection)是指通过构造函数进行注入,通过<constructor-arg index="参数下标"> <value>xx</value></constructor-arg>标签实现,其中参数下标从0开始。如果只有一个参数,则index可以省略。

比如一个类

package com.chinshinfeeng;import java.util.Date;public class Example {    private String name;    private Integer age;    private Date birthday;    public Example(String name, Integer age) {        this.name = name;        this.age = age;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }    // 其他getter、setter省略}

下面以Spring中XML配置方式说明后面两种注入方式,其中用到了bean的概念,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"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    // String基本类型的构造函数注入    <bean id="name" class="java.lang.String">        <constructor-arg value="chinsf"/>    </bean>    // setter注入    <bean id="eg1" class="com.chinshinfeeng.Example">        // 1. 直接设置属性值        <property name="age" value="20"/>        // 2. 属性值引用其他bean        <property name="name">            <ref bean="name"/>        </property>    </bean>    // 构造函数注入    <bean id="eg2" class="com.chinshinfeeng.Example">        <constructor-arg index="0">                                                        <value>chinsf2</value>        </constructor-arg>        <constructor-arg index="1">                                                 <value>27</value>        </constructor-arg>    </bean>

2.2 IoC容器

    Spring核心容器实现了IoC,目的是提供一种框架使得对代码的侵入性降低最低(minimally invasive)。在介绍容器如何管理应用对象的配置和生命周期前,首先介绍一下bean的基本知识。

2.2.1 Bean基础

    Bean用来描述Java中软件组织模型,通过Bean的组合可以快速构建应用程序,此外还可以通过Bean实现代码复用。
正如前面例子所示,一个Bean通过id标示,用class指定Bean的类型,name可以用来指定别名,id要求在所有的XML配置文件中全局唯一。通过scope指定bean生成方式,默认取值是singleton,即只有一个共享的实例存在。如果希望每次通过id获取bean时都会创建一个新的Bean,则需要显示指定scope="prototype"
Bean的其他属性设置请参考源代码。

2.2.2 Bean的生命周期

Spring中,Bean的生命周期包括:定义、初始化、使用和销毁这4个过程。

  • 定义 通常通过xml来进行定义,如前文例子所示
  • 初始化 Spring中有两种方式:1、通过init-method指定初始化过程;2、实现InitializingBean接口,该接口只包含一个函数afterPropertiesSet,通过覆盖该函数实现自己的初始化逻辑,此时bean被加载时,会通过BeanFactory设置所有属性,然后调用afterPropertiesSet函数。
    1 InitializingBean示例
// 示例1. 实现InitializingBeanpackage com.chinshinfeeng.biz.cache;public class InitDistributorCacheManagerListener implements InitializingBean {    private static final Logger logger = LoggerFactory.getLogger(InitDistributorCacheManagerListener.class);    @Autowired    private DistributorInfoDAO distributorInfoDAO;    // 某个关键信息需要在程序启动后加载到内存中    @Override    public void afterPropertiesSet() throws Exception {        try {            List<DistributorInfoDO> distributorInfoDOs = distributorInfoDAO.listAllValid();            DistributorCacheManager.processDistributorInfoList(distributorInfoDOs);        } catch (Exception e) {            logger.error("初次预加载信息失败", e);        }    }}// xml中定义<bean class="com.chinshinfeeng.biz.cache.InitDistributorCacheManagerListener" />

2 init-method示例
定义一个无参函数,在bean的xml定义中指定属性init-method,如代码示例所示。

package com.chinshinfeeng.service.kafka;public class KafkaProducer {     private static final Logger logger = LoggerFactory.getLogger(KafkaProducer.class);    // kafka生产者    protected IProducerProcessor producerProcessor    protected String topicName;    // destroy-method    protected void close() {        try {            producerProcessor.close();        } catch (Exception e) {            logger.error("生产者关闭失败", e);        }    }    // init-method    protected void start() {        logger.info("生产者开始启动");        try {            // kafka生产者初始化            this.producerProcessor = ***        } catch (Exception e) {            logger.error("生产者启动出错", e);        }        logger.info("生产者启动成功");    }}// xml配置<bean id="kafkaProducer" class="com.chinshinfeeng.service.kafka.KafkaProducer"          init-method="start"          destroy-method="close">        <property name="topicName" value="test_csf_topic" />    </bean>

分析Spring源代码可知(org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory),Spring会首先调用afterPropertiesSet方法(如果实现InitializingBean接口),然后通过反射方式调用init-method方法。为了确保代码和配置做到分离,推荐使用init-method方式进行初始化。

  • 使用 org.springframework.beans.factory.BeanFactory是Spring对IoC容器的抽象接口定义,由它负责bean的实例化、配置及建立对象间的依赖关系。ApplicationContextBeanFactory接口的派生接口,实际中应用较多,比如:
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"kafka-consumer.xml"});KafkaProducer kafkaProducer = (KafkaProducer)context.getBean('kafkaProducer');

当然实际中用的更多的是通过@Autowired注解直接使用,由Spring完成bean的创建、初始化和依赖关系的建立。

  • 销毁 销毁也有两种方式:1、通过destroy-method属性指定函数,完成清理销毁工作;2、实现DisposableBean接口,覆盖destroy函数实现自定义的清理工作。代码示例参考初始化部分。

2.2.3 集合的注入

    之前例子中给了Java基本类型如何配置,实际应用中,对集合如ListMapSet以及Properties进行配置还是比较常见的。具体如何配置给出示例:

<!-- List包括数组的配置 --><property name="locations">    <list>        <value>classpath:dal/db.properties</value>        <value>classpath:sal/kafka-producer.properties</value>    </list></property><!-- Set的配置 --><property name="set">    <set>        <value></value>    </set></property><!-- Map的配置 --><property name="map">    <map>        <entry key="age">            <value>21</value>        </entry>    </set></property><!-- Properties的配置 --><property name="map">    <props>        <prop key="host">localhost</prop>        <prop key="port">8080</prop>    </props></property>

事件机制

    事件机制包含事件的触发、事件的接收以及事件处理,事件模型通常基于观察者模式Observer实现,其中:触发事件的对象称为事件发送者;接收事件的对象称为事件接收者。
    Spring中事件机制的实现主要包括:

  • ApplicationListener 观察者,需要指定关心哪类事件
  • ApplicationEvent 事件,充当通信的媒介
  • ApplicationEventMulticaster 代理,负责观察者的注册和移除,即管理ApplicationListener

在Spring中,发布(publish)一个ApplicationEvent事件,注册观察这个事件的监听者listener就会接收到该事件通知。
示例如下:

// 流水事件观察者public class StreamListener implements SmartApplicationListener {    // 指定支持事件类型    @Override    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {        return eventType.isAssignableFrom(StreamEvent.class);    }    @Override    public void onApplicationEvent(ApplicationEvent event) {        StreamEvent streamEvent = (StreamEvent) event;        // do something    }    @Override    public int getOrder() {        return getListenerEnum().ordinal();    }}// 流水事件public class StreamEvent extends ApplicationEvent {    private StreamEventParam param;  // 参数    public StreamEvent(Object source, StreamEventParam param) {        super(source);        this.param = param;    }}// 事件发布者public class EmailBean implements ApplicationContextAware {     private ApplicationContext ctx;      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {          this.ctx = applicationContext;      }      public void publishStreamEvent(StreamEventParam param) {          StreamEvent streamEvent = new StreamEvent(/*参数设置*/);        ctx.publishEvent(event);          return;      }  }  
1 1
原创粉丝点击