Spring扩展原理

Spring的扩展点很多,本文讨论Spring的 BeanPostProcessor、BeanFactoryPostProcessor、ApplicationListener 和 SmartInitializingSingleton 这四个扩展的基础类或接口。 1. Bean后置处理器 bean后置处理器用于bean创建对象初始化前后进行拦截工作,因此它是spring容器扩展必不可少的组件。 1.1. BeanPostProcessor 允许自定义修改新bean实例的工厂钩子,例如,检查标记接口或用代理包装bean。 通常,通过标记接口等填充bean的后处理器将在初始化前实现后处理,而使用代理包装bean的后处理器通常在初始化后实现后处理。 注册表 ApplicationContext 可以在其bean定义中自动检测 BeanPostProcessor bean,并将这些后处理器应用于随后创建的任何bean。一个普通的bean factory允许对后处理器进行编程注册,将它们应用于通过bean工厂创建的所有bean。 顺序 在 ApplicationContext 中自动检测到的 BeanPostProcessor bean将根据 org.springframework.core.PriorityOrdered 和 org.springframework.core.ordered 语义进行排序。相反,以编程方式向 BeanFactory 注册的 BeanPostProcessor bean将按注册顺序应用;通过实现 PriorityOrdered 或 Ordered 接口表示的任何排序语义对于以编程方式注册的后处理器都将被忽略。此外,BeanPostProcessor bean不考虑 @Order 注解。 public interface BeanPostProcessor { /** * 在传入的bean属性设置之后、初始化之前调用。在任何bean初始化回调(如initializengbean的 * afterPropertiesSet或自定义init方法)之前,将此BeanPostProcessor应用于给定的新bean实例。 * bean已经填充了属性值。返回的bean实例可以是原始实例的包装器。 * 默认实现按原样返回给定的bean。 */ @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /** * 在传入的bean初始化完成之后调用 * 在任何bean初始化回调(如initializengbean的afterPropertiesSet或自定义init方法)之后, * 将此BeanPostProcessor应用于给定的新bean实例。bean已经填充了属性值。 * 返回的bean实例可以是原始实例的包装器。 * 对于FactoryBean,将为FactoryBean实例和由FactoryBean创建的对象(从Spring 2.0开始)调用此回调。 * 后处理器可以通过FactoryBean检查的相应bean实例来决定是应用于FactoryBean还是创建的对象, * 或者两者都应用。此回调也将在InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation * 方法触发短路后调用,与所有其他BeanPostProcessor回调不同。 默认实现按原样返回给定的bean。 */ @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } ...

2020-08-12 · 4 min · 804 words · Hank

给Spring中注册Bean的几种方式

1. 使用@Bean定义单个Bean 基于 @Bean 注解导入单个Bean。这种方式跟xml中 <bean> 标签等价,可以添加外部自定义Bean,但是需要自己创建Bean实例,而且只能导入单个Bean。注解定义如下: @Bean注解定义 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { // 自定义bean的名称 @AliasFor("name") String[] value() default {}; // 同value属性,自定义bean的名称 @AliasFor("value") String[] name() default {}; // 设置当前注入的bean是否可用于自动注入,默认是true。 // 如果设置为false,那么即使该bean注入到Spring了,在自动注入时也会找不到bean而抛出NoSuchBeanDefinitionException异常。 // 5.1版本新增 boolean autowireCandidate() default true; (1) // 自定义Bean的初始化方法名称,Spring 在Bean初始化时会调用该方法 String initMethod() default ""; // 自定义Bean的销毁方法名称,Spring在容器关闭时会调用该方法进行自定义Bean销毁工作 String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; } 1 功能与 @Primary 注解相关,都用于自动注入时Bean的选择,而 @Primary 用于指定注入时存在多个Bean实例时优先用哪个,而 autowireCandidate 属性则是设置Bean是否参与自动注入,true 则参与,false 则不参与(即使有Bean实例也可能在自动注入时抛出 NoSuchBeanDefinitionException 异常) ...

2020-05-07 · 4 min · 825 words · Hank

Spring IoC容器实现原理

1. IOC容器接口设计 Spring IOC容器接口设计如下图所示: Figure 1. Spring IoC容器接口设计 IOC容器两个核心的接口为BeanFacotry 和ApplicationContext,前者提供容器管理Bean的基本功能,后者则扩展 BeanFactory,提供了容器级的强大功能,如国际化支持、资源加载、事件、应用上下文环境等。 2. BeanFactory BeanFactory ,即bean工厂,定义IOC容器的基本规范,提供管理容器Bean的基本功能,如获取bean、判断单例、原型、判断包含bean、获取bean类型和别名等。下边有三个子接口 ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory,他们具备不同的功能。 ListableBeanFactory 扩展 BeanFacoty ,增加了列举容器中bean的功能,BeanFactory 提供查询单个Bean的方法,而 ListableBeanFactory 可以查询多个Bean,如获取 BeanDefinition 的名称、数量、按类型查询bean和bean的名称等。 HierarchicalBeanFactory 在 BeanFactory 的基础上增加了父级 BeanFactory 的功能,即 BeanFacotry 可以有层级关系了。 AutowireCapableBeanFactory 在 BeanFactory 的基础上增加自动注入相关的功能。 ConfigurableBeanFactory 这是一个配置接口,扩展 HierarchicalBeanFactory,用于给 BeanFactory 提供各种配置功能,如配置 parentBeanFactory、beanClassLoader 添加后置处理器等等。 ConfigurableListableBeanFactory 这是一个配置接口,提供了大量方法来扩展 ConfigurableBeanFactory 和 ListableBeanFactory ,大多数可列举的bean工厂都需要实现它,除了 ConfigurableBeanFactory 的配置功能外,它还具备分析和修改bean定义以及预实例化单例bean的的能力。 3. ApplicationContext BeanFactory 提供了管理bean的基础功能,而 ApplicationContext 则在其基础上有用强大的容器特性。ApplicationContext 作为应用上下文,是Spring IOC容器的核心接口,具备 BeanFactory 基础管理bean的功能,还继承了 EnvironmentCapable 、 MessageSource 、 ResourceLoader 、 ApplicationEventPublisher ,具有资源加载、国际化支持、容器事件支持、系统环境支持等能力。 ...

2020-04-08 · 3 min · 627 words · Hank

Spring Bean自动扫描原理

Spring 基于注解的Bean自动扫描是由后置处理器 ConfigurationClassPostProcessor 来实现的。 1. ConfigurationClassPostProcessor类 ConfigurationClassPostProcessor 是一个 BeanDefinitionRegistryPostProcessor 实现,它的核心功能就是在容器启动时处理Spring的Java代码配置类(被 @Configuration 注解标记)。而且该类实现了 PriorityOrdered 接口,表示它是一个高优先级的后置处理器,会首先被执行。 ConfigurationClassPostProcessor 的定义如下: public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { // 省略代码 } 2. 注册bean定义 创建 AnnotationConfigApplicationContext 时,其构造函数如下: AnnotationConfigApplicationContext构造函数 public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); } 首先,会调用 this() 方法调用构造函数: public AnnotationConfigApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } 此时会创建 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 对象,前者用来读取配置类(@Configuration 注解标注)所定义的Bean定义信息,而后者用来扫描*classpath*下的bean定义信息。 AnnotatedBeanDefinitionReader 内部创建实例时会执行: ...

2020-03-10 · 9 min · 1827 words · Hank

Spring IoC容器启动过程

1. 容器启动入口 一般而言,都使用 ApplicationContext 容器实现,这里使用了基于注解的 AnnotationConfigApplicationContext 实现。 要跟踪Spring的源码,可能需要结合不同的功能测试代码,以便跟踪源码时查看Spring具体的功能实现。比如:要了解Spring @ComponentScan 注解的原理,则首先需要编写一个配置类(@Configuration),然后定义组件扫描。 本文的跟踪代码的过程可能涉及多个部分的测试代码,但是容器启动的代码都类似,详细的代码见 Github。 容器启动测试代码[1] AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeConfig.class); (1) System.out.println("IOC容器创建完成..."); User user = applicationContext.getBean(User.class); User aUser = applicationContext.getBean(User.class); System.out.println(user == aUser); 1 构造一个 AnnotationConfigApplicationContext 对象。 AnnotationConfigApplicationContext 的构造函数如下: AnnotationConfigApplicationContext构造函数 public AnnotationConfigApplicationContext(Class<?>... componentClasses) { this(); register(componentClasses); refresh(); } 1.1. ApplicationContext 类实现关系 AnnotationConfigApplicationContext 扩展了 GenericApplicationContext,增加了基于java配置类的支持,而 GenericApplicationContext 内部持有一个 DefaultListableBeanFactory 实例,而且仅允许刷新一次容器: GenericApplicationContext构造函数 public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); } ...

2020-02-06 · 7 min · 1345 words · Hank

使用Springboot开发websocket程序(四)——使用RabbitMQ作为STOMP消息代理

上一篇,我们在介绍了Spring中如何使用websocket的子协议stomp,并使用简单的基于内存的stomp消息代理来编写了一个web聊天室实例。基于内存的stomp消息代理,虽然能够满足基本需求,但还是存在一些不足,比如由于stomp代理在应用内部,多个外部websocket应用需要消息互通,那么就难以满足了。在本篇,我们来学习如何使用RabbitMQ作为stomp代理。 1. 为何要使用外部消息代理 简单消息代理,能够满足单websocket应用的需要,但是如果有多个websocket应用,他们之间需要进行消息共享,那么就需要做大量的工作才能实现了。其实,MQ一个最重要的作用就在于能个在各个系统间解耦。引入外部MQ作为stomp消息代理,很好的解决了多系统消息共享的问题,只要其支持stomp协议。RabbitMQ本身提供了对STOMP的支持,加上后结构变化如下: 前边的是单应用时的结构,后边为怎么了RabbitMQ过后,多个应用程序结构。 2. RabbitMQ对STOMP的支持 RabbitMQ对stomp协议的支持是通过插件的方式,默认stomp插件是关闭的,我们需要先启用之。 2.1. 启用插件 进入rabbitmq所在服务器,然后控制台输入如下命令来启用stomp插件: ``rabbitmq-plugins enable rabbitmq_stomp`` 然后可以查看插件是否启用成功: ``rabbitmq-plugins list`` 2.2. 插件配置 默认情况下,STOMP将会监听61613端口,默认的用户名和密码都为guest。通过配置文件来配置: ubuntu下rabbitmq的配置文件在/etc/rabbitmq/rabbitmq.conf,找到stomp开头的选项,就可以进行配置了 比如配置STOMP监听端口: ``stomp.listeners.tcp.1 = 12345`` RabbitMQ中STOMP适配器连接时如果用户名和密码使用默认的guest/guest,则可以忽略,如果需要修改,则配置如下: stomp.default_user = guest stomp.default_pass = guest 2.3. Destinations STOMP规范并没有规定消息代理来支持什么样的目的地(destination),只是根据消息头的destination的值来判断消息发送的目的地,一般由消息代理自定义支持,RabbitMQ中定义了几种destination类型: #exchange[/exchange]: 发送到任意的routing key和订阅任意的binding key #queue[/queue]: 发送和订阅队列,该队列由STOMP管理 #amqqueue[/amq/queue]: 发送和订阅外部创建的队列 #topic[/topic]: 发送和订阅到topic #temptopic[/temp-queue/]: 创建临时的队列(使用reply-to请求头) 现在,我们结合代码来看看Spring中对RabbitMQ的这几类destination是如何支持的。 3. Spring中使用RabbitMQ消息代理 我们通过一个demo来看看如何在Spring中使用RabbitMQ支持的这几个destination,整体界面如下; 下边的示例仅贴上部分关键代码,完整的代码可以参看文末的源码。 首先,我们创建一个名为03-websocket-stomp-rabbitmq的springboot工程,引入如下依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-net</artifactId> <version>2.0.5.RELEASE</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.0.33.Final</version> </dependency> <dependencies> ...

2019-11-12 · 3 min · 509 words · Hank

使用Springboot开发websocket程序(三)——基于子协议STOMP的web聊天室

我们使用Spring Boot对原生WebSocket的支持来编写了一个简单的聊天室程序。它仅支持群发,如果要做到点对点消息,我们还需要编写代码自己实现。本篇,我们将使用websocket的子协议STOMP来更新我们的聊天室程序,使其可以支持单聊。 1. STOMP 1.1. 为什么要支持子协议 WebSocket是一种简单的通信协议,而并非消息协议。它是TCP之上的非常薄的一层,所做的事情仅仅是将字节流转换为消息流(文本或二进制)。至于消息是什么含义,怎么路由和解析,都交由应用程序自身来决定。HTTP协议是一种应用级协议,它明确告诉我们请求的地址、格式、编码等等信息,但是websocket与之不同,websocket并不提供详细的信息来告诉我们如何路由和处理消息。因此,对于开发大型应用程序而言,websocke级别太低了,要实现非常复杂的功能,我们需要进行大量的编码工作。这就好比,大多JAVA WEB开发都会选择使用Spring框架,而不是基于Servlet API来实现。 基于这个原因,WebSocket RFC定义了 子协议的使用规范。在握手阶段,客户端和服务端使用Sec-WebSocket-Protocol请求头来通知彼此使用子协议,即更高级的、应用级的协议。当然,也可以不使用子协议,但是客户端和服务端仍然需要定义消息的格式。使用更规范的通用消息协议,更能让应用程序开发和维护变得简单。STOMP就是这样的一个消息协议,Spring框架提供了对其的支持。 1.2. 什么是STOMP STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,中文称简单(流)文本定向消息协议,它是一种简单的互操作协议**,旨在通过消息代理在客户端之间传递异步消息,STOMP协议具有简单行和互操作性,因此在多种语言和多种平台上得到广泛地应用。。 STOMP是由于需要通过脚本语言(例如Ruby,Python和Perl)连接到企业消息代理而产生的,旨在对消息进行简单的消息处理,例如可靠地发送单个消息并断开连接或在给定目的地上消耗所有消息等等。它是其他开放消息协议(例如AMQP)和JMS代理(例如OpenWire)中使用的实现特定wire protocol的替代。STOMP仅仅实现部分常用的消息API,而不是实现完整的消息API,因此更加轻量和简单。 STOMP除了支持文本消息外,也支持二进制消息,默认使用UTF-8的编码格式。 STOMP有几个比较重要的概念: frame:帧,即STOMP客户端和服务端发送的数据 COMMAND: frame的命令,由STOMP规范定义,用来表示消息的特定用途,比如连接服务端、发送消息等,每个frame都有命令 destination:消息发送的目的地址,通过消息头来表示,如destination:/topic/chat STOMP基于frame,frame的模型源于HTTP,一个frame由命令、一组可选的标头和可选的主体组成,客户端和服务端发送frame来进行通信。frame的格式如下: COMMAND header1:value1 header2:value2 Body^@ frame分为两个部分:消息头和消息体。消息头第一行为命令,然后跟key:value格式的头信息;然后是消息体,消息体跟消息头之间用一个空行分隔。最后,消息体后跟八位空字节(上文用^@表示)。另外,frame的命令和消息头信息都区分大小写。 STOMP客户端支持的命令包括:SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT、CONNECT、STOMP 服务端支持的命令包括:CONNECTED、MESSAGE、RECEIPT、ERROR 只有几种消息可以包含消息体:SEND、MESSAGE、ERROR 举个例子,客户端连接到服务端,则会想服务端发送如下的frame: CONNECT accept-version:1.0,1.1,2.0 host:stomp.github.org ^@ 服务端接收连接请求则会发送下边的frame给客户端,否则发送ERROR frame: CONNECTED version:1.2 ^@ 1.3. 使用STOMP的优点 使用STOMP作为子协议,与使用原生WebSocket相比,Spring框架和Spring Security可以提供更丰富的编程模型。具体的优点有以下几点: 无需自定义消息协议和消息格式 可以使用STOMP客户端,包括Spring框架中的Java客户端 消息代理(例如RabbitMQ,ActiveMQ和其他代理)可以用于管理订阅和广播消息 可以在任意数量的@Controller中组织应用程序逻辑,并根据STOMP消息头将消息路由给它们,而对于给定的连接,可以使用单个WebSocketHandler处理原始WebSocket消息 可以使用Spring Security基于STOMP 目标(destinations)和消息类型来对消息进行安全处理 详细内容请看STOMP规范: https://stomp.github.io/stomp-specification-1.2.html 2. Spring Boot中使用STOMP 使用STOMP有如此多的好处,我们看看Spring中如何使用。 在Spring boot中使用STOMP,大概需要以下几步: ...

2019-11-02 · 5 min · 914 words · Hank

使用Springboot开发websocket程序(二)——基于原生websocket的web聊天室

上一篇介绍了什么是websocket,说到websocket是一种由HTML5定义的浏览器和服务器保持长连接的通信协议,可以进行实时数据交换。在本篇,我们将使用Spring boot,基于原生websocket开发一个web聊天室,并一步步介绍如何在spring boot中开发websocket程序。 一个WebSocket程序包括客户端和服务端。WebSocket客户端除了支持Html5的浏览器外,还包括各大语言提供的WebSocket实现,比如Java中Spring框架的实现,从而在没有浏览器时也能进行websocket通信。HTML5中WebSocket API请看 这里。服务端中,Java定义Java WebSocket API标准 JSR-356[JSR-356],Java主流容器都已经支持websocket,主要包括Tomcat 7.0.47 ,Jetty 9.1 +,GlassFish 4.1 +,WebLogic 12.1.3+和Undertow 1.0(以及WildFly 8.0+)等。 1. 整体功能 现在我们来写一个简单的web的聊天室程序,并一步步学习Spring中是如何封装WebSocket的,这里工程还是使用Spring Boot。 整体功能界面如下: 功能很简单:用户填写上自己的用户名,然后点击链接按钮进入聊天室,然后就可以发送消息给其他人,聊天室中的用户可以看到他人的连入信息和发送的消息。 我们看看在spring boot中如何编写以上程序。 2. 服务端 1、新建一个springboot-websocket的maven工程,引入如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 引入该启动器后,Spring boot会自动引入一下几个启动器或jar包: spring-boot-starter:spring boot启动器 spring-boot-starter-web:spring boot web工程启动器 spring-messaging:包含对消息传递应用程序的基础支持,下一篇用到的STOMP协议的支持也在这个模块。它是Spring Integration项目中的关键抽象,包含Message,MessageChannel,MessageHandler和其他可以作为此类消息传递体系结构基础支撑 spring-websocket: Spring对websocket的支持,它与Java WebSocket API标准( JSR-356[JSR-356])兼容,并且还提供了很多扩展 由此可见,对websocket的支持在spring-websocket和spring-messaing两个模块。 2、编写启动类: @SpringBootApplication public class SpringWebsocketApplication { public static void main(String[] args) { SpringApplication.run(SpringWebsocketApplication.class, args); } } 上边的两步没什么说的,常规的spring boot工程开发。 3、编写websocket握手拦截器: ...

2019-11-01 · 3 min · 577 words · Hank

使用Springboot开发websocket程序(一)——什么是websocket

在互联网飞速发展的当代,浏览器和服务器之间的实时通信已经越来越重要,传统的HTTP协议难以解决实时通信的需求。因此,由HTML5定义的websocket协议应运而生。这里我将用三篇文章来介绍websocket,并使用Springboot开发一个web聊天室的程序,其中会使用原生websocket协议开发,也会单独来介绍使用STOMP协议来开发。 在本篇,我们先看看什么是websocket。 1. 诞生背景 由于HTTP是无状态的协议,采用请求响应模型,服务端是不能主动推送信息给客户端的,只能由客户端发起请求,然后再由服务端进行响应。如果服务端数据有变化,必须通过客户端来获取,服务端是不能主动推送的。要解决实时交换数据的需求,一般的做法是通过轮询(还有http long pull和streaming两种方案,参见这篇 博文)来获得服务端最新信息,即:客户端(浏览器)每隔一段时间(比如1秒)向服务端发起请求,以获取最新的数据。 我们知道,HTTP是一种无状态协议,客户端请求然后服务端响应,则会断开TCP连接。因此,使用轮询的弊端是,客户端不断与服务端建立连接、断开连接,效率非常低下,且非常消耗服务器资源,并且也不是真正意义上的实时。 因此,我们需要一种技术,能够让服务端主动推送数据给客户端,而且消耗资源很少,效率更高。 2. 什么是websocket WebSocket是一种在 单个TCP连接上进行全双工通信的协议,2008诞生,在2011年被IETF定为标准 RFC6455,并由 RFC7936补充规范。WebSocket API也被W3C定为标准,目前各大主流浏览器都已经支持WebSocket。 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据,这些数据通常包括两种:文本和二进制。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 3. 客户端与服务器的握手 WebSocket基于Http协议来完成握手(Handshake)阶段,在该阶段,客户端需要将服务端请求协议升级,由原来的HTTP协议升级为WebSocket协议,如果服务端能够支持WebSocket,则会告诉客户端升级成功。之后,双方就建立了一条快速通道,可以互相进行数据交互,后续通信就采用WebSocket协议来完成。 Figure 1. websocket与http的区别(图片来源于网络 我们来简单看一下websocket与服务端的握手过程。WebSocket请求头跟HTTP请求头类似,只是添加了一些特殊的头标签,一个握手请求的头信息如下: GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com 可以看到,websocket在握手阶段,采用的仍然是HTTP协议,但是请求添加了一些websocket特有的信息: Upgrade: websocket:就是在请求服务端升级通信协议为WebSocket Sec-WebSocket-Key:浏览器随机生成的一个base64的加密字符串,服务器用它来进行校验请求是来自websocket客户端 Sec-WebSocket-Protocol: 告诉服务器不直接使用websocket,而是使用websocket协议的子协议,例如后文将要介绍的STOMP就是websocket协议的子协议来通信 Sec-WebSocket-Version: 使用websocket协议的版本号 请求发送到服务器,如果服务器也支持websocket则升级成功,并响应如下头信息: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat 这里,websocket握手完成服务器则会响应101状态码,至此,HTTP协议服务就已经结束,后续通信就采用websocket了。 4. websocket的应用场景 浏览器需要与服务端进行实时数据交换时,都可以使用websocket协议。最常见的如视频直播网站,都有聊天室和弹幕功能,巨大的数据量,如果使用HTTP,交过可想而知。另外,网页游戏开发、也包括一些电商类网站的秒杀、团购等等场景,都会用到websocket。 5. websocket备选方案 目前,各大浏览器都已经支持websocket,如果仍要兼容老版本的浏览器,或者某些代理服务对websocket协议有一些限制,又或者由于连接保持打开时间过长而被浏览器强制断开,导致出现问题,该怎么办呢? ...

2019-11-01 · 1 min · 98 words · Hank

SpringBoot自定义日志配置

当系统出现问题时,先从表现分析问题远远不够,要快速、准确地分析并定位问题原因,日志就是一个非常重要且必不可少的手段。Java而言,日志框架有很多,常用的有Common-logging、Java自带的Logging、Log4J、Logback等。另外,还有一个Slf4J的框架,主要目的是将这些框架进行整合。本篇主要介绍的是SpringBoot中的日志支持,如何进行日志自定义。 1. 日志框架选择 在SpringBoot内部,使用common-logging框架来记录日志,也支持自定义配置,支持的框架有:Java util logging(JDK自带)、Log4J和Logback等。 如果使用了Springboot的starter,则springboot会使用Logback作为默认的日志框架,从maven的依赖关系可以看到,spring-boot-starter下依赖的spring-boot-starter-logging启动器使用了logback: <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> </dependency> </dependencies> 如上所示,Spring boot内部使用Slf4J框架,作为日志抽象层,以便整合并支持各大主流的框架,具体日志实现还是使用logback。 2. 日志输出内容 一个标准Spring boot日志如下图所示: 其实,Spring boot工程默认的日志级别为INFO,仅输出INFO级别的日志信息,由于我这里开启了debug模式(配置debug=true),所以能打印出DEBUG级别的日志级别。 一般而言,日志有几个重要的输出项: 时间:记录日志时的时间,精确到毫秒,便于排序,缩小问题范围的重要指标,例如问题产生在某天10:20,那么就需要查找这段时间的所有日志以帮助分析问题 线程:输入日志的执行线程名称 日志级别:日志的输出级别,不同的日志框架级别定义不同,而且日志级别有层级之分,一般而言,日志级别从低到高都有调试(DEBUG)、信息(INFO)、警告(WARNING)、错误(ERROR)等,不同的框架可能名称不同,日志级别越高,输出的信息越少,反之则越多 进程ID:记录输出日志的进程ID 类名:输出日志的类名称,通常是简写的,例如:c.b.WebappViewStarter 日志内容:具体的日志内容 如果输出终端支持ANSI字符集,那么日志会彩色显示,可以通过配置spring.output.ansi.enabled的值来设置彩色显示的条件,包括ALWAYS、DETECT、NEVER。日志的颜色可以通过在日志输出格式时定义%clr来配置,支持的颜色有:blue、cyan、faint、green、magenta、red、yellow,例如: ``%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}`` 输出的日志级别默认为INFO,可以通过在配置文件修改来自定义日志级别: logging.level.root=WARN logging.level.org.springframework.web=DEBUG logging.level.org.hibernate=ERROR root配置整个日志输出的级别,没有特别配置均按照此级别输出,而下边两项则是配置具体某一个框架的日志级别。 3. 输出日志到文件 默认情况下,SpringBoot仅将日志输出到控制台,但我们更希望将日志输出到单独的文件,此时我们需要在配置文件中添加logging.file或者logging.path配置项,他们的作用如下: logging.file:配置一个具体的日志文件的路径,可以是绝对或者相对路径,建议定义为绝对路径,避免不需要的路径问题,例如logging.file=/data/logs/springboiot-demo.log, logging.path:配置一个存储日志的目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log 需要注意的是,二者不能同时使用,如若同时使用,则只有logging.file配置生效。默认情况下,日志文件的大小达到10MB时会重新创建新文件来写入日志,原日志文件会被顺序重新命名,例如:springboot-demo.log、springboot-demo.log1,默认输出的日志级别为:ERROR、WARN、INFO。 4. 自定义日志 Springboot虽然可以成功将日志输出到自定义的文件中,但是通常,我们更希望按照日期来输出日志文件,例如每天一个日志文件,保留一定天数的日志文件。Springboot也支持此类特殊需求的自定义日志,只需要提供一个自定义日志配置文件即可,该配置文件可以通过logging.config配置项来定义,例如:logging.config=classpath:logging-config.xml,但是,一般不需要我们配置,只需要使用springboot能加载的日志配置文件名即可,springboot默认可以加载下表的这些配置文件: 日志框架 自定义日志配置文件 Logback logback-spring.xml, logback-spring.groovy, logback.xml 或 logback.groovy Log4j2 log4j2-spring.xml or log4j2.xml JDK (Java Util Logging) logging.properties 例如,我们要使用默认的logback日志框架,并且日志要按天输出,我们只需在resources目录下添加一个logback-spring.xml文件,加入如下内容: ...

2019-09-06 · 1 min · 182 words · Hank

Spring AMQP消息转换

上一篇,我们介绍了如果使用Spring AMQP注解来实现消息发送和监听,示例都是使用的默认的消息转换器,即SimpleMessageConverter,它只能处理byte[]、String、java序列化对象(实现了Serializable接口的对象)。 通常,不推荐使用Java序列化,因为它存在与Java对象强耦合、依赖java语言等缺点,Spring AMQP也提供了其他的消息转换方式,在本篇,我们将重点来看看如果将消息序列化为JSON格式。 1. MessageConverter Spring AMQP消息转换定义了顶层接口MessageConverter,它的定义如下: public interface MessageConverter { // 将对象转换为Message对象,支持自定义消息属性 Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException; // 将Message转换为对象 Object fromMessage(Message message) throws MessageConversionException; } 它定义了两个方法:将对象转换为Message,将Message转换为对象。 同时,在AmqpTemplate中定义了便捷的消息转换和发送的方法: void convertAndSend(Object message) throws AmqpException; void convertAndSend(String routingKey, Object message) throws AmqpException; void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException; void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException; ...

2019-06-17 · 3 min · 488 words · Hank

Spring AMQP注解的使用

上一篇 "Spring AMQP简介和使用",我们介绍了Spring AMQP的一些基本要素和概念,也通过一些示例代码介绍了消息的发送和接收,但是都是使用的原始编码方式来实现,并不依赖Spring环境。其实,Spring AMQP也支持使用注解的方式来进行异步接收消息,极大的简化了编码。 1. hello world 要使用注解,首先需要在Spring应用环境中,我们看一个最简单的demo: 1、重新新建一个Spring boot工程,添加如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-amqp</artifactId> <version>1.7.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>1.7.6.RELEASE</version> </dependency> 2、新建一个Spring配置类RabbitConfiguration,用来申明Bean: @Configuration public class RabbitConfiguration { public static final String ANONYMOUS_QUEUE_NAME = "spring.amqp.anonymous.queue"; @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory("192.168.0.27", 5672); cachingConnectionFactory.setUsername("admin"); cachingConnectionFactory.setPassword("123456"); return cachingConnectionFactory; } @Bean public AmqpAdmin amqpAdmin() { return new RabbitAdmin(connectionFactory()); } @Bean public RabbitTemplate rabbitTemplate() { return new RabbitTemplate(connectionFactory()); } @Bean public Queue anonymousQueue() { // 匿名队列 return new AnonymousQueue(() -> ANONYMOUS_QUEUE_NAME); } } ...

2019-06-13 · 5 min · 990 words · Hank

Spring AMQP简介和使用

1. 前言 很早之前写过几篇关于RabbitMQ的一些基础文章,在本篇中,我们将来学习Spring AMQP。 Spring AMQP 是 Spring 对 AMQP( http://www.amqp.org) 协议的封装和扩展,它提供了模板来对消息的发送和接收进行高级抽象,还提供了基于消息驱动的POJO的支持(类似JMS,Java消息服务)。 在开始之前,首先需要了解一些 RabbitMQ 和 AMQP 的一些基础概念,并在你的机器上安装 RabbitMQ, 本文使用的spring-amqp的版本为1.7.6。 2. Spring AMQP抽象 Spring AMQP 是 Spring 对 AMQP 协议的封装和扩展,提供了消息发送和接收的模板。Spring AMQP项目将核心Spring概念应用于基于 AMQP 的消息传递解决方案的开发,以便更容易和简单的管理AMQO资源。 Spring AMQP由spring-amqp和spring-rabbit两个模块组成。spring-amqp模块位于org.springframework.amqp.core包,它的目标是提供不依赖于任何特定AMQP代理实现或客户端库的通用抽象;而spring-rabbit是spring-amqp通用抽象的具体实现,目前仅提供了rabbitmq的实现。 Spring AMQP包括一些基本的抽象定义(上边说过,他们位于org.springframework.amqp.coreb包中,而非AMQP协议本身定义): 2.1. Message 在0-9-1版本的 AMQP 规范中没有定义 Message 类或接口,当执行诸如 basicPublish() 操作时,内容作为字节数组参数传递,而其他属性作为单独的参数传递。Spring AMQP将 Message 类定义为更通用的AMQP域模型表示的一部分。Message该类的目的是将主体和属性封装在单个实例中,以便API可以更简单。以下示例显示了Message类定义: public class Message { private final MessageProperties messageProperties; private final byte[] body; public Message(byte[] body, MessageProperties messageProperties) { this.body = body; this.messageProperties = messageProperties; } public byte[] getBody() { return this.body; } public MessageProperties getMessageProperties() { return this.messageProperties; } } ...

2019-06-05 · 8 min · 1703 words · Hank

Spring boot集成quartz并实现任务调度

最近在实现任务调度服务,使用 quartz调度框架,由于使用的spring boot版本为1.5.10,该版本并没有提供quartz的starter,在集成的时候还是遇到了很多问题。本文并不会详细介绍quartz框架,请自行查阅相关资料。如果对Spring boot不太熟悉,可以看前边几篇关于spring boot的文章。 1. quartz简介 1.1. 核心概念 scheduler:任务调度器,相当于指挥中心,负责管理和调度job,由SchedulerFactory创建和关闭,创建后需要通过starter()方法来启动调度。 trigger:触发器,定义job的触发时机,通过TriggerBuilder来创建,有SimpleTrigger和CronTrigger,前者可以定义在某一个时刻触发或者周期性触发,后者使用cron表达式来定义触发时间。 Job:具体的调度业务逻辑实现,也就是任务,具体任务需要实现org.quartz.Job接口。 JobDetail:org.quartz.JobDetail用来描述Job实例的属性,并且可以通过JobDataMap传递给Job实例数据,通过JobBuilder来创建。 1.2. quartz配置 quartz框架默认会通过quartz.properties文件来加载配置信息,有几个重要的配置项: org.quartz.scheduler.*:调度器相关配置 org.quartz.threadPool.*:线程池相关配置 org.quartz.jobStore.*:quartz数据存储相关配置,在quartz中,有三种存储类型: RAMJobStore:基于内存存储job JobStoreCMT:基于数据库的数据存储,并且受运行的java容器的事务控制 JobStoreTX:基于数据库的数据存储,不受事务控制 org.quartz.dataSource.*:配置数据库存储quartz数据时的数据源信息 org.quartz.plugin.*:quartz插件相关配置 quartz简单介绍这么多,更多信息请看 这里。 2. Spring boot与quartz集成 前边说过,我使用的spring boot版本为1.5.10,没有quartz的starter,所以需要自己编码实现。同时,我还需要对quartz做持久化处理,需要在mysql库中导入quartz的建表脚本。集成步骤如下: 1、引入依赖 引入quartz的依赖包,quartz-jobs根据实际需要决定是否引入。 <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.2.3</version> </dependency> 2、创建表 在下载的quartz包中有建表脚本,我的为quartz-2.2.3,建表脚本在/docs/dbTables下边,导入tables_mysql_innodb.sql即可。 3、配置文件 在resources目下新建一个quartz.properties配置文件,我的配置如下: #============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName:event-scheduler org.quartz.scheduler.instanceId:AUTO org.quartz.scheduler.skipUpdateCheck:true #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount:5 org.quartz.threadPool.threadPriority:5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold:60000 org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.useProperties:false org.quartz.jobStore.tablePrefix:QRTZ_ org.quartz.jobStore.isClustered:false #============================================================================ # Configure Datasources #============================================================================ org.quartz.dataSource.ds.maxConnections:7 #============================================================================ # Configure Plugins #============================================================================ org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingTriggerHistoryPlugin ...

2019-01-22 · 4 min · 647 words · Hank

Spring boot工程Feign请求时Get请求无法传递body的问题

前一篇,我们解决了Spring的RestTemplate在发送Get请求不能传递body,相比RestTemplate,我们更多的还是采用更方便的申明式服务调用框架Feign。本篇不会介绍Feign,而是解决Feign发送Get请求时仍然不能传递body的问题。 1. 准备 在这里,我使用的Spring boot的版本为1.5.10.RELEASE,Spring Cloud版本为Edgware.SR3。 准备三个服务:注册中心、服务生产者、服务消费,这里我们继续使用在 Spring Cloud服务注册中心Eureka定义的三个服务:服务注册中心01-eureka-server、服务提供者01-service-demo,同时新建一个服务消费者01-feign-consumer,使用feign来请求服务而不是ribbon,记得在启动类加上@EnableFeignClients启用Feign。 1、在服务提供者01-service-demo添加一个接口,代码如下: @GetMapping("/hello/user") public String sayHello(@RequestBody User user) { return "hello, " + user.getName() + ", give you a gift " + user.getGift(); } 这样,Get请求需要接收body数据。 2、在服务消费者01-feign-consumer编写如下代码: (1)在SayHelloClient中添加如下声明式服务请求: @FeignClient("hello-service") public interface SayHelloClient { @GetMapping(value = "/hello/user") String sayHello(@RequestBody User user); } (2)在SayHelloController中调用SayHelloClient: @RestController public class SayHelloController { private static Logger log = LoggerFactory.getLogger(SayHelloController.class); @Autowired private ApiProductClient apiProductClient; @GetMapping("/user") public String userHello() { User user = new User(); user.setName("lily"); user.setGift("birthday card"); return sayHelloClient.sayHello(user); } } ...

2019-01-22 · 1 min · 125 words · Hank

使用Spring的RestTemplate发送GET请求,并支持传递Request body参数

最近在使用Spring Boot实现微服务,都是使用RESTful风格的Api接口,服务间使用RestTemplate来进行HTTP通信,遇到这样一个需求:开发一个查询请求Api,参数使用JSON格式的字符串来提交。 1. 请求格式 希望的请求格式如下: GET /pointCard/ HTTP/1.1 Host: localhost:8100 Content-Type: application/json;charset=UTF-8 Content-Length: 114 {"iColumns":7,"iDisplayLength":10,"iDisplayStart":0,"iSortingCols":0,"sColumns":"","sEcho":1,"subjectId":"11227"} 在RESTful下,这样的设计是合理的,GET请求表示从服务器获取资源,但需要将查询参数以JSON格式来提交。但是,这违背了传统的GET请求的规范,我们都知道,GET请求只能将请求参数拼接URI后边,而不能单独传递request body参数,除非你改用POST。 2. 代码实现 我们先来编一个上述请求的API,然后进行测试。 1、编写一个API: @GetMapping(value = "/") public Response getById(@RequestBody @Valid PointCardQuery query) throws Exception { Assert.notNull(query,"查询条件不能为空!"); …… return Response.success(pointCardPurePager, "积分卡获取成功!"); } 上边的代码片段处于一个Restcontroller,要求使用GET方法,并且使用了@RequestBody注解来获取request body参数。 2、我们使用RestTemplate来测试一下: @Test public void testGetWithBody() { RestTemplate restTemplate = new RestTemplate(); String p = "{\"iColumns\":7,\"iDisplayLength\":10,\"iDisplayStart\":0,\"iSortingCols\":0,\"sColumns\":\"\",\"sEcho\":1,\"subjectId\":\"11227\"}"; String url = "http://localhost:8100/pointCard/"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); HttpEntity<String> httpEntity = new HttpEntity<>(p, headers); ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class); String body = responseEntity.getBody(); System.out.println(body); System.out.println(responseEntity.getStatusCode()); System.out.println(responseEntity.getStatusCodeValue()); System.out.println(responseEntity); } ...

2018-10-31 · 2 min · 341 words · Hank

Spring Boot参数验证(下)——Bean Validation在Web中的应用

在 Spring Boot参数验证(上)--Bean Validation及其Hibernate实现 一篇中,我们介绍了验证标准Bean Validation和其Hibernate实现,在本篇,我们看看它们是如何应用在Spring Boot Web项目中。 1. Spring Validator 其实,Spring很早就有了自己的Bean验证机制,其核心为Validator接口,表示校验器: public interface Validator { // 检测Validator是否支持校验提供的Class boolean supports(Class<?> clazz); // 校验逻辑,校验的结果信息通过errors获取 void validate(@Nullable Object target, Errors errors); } Errors接口,用以表示校验失败的错误信息: public interface Errors { // 获取被校验的根对象 String getObjectName(); // 校验结果是否有错 boolean hasErrors(); // 获取校验错误数量 int getErrorCount(); // 获取所有错误信息,包括全局错误和字段错误 List<ObjectError> getAllErrors(); // 获取所有字段错误 List<FieldError> getFieldErrors(); …… } 当Bean Validation被标准化过后,从Spring3.X开始,已经完全支持JSR 303(1.0)规范,通过Spring的LocalValidatorFactoryBean实现,它对Spring的Validator接口和javax.validation.Validator接口进行了适配。 1.1. 全局Validator 全局Validator通过上述的LocalValidatorFactoryBean类来提供,只要使用@EnableWebMvc即可(Xml配置开启<mvc:annotation-driven>),也可以进行自定义: @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public Validator getValidator(); { // return "global" validator } } ...

2018-10-12 · 3 min · 624 words · Hank

Spring boot全局异常处理和自定义异常页面

Spring boot提供了默认的异常处理机制,但是难以满足业务需要,一般需要编码来实现自己的业务处理机制。在本篇,将介绍如何自定义异常页面,和进行全局的异常处理。 1. 全局异常处理 如果系统业务处理发生异常,我们希望能够将异常信息以友好的形式返回给调用方,而不是仅在后台记录日志,尤其是在开发RESTFul的API时,需要将业务异常信息进行明确定义并返回给API调用方,这显得尤为重要。 现在,我们来定义一个全局的业务异常类BusinessException,如果业务处理失败抛出该类或者其子类,然后编写一个全局异常处理器,将异常转换为有好的信息并返回。 1、定义异常类 public class BusinessException extends Exception { public BusinessException() { super(); } public BusinessException(String msg) { super(msg); } public BusinessException(Throwable cause) { super(cause); } public BusinessException(String message, Throwable cause) { super(message, cause); } public BusinessException(MsgDefinition msgDefinition) { super(msgDefinition.msgOf()); this.msgDef = msgDefinition; } public MsgDefinition msgDef() { return msgDef == null ? MsgDefinition.UNKOWN_ERROR : msgDef; } } 其中的MsgDefinition为具体的错误信息,包含错误码code和错误提示信息msg,子类继承该类进行扩展: public class MsgDefinition { public static final MsgDefinition SUCCESS = new MsgDefinition("0000", "请求成功"); public static final MsgDefinition EMPTY_ARGUMENTS = new MsgDefinition("4001", "请求参数为空"); public static final MsgDefinition ILLEGAL_ARGUMENTS = new MsgDefinition("4002", "请求采参数非法"); public static final MsgDefinition FILE_SIZE_OVER_LIMIT = new MsgDefinition("4301", "文件大小超过限制"); public static final MsgDefinition FILE_NUMBER_OVER_LIMIT = new MsgDefinition("4302", "文件数量超过限制"); public static final MsgDefinition FILE_FORMAT_UNSUPPORTED = new MsgDefinition("4310", "文件格式不支持"); public static final MsgDefinition UNKOWN_ERROR = new MsgDefinition("9999", "系统未知异常"); private String code; private String msg; public MsgDefinition(String code, String msg) { this.code = code; this.msg = msg; } public String codeOf() { return this.code; } public String msgOf() { return this.msg; } @Override public String toString() { return JsonUtil.toJson(this); } } ...

2018-10-10 · 2 min · 383 words · Hank

Spring Boot参数验证(上)——Bean Validation及其Hibernate实现

通常,web项目都需要对请求的参数进行校验,一般是前端JavaScript需要校验,然后后台还需要再进行校验,而校验逻辑大多数是重复的。如果在后台通过硬编码的方式,对Controller参数进行逐个校验,将会非常耗时和低效的,尤其是在使用Spring Boot和Spring Cloud的微服务体系中,大多数服务均会对外提供RESTFul的api,如果有统一的验证机制和框架,将大大提高生产力。幸运的是,Java已经提供了标准的验证API,称为Bean Validation。 1. 简介 1.1. Bean Validation Bean Validation,是JCP(Java Community Process)定义的标准化的JavaBean校验API,基于注解,并且具有良好的易用性和扩展性,1.0版本定义为 /attachment/20181009/28f52319fd84452ba22b490ff911fd04.pdf[JSR 303],而现在发布了2.0版本,定义为 JSR 380,了解详细信息可以看 这里。 Bean Validation并不局限于应用程序的某一层或者哪种编程模型, 它可以被用在任何一层, 也可以是像Swing这样的富客户端程序中. 目标:简化Bean校验,将以往重复的校验逻辑进行抽象和标准化,形成统一API规范; 版本变化:JSR 303在2009发布了1.0Final版,而最新的是在2017年发布的Bean Validation 2.0,被定义为 JSR 380。 需要注意的是,Bean Validation只是一个规范和标准,并没有提供实现,而接下来介绍的hibernate validator就是它的一种实现。 1.2. Hibernate Validator 是JSR 380的一种标准实现,同时还对其进行了扩展,如增加了部分验证约束。目前,最新的稳定版本为 6.0.13.Final。 2. 入门示例 接下来,我们使用Spring Boot来编写一个简单的示例工程。如果对Spring Boot不熟悉的可以看这几篇文章: "Spring Boot之基础入门" "SpringBoot-工程结构、配置文件以及打包" "Spring Boot JPA使用详解" 1、添加maven依赖 前边说过,JSR 303定义了JavaBean的验证标准,而Hibernate Validator是它的一种实现,所以,这两个的jar包我们都需要添加到工程。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> 其实,spring-boot-starter-web启动器已经依赖了hibernate-validator,而后者很明显会依赖validation-api。 ...

2018-10-10 · 8 min · 1686 words · Hank

Spring Cloud服务注册中心Eureka高可用

在 上一篇,我们通过编码,分别编写了服务注册中心、服务提供者、服务消费者,但是服务注册中心仍然是单节点,如果服务注册中心不可用,则会导致整个体系的所有服务都不可用。所以,有必要保证服务注册中心的高可用,本章将来讨论整个问题。 1. Eureka集群 对于Eureka而言,其本身是支持多节点集群的。其原理大致如下:服务注册中心的多个实例彼此互相注册,形成注册中心的集群,同时对外提供服务,即使其中一个挂掉,其他实例仍然可以对外提供服务,从而保证了高可用。 Figure 1. Eureka双节点结构示意图 以双节点为例,如上图所示,两个服务注册中心(Eureka Server)彼此相互注册形成集群,服务提供者(Service Provider)会自动向两者都进行服务注册,即是说,两个服务注册中心有用相同的服务实例列表,服务消费者(Service Consumer)会采用负载均衡算法从服务注册中心集群中选择出一个可用的服务注册中心,来获取服务注册实例,并完成请求。 上一篇已经说过,默认情况下,每一个Eureka服务端同样也是一个Eureka客户端,因此至少需要配置一个其他的Eureka服务端URL来让自己完成注册。如果不提供这个服务端URL,服务端本身可以正常运行和提供服务,但是控制台会打印许多无法成功注册的日志信息。在上一篇在编码单节点服务注册中心时,我们做了如下配置,让其不作为客户端让其他服务注册中心注册: eureka.client.register-with-eureka=false eureka.client.fetch-registry=false 这两项配置默认均为true,即是说eureka默认是开启集群功能的。 2. 编码实战 接下来,我们参照上一篇的服务注册中心、服务提供者、服务消费者代码,来组建一个双节点的服务注册中心,并通过服务消费者获取服务实例,完成远程请求。 2.1. 服务注册中心 1、新建一个名为02-eureka-server-nodes的Spring Boot工程,作为服务注册中心 2、引入如下依赖和插件: <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> 引入spring-boot-maven-plugin的目的是为了打可执行jar包,由于要组建双节点集群,需要启动两次,所以不能直接在idea里边运行工程了,需要打包成可执行jar,通过命令行来启动。 3、启动类添加@EnableEurekaServer注解,表明该工程为Eureka服务端 4、配置多环境,新建三个配置文件:application.properties、application-node1.properties、application-node2.properties,分别进行如下配置: application.properties: spring.application.name=eureka-server # 开发用,关闭服务端自我保护,防止关闭的实例不能被剔除 eureka.server.enable-self-preservation=false application-node1.properties: server.port=8080 eureka.instance.hostname=eureka-node1 # 服务注册中心访问地址 eureka.client.service-url.defaultZone=http://eureka-node2:8081/eureka application-node2.properties: server.port=8081 eureka.instance.hostname=eureka-node2 # 服务注册中心访问地址 eureka.client.service-url.defaultZone=http://eureka-node1:8080/eureka 需要注意的是,eureka-node1需要向eureka-node2来注册服务,而eureka-node2需要向eureka-node1来注册服务,彼此交叉注册,注意配置地址的不同。 5、由于在本机测试,并且这里通过hostname来访问服务,所以需要让hostname可以访问,修改hosts文件进行本机映射: 127.0.0.1 eureka-node1 127.0.0.1 eureka-node2 各操作系统修改hosts文件的方式不同,请自行查阅相关资料。 6、到这里编码工作就已完成,现在,使用maven命令进行打包: ``mvn clean package`` ...

2018-08-16 · 1 min · 202 words · Hank

Spring Cloud服务注册中心Eureka

上一篇,我们学习了Spring Cloud配置集成化组件Config,在本篇,我们来学习Spring Cloud微服务架构的一个核心组件----Eureka。 1. 服务治理 服务治理,即将体系中各个微服务统一纳入服务注册中心,由注册中心维护各个微服务的名称、地址、端口、状态等信息,并提供服务自动化注册和发现机制。 为什么需要服务治理? 对于传统应用而言,应用数量少,各应用间的API调用我们可以通过在配置文件中写入IP地址和端口的方式来进行维护。那么对于现在的微服务架构而言,应用的服务数量上百甚至上千,如果还使用传统的配置方式来维护各个服务信息及其其调用管理,那么无疑是困难重重,不仅容易出错,而且成本直线上升。 <p style="text-align: center;">没有服务治理时,如何管理各个服务? <p style="text-align: left;">在微服务体系中,服务实例的网络位置都是动态分配的,而且实例的地址可能会经常性的改变,有了服务治理,不再需要人为的维护各个服务信息,一切交给服务注册中心来自动管理和维护,大大提高了效率。 几个核心概念 服务注册表:维护了服务实例的网络地址、名称等信息的数据库 服务提供者:提供RESTApi以供其他服务调用的被调用方 服务消费者:调用服务端提供的RESTApi接口,发起服务请求的请求方 服务注册:将服务注册表中提供服务信息进行注册服务的行为 服务发现:从服务注册表中查询服务实例信息的行为 接下来,我们看看服务发现的两种模式。 服务发现模式 服务发现主要有两种模式:服务端发现模式和客户端发现模式。 1、客户端发现模式 即由客户端来获取服务实例列表和网络位置,并且进行负载均衡。客户端查询服务注册表(一个维护了服务实例和其网络位置等信息的数据库),获取服务实例列表,并且采用负载均衡算法,从列表中选择一个服务实例作为请求目标。 Figure 1. 客户端发现模式示意图 Eureka采用的就是客户端发现模式。 2、服务端发现模式 即由服务端来发现服务实例并进行负载均衡:客户端通过负载均衡器向某个服务提出请求,负载均衡器查询服务注册表,并将请求转发到可用的服务实例。 Figure 2. 服务端发现模式示意图 AWS Elastic Load Balancer(ELB)采用的是服务端发现模式。 本篇讲解的Spring Cloud Eureka采用的是客户端发现模式,接下来我们看看eureka的基本概念。 2. Eureka简介 Eureka,字面意思即"发现"之意,是Netflix下开源的服务治理(注册、发现等)中间件。Spring Cloud Eureka在其基础上进行了二次封装,添加了自动化配置等功能,使其成为Spring Cloud微服务体系中核心的服务治理方案之一。 Eureka包括三个角色,如下图所示: Figure 3. Eureka基础架构(图片来源网络) Eureka Server:服务注册中心,维护了服务注册表,提供服务注册、服务发现、刷新和取消注册等功能 Service Provider:服务提供者,对外提供服务,一般为RESTApi接口 Service Consumer:服务消费者,调用服务提供者接口,实现具体业务逻辑 由上图可见,Service Provider将注册中心注册服务,Service Consumer从注册中心查询服务注册表,获取服务实例信息,并通过负载均衡算法获取到一个服务提供者实例,并发起调用请求。 Eureka Server并没有后端存储机制,注册表中的所有服务实例必须发送心跳监测以保证正确连接(内存中实现),服务实例(客户端)会将服务端注册表拉去到本地并缓存在内存中,而不是每次都向服务端发送请求来获取服务实例注册信息。 前边介绍了Eureka和服务治理相关的理论,接下来,我们来编程实现服务注册中心。 3. 单节点服务注册中心 3.1. 编写服务注册中心 1、新建名为01-eureka-server的Spring Boot工程,作为服务注册中心 2、引入如下依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> ...

2018-08-15 · 2 min · 247 words · Hank

Spring Cloud Config之配置集成化

在 Spring Cloud简介中,我们了解了Spring Cloud的基本概念、特点、各大组件和其作用,还介绍了Spring Cloud的版本定义和发布规则,对Spring Cloud应该有了一定的了解。在本篇,我们来看看实现配置集成化的重要组件----Spring Cloud Config。 1. 简介 Spring Cloud Config是一款配置集中管理的组件,它提供了分布式系统的客户端和服务端外部化配置支持,由git(也支持svn、本地文件系统等)存储库支持的集中式外部配置管理,实现了配置的外部化存储。 Spring Cloud Config有几个核心的概念: 配置仓库:集中存储配置文件的地方, 推荐git仓库,也可以使用svn仓库或者本地文件系统; Server:配置管理服务端,提供配置文件管理服务,通过server可以按照一定规则读取配置文件,它直接访问配置仓库; Client:配置客户端,即需要从Server读取配置的应用,它并不直接访问配置仓库,而是通过Server读取配置信息; git仓库本身使用频率高,可以进行版本跟踪等特点,所以spring推荐使用git仓库,本文也将重点介绍基于git仓库的配置中心。 2. 配置服务端 服务端支持的配置仓库有git、svn和native本地文件,spring.profiles.active配置项用于启用配置文件支持的配置仓库,默认为git。在使用git配置仓库有几个重要概念: application: 访问配置服务的名称,默认为application,可以通过spring.config.name配置 profile:激活的profile配置文件,多个以逗号分隔 label:通常为git的分支,默认为master,也可以为commit id、tag名称;如果分支或tag名称包含斜线“/”,那么在HTTP URL中需要更换为"(_)" 下边先简单介绍使用本地文件和本地git仓库搭建配置服务端。 2.1. 使用本地文件 1、新建config-server-native工程,引入依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> 2、启动类加上@EnableConfigServer注解,表明该工程为配置服务端 3、新建本地配置仓库文件夹: ``mkdir /Users/sun/Desktop/belonk/config-native`` 在config-native文件夹中建三个文件:application-dev.properties,application-uat.properties,application-pro.properties,分别配置部分内容以区别不同环境(profile)。 4、application.properties添加如下配置: spring.profiles.active=native # 采用推荐的8888端口 server.port=8888 spring.config.name=config # 配置本地文件目录地址 spring.cloud.config.server.native.searchLocations=/Users/sun/Desktop/belonk/config-native spring.cloud.config.server.native.addLabelLocations=true # 配置本地git仓库地址 #spring.cloud.config.server.git.uri=file:///Users/sun/Desktop/belonk/config-center/ spring.profiles.active:默认为git,改为native启用本地文件系统配置仓库 spring.config.name:访问URL的application名称 spring.cloud.config.server.native.searchLocations:配置本地搜索配置文件的位置,默认包括[classpath:/, classpath:/config, file:./, file:./config] 5、启动应用,浏览器访问http://localhost:8888/config/pro,dev,可以看到显示了正确pro和dev的配置信息,需要注意的是这里的config来自spring.config.name配置。 由于本地文件系统没有版本控制,不便于管理和追述,建议仅仅用于测试,不建议生产上使用。 2.2. 使用本地git仓库 git仓库在本地文件系统,并未push到远端,可用于快速测试。我们在config-server-native工程上稍作修改,改为本地git仓库。 ...

2018-08-14 · 2 min · 380 words · Hank

Spring Cloud简介

1. 简介 微服务的实施,涉及的内容非常多,包括服务治理、配置中心、服务网关、服务熔断、分布式事务、集群、消息总线等等,各个部分开源的产品也非常多: 服务治理:阿里的Dubbo以及当当维护的DubboX、Netflix的Eureka、Apache的Consul等; 配置中心:Spring Cloud的Config、百度的Disconf、Netflix的Archaius、淘宝的Diamond等; 服务跟踪:Spring Cloud的Sleuth、Twitter的Zipkin等 服务网关:Netflix的Zuul等 …… 开源组件如此众多,在实施微服务时,势必带来技术选型方面的困扰,不仅要了解各个技术的作用、易用性、优缺点,还需要知道其他组件对其支持程度,以及组件之间的依赖、协作关系,因此,很多公司在一开始就心生畏惧甚至直接放弃。 为了解决上述问题,Spring Cloud应运而生。它不单独解决微服务某一方面的问题,而是提出了微服务一整套解决方案。Spring Cloud整合了很多开源的微服务产品,针对微服务某一方面的问题,它提供了默认的实现,从而极大的简化了微服务实施的技术难度,使得技术人员不必再关注技术选型方面的问题,大大节约了成本。 简言之,Spring Cloud是一套构建于Spring boot之上的微服务框架,它整合了大部分微服务实施所需的组件,并提供了默认实现,使得复杂的微服务实施变得简单化。官网地址: http://projects.spring.io/spring-cloud/ 2. 特点 Spring Cloud专注于为典型用例提供良好的开箱即用体验,并为其他用户提供可扩展性机制。 分布式/版本化配置 服务注册和发现 路由 服务间调用 负载均衡 断路器 分布式消息 3. 组件 Spring Cloud整合了微服务架构所需的大部分开源组件,包括: Spring Cloud Config 即配置管理工具,由git(也支持svn、本地文件系统等)存储库支持的集中式外部配置管理,实现了配置的外部化存储。 Spring Cloud Netflix 核心组件,整合了Netflix OSS开源的组件,包括许多子组件: Eureka:服务治理组件,包括服务注册、发现,对Netflix的Eureka进行整合; Hystrix:容错管理组件,断路器,对Netflix的Hystrix进行了整合; Ribbon:客户端负载均衡,也是Netflix的开源组件,Spring Cloud进行了整合; Feign:基于Ribbon和Hystrix的声明式服务调用,整合Netflix的Feign; Zuul:网关组件,智能路由、访问过滤,整合Netflix的Zuul; Archaius:外部化配置组件,整合netflix的Archaius; Spring Cloud Bus 轻量级消息总线,用于集群间通信,传播集群间状态改变,例如动态更新配置。 Cloud Foundry 将您的应用程序与Pivotal Cloud Foundry集成。提供服务发现实现,还可以轻松实现受SSO和OAuth2保护的资源。 Spring Cloud Open Service Broker 提供构建实现Open Service Broker API的服务代理的起点。 Spring Cloud Cluster 基于Zookeeper,Redis,Hazelcast,Consul的选举和通用状态模式的抽象实现。 Spring Cloud Consul Hashicorp Consul的服务发现和配置管理。 Spring Cloud Security 为Zuul代理中的负载平衡OAuth2 rest客户端和身份验证头中继提供支持。 Spring Cloud Sleuth Spring Cloud应用程序的分布式跟踪,兼容Zipkin,HTrace和基于日志(例如ELK)的跟踪。 Spring Cloud Data Flow 组合微服务应用程序的本地云编排服务,提供了易用的DSL、拖放式GUI和REST-API共同简化了基于数据管道的微服务整体编排。 Spring Cloud Stream 轻量级事件驱动的微服务框架,可快速构建可连接到外部系统的应用程序。采用声明式模型,在Spring Boot应用程序之间使用Apache Kafka或RabbitMQ发送和接收消息。 Spring Cloud Stream App Starters 基于Spring Boot的Spring应用程序集成,可提供与外部系统的集成。 Spring Cloud Task 一种短命的微服务框架,用于快速构建执行有限数据处理的应用程序。用于向Spring Boot应用程序添加功能和非功能功能的简单声明。 Spring Cloud Task App Starters Spring Cloud Task App Starters是Spring Boot应用程序,可能是任何进程,包括不能永久运行的Spring Batch作业,它们在有限的数据处理期后结束/停止。 Spring Cloud Zookeeper 使用Apache Zookeeper进行服务发现和配置管理。 适用于Amazon Web Services的Spring Cloud 与托管的Amazon Web Services轻松集成。它提供了一种使用众所周知的Spring习语和API(如消息传递或缓存API)与AWS提供的服务进行交互的便捷方式。开发人员可以围绕托管服务构建应用程序,而无需关心基础结构或维护。 Spring Cloud Connectors 使各种平台中的PaaS应用程序可以轻松连接到数据库和消息代理(该项目以前称为“Spring Cloud”)等后端服务。 Spring Cloud CLI Spring Boot CLI插件,用于在Groovy中快速创建Spring Cloud组件应用程序 Spring Cloud Gateway 一款基于Project Reactor的智能可编程路由器。 Spring Cloud OpenFeign 通过自动配置和Spring环境以及其他Spring编程模型习惯用法提供Spring Boot应用程序的集成。 Spring Cloud Function 通过函数促进业务逻辑的实现。它支持无服务器提供商之间的统一编程模型,以及独立运行(本地或PaaS)的能力。 ...

2018-07-20 · 2 min · 275 words · Hank

Spring Boot JPA使用详解

在上一篇SpringBoot-工程结构、配置文件以及打包中,我们介绍了Spring Boot项目的工程结构、基本配置、使用IDEA开发的配置,以及可执行jar包的结构和打包方式,对Spring Boot的项目有了整体的认识。在本篇,我们将介绍JPA的使用。 1. 简介 百度百科对JPA的解释是这样的: JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 JPA由EJB 3.0软件专家组开发,作为JSR-220实现的一部分。但它又不限于EJB 3.0,你可以在Web应用、甚至桌面应用中使用。JPA的宗旨是为POJO提供持久化标准规范。Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现。 简单来说,Hibernate这种ORM的持久化框架的出现极大的简化了数据库持久层的操作,随着使用人数的增多,ORM的概念越来越深入人心。因此,JAVA专家组结合Hibernate,提出了JAVA领域的ORM规范,即JPA。 JPQL 其实同Hibernate的HQL类似,是JPA标准的面向对象的查询语言。百度百科的解释如下: JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 JPA由EJB 3.0软件专家组开发,作为JSR-220实现的一部分。但它又不限于EJB 3.0,你可以在Web应用、甚至桌面应用中使用。JPA的宗旨是为POJO提供持久化标准规范。Hibernate3.2+、TopLink 10.1.3以及OpenJPA都提供了JPA的实现。 JPA与Hibernate的关系 JPA是参考Hibernate提出的Java持久化规范,而Hibernate全面兼容JPA,是JPA的一种标准实现。 JPA与Spring Data JPA Spring Boot对JPA的支持其实使用的是Spring Data JPA,它是Spring对JPA的二次封装,默认实现使用的是Hibernate,支持常用的功能,如CRUD、分页、条件查询等,同时也提供了强大的扩展能力。 2. HelloWorld 僚机了JPA的概念过后,接下来,我们使用Spring Boot工程来实现一个最简单的CRUD操作,看看使用JPA我们需要做哪些事情。 引入依赖 直接引入如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> ...

2018-06-27 · 7 min · 1461 words · Hank

SpringBoot-工程结构、配置文件以及打包

前一篇,我们简单介绍了SpringBoot的概念,然后逐步构建了一个HelloWorld的Web应用。现在,我们来整体认识一下SpringBoot,包括:工程结构、配置文件、可执行Jar包等。 1. 工程结构 本质上而言,SpringBoot是一个遵循Maven标准的Maven工程,但是如果开发的是Web项目,与标准Maven存在一定的差别:默认情况下,SpringBoot打包为jar(简单方便,使用自带容器,不需要部署到tomcat等java容器,当然也可以打包为可执行war),此时,不能使用Maven的webapp目录来存放web资源(js、css、图片、页面等),SpringBoot有一套自己的规范,整个工程结构如下: \---src +---main | +---java // java后台代码 | \---resources // 资源文件目录 | application.properties // 配置文件 | +---static // 存放静态文件,如js、css、图片等 | \---templates // 存放模板文件 \---test \---java // 测试代码 默认情况下,Spring boot规定将静态资源存放到resources/static目录,而页面存放到resources/templates目录,也可以通过配置文件修改默认存放目录。 2. 代码结构 Spring Boot推荐的后台代码结构如下: +---src | +---main | | +---java | | | \---com | | | \---belonk | | | | WebappViewStarter.java // 包含main方法的启用类 | | | +---config | | | | CustomParamConfig.java | | | | RandomParamConfig.java | | | +---domain | | | +---service | | | \---web | | | HomeController.java ...

2018-06-07 · 3 min · 505 words · Hank

Spring Boot之基础入门

最近在了解微服务和Spring Cloud相关的东西,Spring Cloud的微服务体系基于Spring boot,所以了解Spring Boot是前提和基础。本文将介绍Spring Boot基础内容,包括简介、环境搭建、示例代码等,所使用JDK版本为JDK8,构建工具为Maven3.5。 1. 简介 Spring Boot是Spring的开源项目,其目的是让开发者可以简单、快速地使用Spring框架创建生产级的应用,其目的不是为了替换Spring,而是简化Spring的使用。Spring Boot可以看做是对Spring框架的二次封装,不过,在封装的同时也还提供了许多高级功能。 Spring Boot特性如下: 创建独立的Spring应用程序 直接嵌入Tomcat、Jetty或Undertow(无需部署WAR文件) 为项目构建提供了许多的“starter”,适用于整合不同的框架,从而简化您的构建工具(Maven)配置 尽可能自动配置Spring 提供可生产的特性,如度量指标、健康检查和集中配置 绝对没有代码生成,也不需要XML配置 2. 系统需求 Spring Boot需要JDK8或以上版本,嵌入的tomcat8.5所支持的Servlet版本为3.1,如下表所示: 依赖 版本 JDK 8或以上版本 Maven 3.2或以上版本 Servlet 3.1+ 3. 构建应用 现在,我们来一步步构建一个Hello world应用。 3.1. 构建 Spring Boot项目是基于Maven构建,完全遵循Maven的项目构建规则和目录规范。所以,你可以直接使用开发工具或者maven命令行来创建Spring Boot项目。需要注意的饿是,如果是Web项目,而且打包的格式为jar,那么与标准Maven项目不同的是不能使用src/main/webapp目录。 也可以访问 https://start.spring.io/来快速构建应用,选择对应的构建工具、语言和版本信息,填写开发信息并加入依赖下载,然后导入开发工具即可: 3.2. 配置Pom.xml 应用创建好了,我们来配置pom.xml,引入Spring boot相关的依赖。 1、继承parent节点 Spring为我们提供了顶层的parent节点,用来定义Spring Boot使用的版本,配置如下: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> </parent> ...

2018-05-22 · 2 min · 327 words · Hank

Spring事务管理四:声明式事务

编程式事务虽然可以精确控制事务,但是事务控制代码必须侵入业务逻辑代码中,耦合度高,后期难以维护。一般而言,不需要精确控制事务,所以采用的更多的是Spring的声明式事务。 Spring声明式事务基于AOP实现,有两种事务定义方式:xml配置和注解定义,前者使用tx命名空间,后者使用@Transactional注解。 1. 事务属性 在定义事务之前,需要了解一些事务的参数,正如前边TransactionDefinition类定义的,包括传播机制、隔离级别、是否只读、事务超时等,还包括回滚规则定义等参数。 1.1. 传播机制 传播机制(propagation)定义了客户端与被调用方法之间的事务界限。简单而言,就是一个方法调用其他一个或多个方法来实现业务逻辑时,这些方法间的事务如何进行传播,这就由传播机制来决定。 Spring提供了7中传播机制,如下表所示: Table 1. 事务的7种传播机制 传播行为 说明 REQUIRED 业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务 NOT_SUPPORTED 声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行 REQUIRES_NEW 属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行 MANDATORY 该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出异常。 SUPPORTS 这一事务属性表明,方法可以受事务控制,也可以不。如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行 NEVER 指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行 NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效 注意REQUIRES_NEW和NESTED两者的区别; PROPAGATION_REQUIRES_NEW启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. ...

2018-03-08 · 2 min · 401 words · Hank

Spring事务管理三:编程式事务

前边提到,编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,它可以实现细粒度的事务控制。 1. TransactionTemplate Spring提供了TransactionTemplate对象来控制事务,它使用了一种回调机制,通过回调来修改事务状态,继承关系如下: TransactionOperations接口定义了基础的事务的执行操作execute(),该接口便于后续扩展,一般不直接使用。 在使用TransactionTemplate之前,需要声明bean: <!--JDBC事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--编程式事务配置--> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> TransactionTemplate使用PlatformTransactionManager的实现来管理事务,这里注入的是JDBC的事务管理器。事务控制代码如下: public void add(User user) throws Exception{ // Spring编码式事务,回调机制 transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { try { userMapper.insertSelective(user); } catch (Exception e) { // 异常,设置为回滚 status.setRollbackOnly(); throw e; } return null; } }); } 调用TransactionTemplate的execute()方法,传递一个TransactionCallback接口的实现,执行其doInTransaction()方法,该方法会回传事务状态对象TransactionStatus,如果有异常,则调用status.setRollbackOnly()将事务状态标记为回滚,否则doInTransaction方法正常返回,事务则会提交。 如果事务控制的方法不需要返回值,那么可以使用TransactionCallback接口的抽象实现类TransactionCallbackWithoutResult: @Override public void add(User user) throws Exception { // Spring编码式事务,回调机制 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { try { userMapper.insertSelective(user); } catch (Exception e) { // 异常,设置为回滚 status.setRollbackOnly(); throw e; } } }); } ...

2018-03-08 · 1 min · 103 words · Hank

Spring事务管理二:Spring事务管理器

前边简单介绍了事务的概念和其ACID特性,现在让我们看看Spring是如何实现对事务的支持的。 1. Spring事务管理器结构 Spring并不直接管理事务,而是提供多种事务管理器,将管理事务的责任委托给JTA或相应的持久性机制所提供的某个特定平台的事务实现。 Spring对事务管理器的抽象: Spring提供了顶层接口PlatformTransactionManager,并提供了扩展接口ResourceTransactionManager和抽象实现类AbstractPlatformTransactionManager。PlatformTransactionManager下的所有接口和实现类如图所示: 一般而言,每一种事务实现的类图如下: PlatformTransactionManager是Spring事务管理器的核心,接口定义如下: public interface PlatformTransactionManager { // 返回一个已经激活的事务或创建一个新的事务(根据给定的TransactionDefinition类型参数定义的事务属性), // 返回的是TransactionStatus对象代表了当前事务的状态,其中该方法抛出TransactionException(未检查异常) // 表示事务由于某种原因失败。 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交给定的事务,检查其状态。如果事务被标记为rollback-only,则执行回滚。 // 如果事务不是新的事务,则忽略提交周围适当的事务。如果先前的事务被挂起,则在提交新事务后恢复先前的事务。 void commit(TransactionStatus status) throws TransactionException; // 执行给定事务的回滚。如果事务不是一个新的事务,将其周边适当的事务标记为rollback-only。 // 如果先前的事务被挂起,则在回滚新事务后恢复先前的事务。 void rollback(TransactionStatus status) throws TransactionException; } TransactionDefinition接口定义如下: public interface TransactionDefinition { // 返回事务传播行为 int getPropagationBehavior(); // 返回事务隔离级别 int getIsolationLevel(); // 返回事务超时时间 int getTimeout(); // 返回事务是否只读 boolean isReadOnly(); // 返回事务的名称 String getName(); } ...

2018-03-07 · 1 min · 200 words · Hank

Spring事务管理一:Spring事务简介

1. 定义 在软件开发领域, 全有或全无的操作被称为事务(transaction)。 事物允许将几个操作组合成一个要么全部发生要么全部不发生的工作单元。发生和不发生两者只能选择其一,而不可能两者都选择。事务确保了数据和资源免于处在不一致的状态。 让我们继续使用最简单有效的转账例子来说明事务: A给B转100元,这个过程大概需要两步: step1、从A的账户扣除100元; step2、给B的账户增加100元。 这两步必须要么全部执行成功要么都不成功并且还原账户的金钱为初始值,否则转账失败。如果step1成功而step2失败,那么A账户少了100元,但是B却没收到钱;如果step1失败而step2成功,B账户会多出100元。这两种情况都是不允许发生的。 所以,step1和step2必须处于同一事务中,我们就说事务把这两个操作组合为要么都成功要么都失败的工作单元。整个操作成功,那么转账成功,结果是A账户扣了100元而B账户多了100元;如果整个过程失败,那么A和B的账户的金额恢复到转账前的状态,即未发生任何变化,就像转账操作从未发生过一样。 2. ACID特性 Atomic(原子性):确保所有操作要么都发生,要么都不发生。 Consistent(一致性):事务前后,数据均处于正确状态,数据不应该被破坏。 Isolated(隔离性):事务彼此隔离,用户间操作不相混淆。 Durable(持久性):事务一旦完成,结果应该持久化,例如保存到数据库中。 上边转账的例子中,两步操作必须要么成功要么都失败,确保了原子性;而原子性保证账户中的金额不会处于不一致或者说部分完成,保证了一致性;转账过程与其他转账过程互不影响,遵循隔离性;转账成功后,结果必须持久化保存,防止事务结果丢失。 3. Spring对事务管理的支持 Spring提供两者事务管理方式:编程式事务管理和声明式事务管理。 3.1. 编程式事务管理 编写程序式的事务管理可以清楚的定义事务的边界,可以 实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。 3.2. 声明式事务管理 如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中, 解除了和代码的耦合, 这是 对应用代码影响最小的选择,从这一点再次验证了Spring关于 AOP的概念。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置。 Spring通过回调机制将实际的事务实现从事务性代码中抽象出来。如果应用只使用一种持久化资源,那么可以使用持久化机制本身的事务性支持(JDBC、Hibernate、JPA等);如果应用事务跨多个资源,那么Spring会使用第三方的JTA(JAVA事务API)实现来支持分布式(XA)事务。 使用声明式事务还是编程式事务管理,在很大程度上是 细粒度和易用性之间权衡。一般情况下,不需要精确控制事务,采用声明式事务不仅易用性高,而且降低了事务与其他业务代码的耦合性。 4. 总结 在软件开发领域,全有或全无的操作被称为事务。事务必须具备ACID特性,在Spring中,除了编程来精确控制事务,还可以使用AOP来配置低耦合度的声明式事务控制。

2018-03-07 · 1 min · 38 words · Hank