亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式

 更新時(shí)間:2022年03月17日 12:00:33   作者:TGITCIC  
本文主要介紹了阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

在網(wǎng)上很多關(guān)于dubbo異常統(tǒng)一處理的博文,90%都是抄來(lái)抄去。大多都是先上一段dubbo中對(duì)于異常的統(tǒng)一處理的原碼,然后說(shuō)一堆的(甚至有12345,五種)不靠譜方案,最后再說(shuō)“本篇使用的是方案4”,然后再對(duì)所謂的方案4寫(xiě)了一段文字,最后還說(shuō)不清?。?!

本篇解決方案不會(huì)那么羅里吧嗦也不會(huì)貼dubbo源碼來(lái)湊字?jǐn)?shù),我就直接從剛結(jié)束不久的雙11保衛(wèi)戰(zhàn)性能全鏈路優(yōu)化中我們的面對(duì)10萬(wàn)級(jí)別TPS的方案中提取的代碼來(lái)說(shuō)明這個(gè)dubbo統(tǒng)一處理異常是怎么個(gè)處理方式吧!

1. 為什么要拋異常?

不同開(kāi)發(fā)團(tuán)隊(duì)間便于追溯異常的來(lái)源以及為了便于定位問(wèn)題的需要

往往實(shí)際開(kāi)發(fā)中的架構(gòu)是這么一個(gè)樣子的:

dubbo微服務(wù)架構(gòu)簡(jiǎn)圖

不同層的開(kāi)發(fā)人員都是不同的人或者是不同的幾波人馬;

無(wú)狀態(tài)的API層(一組Tomcat對(duì)Nginx Web層的API暴露)是一組開(kāi)發(fā)團(tuán)隊(duì);

微服務(wù)Dubbo層是另一組開(kāi)發(fā)團(tuán)隊(duì);

在調(diào)試、測(cè)試、上線后我們經(jīng)常會(huì)發(fā)生各種Exception,此時(shí)這幾個(gè)不同的開(kāi)發(fā)團(tuán)隊(duì)間會(huì)互相扯皮、打架,并且大家都要忙于定位這個(gè)Exception到底是發(fā)生在哪一層,甚至需要追溯Exception發(fā)生在哪個(gè)點(diǎn)(stackTrace)。

Service層有數(shù)據(jù)庫(kù)事務(wù)一致性的問(wèn)題必須拋出異常

我們都知道在spring中的Service層必須拋出Runtime Exception,否則Service層的方法如果有涉及數(shù)據(jù)庫(kù)的修改操作是不會(huì)回滾的。

2. 給出解決方案

其實(shí)解決方案真正的無(wú)外乎就2種:

  • provider向遠(yuǎn)程consumer層直接拋RuntimeException即可;
  • provider端把所有的Exception進(jìn)行統(tǒng)一包裝,向consumer端返回json報(bào)文體的類(lèi)似message:xxx,code:500,data{xxx:xxx,xxx:xxx}這樣的消息而在provider端進(jìn)行“logger.error”的記錄即可;

本文把這2種實(shí)現(xiàn)方式都給實(shí)現(xiàn)了,下面開(kāi)始直接show me the code的方式來(lái)說(shuō)話吧。

3. 兩種拋異常的實(shí)例解說(shuō)

環(huán)境搭建

nacos1.1.4

我們這邊不用dubbo admin,因?yàn)閐ubbo admin太老且使用不方便,缺少了很多管理微服務(wù)所需要的基本功能。并且dubbo從2.6開(kāi)始已經(jīng)把dubbo admin從它的主工程里分離了出去,同時(shí)dubbo2.6開(kāi)始支持nacos registry了。

目前來(lái)說(shuō)nacos是最方便、效率最高、功能最強(qiáng)大的微服務(wù)發(fā)現(xiàn)組件(甚至支持spring cloud)。

下載地址在這里(請(qǐng)戳):阿里nacos最新下載地址

下載后直接解壓,然后進(jìn)行nacos配置

編輯這個(gè)application.properties文件,我們把nacos自動(dòng)服務(wù)發(fā)現(xiàn)管理端連上自己開(kāi)發(fā)環(huán)境上的mysql。

# spring
spring.datasource.platform=mysql
server.contextPath=/nacos
server.servlet.contextPath=/nacos
server.port=8848
db.num=1
db.url.0=jdbc:mysql://192.168.56.101:3306/nacos?useUnicode=true&characterEncoding=utf-8&useSSL=false
db.user=nacos
db.password=111111

配完后直接雙擊:startup.cmd啟動(dòng)nacos

登錄界面中使用nacos/nacos即可進(jìn)行登錄了。

登錄后看到nacos管理界面就說(shuō)明nacos配置和啟動(dòng)成功了。接下來(lái)我們就要開(kāi)始書(shū)寫(xiě)dubbo的provider端與consumer端了。

dubbo工程搭建

nacos-parent工程

整個(gè)工程我已經(jīng)放在git上了,地址請(qǐng)戳這里:nacos-dubbo-demo

工程的依賴(lài)結(jié)構(gòu)如下:

由于dubbo與springboot結(jié)合的項(xiàng)目不多,很多網(wǎng)上有的博客也充斥著亂抄、自己都沒(méi)有驗(yàn)證過(guò)就上代碼的,因此大多網(wǎng)友們通過(guò)網(wǎng)上之言片語(yǔ)拼湊起來(lái)的項(xiàng)目在本地很難運(yùn)行起來(lái),不是maven包沖突就是少這個(gè)、那個(gè)包。下面給出工程的parent pom文件。

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-parent</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>
	<description>Demo project for Spring Boot Dubbo Nacos</description>
	<modules>
	</modules>
 
	<properties>
		<java.version>1.8</java.version>
		<spring-boot.version>1.5.15.RELEASE</spring-boot.version>
		<dubbo.version>2.7.3</dubbo.version>
		<curator-framework.version>4.0.1</curator-framework.version>
		<curator-recipes.version>2.8.0</curator-recipes.version>
		<druid.version>1.1.20</druid.version>
		<guava.version>27.0.1-jre</guava.version>
		<fastjson.version>1.2.59</fastjson.version>
		<dubbo-registry-nacos.version>2.7.3</dubbo-registry-nacos.version>
		<nacos-client.version>1.1.4</nacos-client.version>
		<mysql-connector-java.version>5.1.46</mysql-connector-java.version>
		<disruptor.version>3.4.2</disruptor.version>
		<aspectj.version>1.8.13</aspectj.version>
		<nacos-service.version>0.0.1-SNAPSHOT</nacos-service.version>
		<skycommon.version>0.0.1-SNAPSHOT</skycommon.version>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${java.version}</maven.compiler.target>
		<compiler.plugin.version>3.8.1</compiler.plugin.version>
		<war.plugin.version>3.2.3</war.plugin.version>
		<jar.plugin.version>3.1.2</jar.plugin.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-web</artifactId>
				<version>${spring-boot.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo-spring-boot-starter</artifactId>
				<version>${dubbo.version}</version>
				<exclusions>
					<exclusion>
						<groupId>org.slf4j</groupId>
						<artifactId>slf4j-log4j12</artifactId>
					</exclusion>
				</exclusions>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo</artifactId>
				<version>${dubbo.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.curator</groupId>
				<artifactId>curator-framework</artifactId>
				<version>${curator-framework.version}</version>
			</dependency>
 
			<dependency>
				<groupId>org.apache.curator</groupId>
				<artifactId>curator-recipes</artifactId>
				<version>${curator-recipes.version}</version>
			</dependency>
			<dependency>
				<groupId>mysql</groupId>
				<artifactId>mysql-connector-java</artifactId>
				<version>${mysql-connector-java.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>druid</artifactId>
				<version>${druid.version}</version>
			</dependency>
			<dependency>
				<groupId>com.lmax</groupId>
				<artifactId>disruptor</artifactId>
				<version>${disruptor.version}</version>
			</dependency>
			<dependency>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
				<version>${guava.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>fastjson</artifactId>
				<version>${fastjson.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo-registry-nacos</artifactId>
				<version>${dubbo-registry-nacos.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba.nacos</groupId>
				<artifactId>nacos-client</artifactId>
				<version>${nacos-client.version}</version>
			</dependency>
			<dependency>
				<groupId>org.aspectj</groupId>
				<artifactId>aspectjweaver</artifactId>
				<version>${aspectj.version}</version>
			</dependency>
 
		</dependencies>
	</dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${compiler.plugin.version}</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>${war.plugin.version}</version>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>${jar.plugin.version}</version>
			</plugin>
		</plugins>
	</build>
</project>

演示用數(shù)據(jù)庫(kù)(mySQL5.7)建表語(yǔ)句

CREATE TABLE `t_product` (
  `product_id` int(11) NOT NULL AUTO_INCREMENT,
  `product_name` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`product_id`)
);
CREATE TABLE `t_stock` (
  `stock_id` int(11) NOT NULL AUTO_INCREMENT,
  `stock` int(11) DEFAULT NULL,
  `product_id` int(11) NOT NULL,
  PRIMARY KEY (`stock_id`)
);

它建了兩張表,t_product表和t_stock表。這兩張表我們會(huì)用于演示dubbo provider中對(duì)于數(shù)據(jù)庫(kù)一致性插入時(shí)在碰到Exception時(shí)怎么處理回滾的場(chǎng)景。

nacos-service工程搭建說(shuō)明

先上pom.xml(很重要,這里面的依賴(lài)是正確的springboot+dubbo+nacos客戶(hù)端的完整配置)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>nacos-service</name>
	<description>服務(wù)者 Demo project for Spring Boot dubbo nacos</description>
	<parent>
		<groupId>org.sky.demo</groupId>
		<artifactId>nacos-parent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
 
	<dependencies>
 
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-core</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-spring</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-tomcat</artifactId>
			</exclusion>
		</dependency>
 
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<dependency>
			<groupId>com.lmax</groupId>
			<artifactId>disruptor</artifactId>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>
		<!-- Dubbo Registry Nacos -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-registry-nacos</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.nacos</groupId>
			<artifactId>nacos-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.sky.demo</groupId>
			<artifactId>skycommon</artifactId>
			<version>${skycommon.version}</version>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<testSourceDirectory>src/test/java</testSourceDirectory>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<directory>src/main/webapp</directory>
				<targetPath>META-INF/resources</targetPath>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>application.properties</include>
					<include>application-${profileActive}.properties</include>
				</includes>
			</resource>
		</resources>
	</build>
</project>

然后我們?cè)O(shè)置application.properties文件內(nèi)容

這邊dubbo的部分配置是相對(duì)于我虛擬出來(lái)的模擬環(huán)境4C CPU,4GB內(nèi)存來(lái)設(shè)的,具體更多設(shè)置參數(shù)可以直接參照于dubbo官方文檔。

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.56.101:3306/mk?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=mk
spring.datasource.password=111111
 
server.port=8080
server.tomcat.max-connections=300
server.tomcat.max-threads=300
server.tomcat.uri-encoding=UTF-8
server.tomcat.max-http-post-size=0
 
#Dubbo provider configuration
dubbo.application.name=nacos-service-demo
dubbo.registry.protocol=dubbo
dubbo.registry.address=nacos://127.0.0.1:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.protocol.threads=200
dubbo.protocol.queues=100
dubbo.protocol.threadpool=cached
dubbo.provider.retries = 3
dubbo.provider.threadpool = cached
dubbo.provider.threads = 200
dubbo.provider.connections = 100
dubbo.scan.base-packages=org.sky.service
 
 
logging.config=classpath:log4j2.xml

我們可以看到要把dubbo與nacos連接起來(lái)只需要在pom.xml文件中引入

		<!-- Dubbo Registry Nacos -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-registry-nacos</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.nacos</groupId>
			<artifactId>nacos-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>

以及在application.properties文件中把相應(yīng)的dubbo協(xié)議依舊使用dubbo,這是因?yàn)閐ubbo2.6中已經(jīng)帶入了nacos-registry了,因此就必須把dubbo.registry.address設(shè)成指向你本機(jī)的nacos啟動(dòng)實(shí)例(默認(rèn)為8848端口)即可。

dubbo.registry.protocol=dubbo
dubbo.registry.address=nacos://127.0.0.1:8848

springboot的啟動(dòng)代碼,Application.java

package org.sky;
 
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
@EnableDubbo
@EnableAutoConfiguration
@ComponentScan(basePackages = { "org.sky" })
@EnableTransactionManagement
 
public class Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

有兩個(gè)重要的注解

@EnableDubbo申明該項(xiàng)目啟用dubbo的自動(dòng)注解;

@EnableTransactionManagement申明該項(xiàng)目會(huì)使用數(shù)據(jù)庫(kù)事務(wù);

把項(xiàng)目連接上數(shù)據(jù)庫(kù)

我們使用druid做數(shù)據(jù)庫(kù)的連接池。

package org.sky.config;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
 
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
 
@Configuration
@EnableAutoConfiguration
public class DruidConfig {
 
	@ConfigurationProperties(prefix = "spring.datasource")
	@Bean
	public DruidDataSource dataSource() {
 
		return new DruidDataSource();
 
	}
}

制作一個(gè)自定義的全局Exception,DemoRpcRunTimeException

把它放置于common項(xiàng)目?jī)?nèi)

package org.sky.exception;
 
import java.io.Serializable;
 
public class DemoRpcRunTimeException extends RuntimeException implements Serializable {
	public DemoRpcRunTimeException() {
	}
 
	public DemoRpcRunTimeException(String msg) {
		super(msg);
	}
 
	public DemoRpcRunTimeException(Throwable cause) {
		super(cause);
	}
 
	public DemoRpcRunTimeException(String message, Throwable cause) {
		super(message, cause);
	}
 
}

制作一個(gè)AOP, DemoRpcRuntimeExceptionHandler

用于包裝自定的異常用,它位于nacos-service項(xiàng)目中,做它會(huì)以AOP的方式注入。

package org.sky.config;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.sky.exception.DemoRpcRunTimeException;
import org.springframework.stereotype.Component;
 
@Aspect
@Component
public class DemoRpcRuntimeExceptionHandler {
	protected Logger logger = LogManager.getLogger(this.getClass());
 
	/**
	 * service層的RuntimeException統(tǒng)一處理器
	 * 可以將RuntimeException分裝成RpcRuntimeException拋給調(diào)用端處理 或自行處理
	 * 
	 * @param exception
	 */
	@AfterThrowing(throwing = "exception", pointcut = "execution(* org.sky.service.*.*(..))")
	public void afterThrow(Throwable exception) {
		if (exception instanceof RuntimeException) {
			logger.error("DemoRpcRuntimeExceptionHandler side->exception occured: " + exception.getMessage(),
					exception);
			throw new DemoRpcRunTimeException(exception);
		}
		// logger.error("DemoRpcRuntimeExceptionHandler side->exception occured: " +
		// exception.getMessage(), exception);
	}
}

開(kāi)始進(jìn)入核心provider Service端的制作。

ProductService接口

我們把它放置于common工程,這樣consumer工程也就可以通過(guò)nacos的注冊(cè)中心找到這個(gè)接口名,然后通過(guò)spring的invoke來(lái)對(duì)于遠(yuǎn)程的用于具體實(shí)現(xiàn)service邏輯的xxxServiceImpl類(lèi)進(jìn)行調(diào)用了。

package org.sky.service;
 
import org.sky.exception.DemoRpcRunTimeException;
import org.sky.platform.util.DubboResponse;
import org.sky.vo.ProductVO;
 
public interface ProductService {
	public DubboResponse addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException;
}

具體業(yè)務(wù)邏輯實(shí)現(xiàn)類(lèi),ProductServiceImpl

該類(lèi)做這么一件事:

1)插入t_product表數(shù)據(jù)

2)插入t_stock表數(shù)據(jù)

插兩張表時(shí),只要有一點(diǎn)點(diǎn)錯(cuò)誤那么整個(gè)插入事務(wù)回滾,否則成功。這邊需要注意的就是:

  • springboot service只有接到RuntimeException才會(huì)回滾;
  • 要把RuntimeException從provider遠(yuǎn)程傳遞到consumer端,包括把stackTrace這些信息也遠(yuǎn)程傳遞到consumer端,那么這個(gè)exception必須是serializable的;
  • 暴露成dubbo provider service的service方法必須加上@Service注解,這個(gè)Service可不是spring annotation的service而是ali dubbo的service,在2.7.3開(kāi)始變成了org.apache.dubbo包了。它配合著springboot的主啟動(dòng)文件中的@EnableDubbo來(lái)啟作用,它在啟動(dòng)后會(huì)通過(guò)application.properties中的dubbo.scan.base-packages中所指的路徑把這個(gè)路徑下所有的類(lèi)尋找是否帶有@Service注解,如有那么就把它通過(guò)nacos-registry給注冊(cè)到nacos中去;

ProductServiceImpl.java

package org.sky.service;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
 
import org.apache.dubbo.config.annotation.Service;
import org.sky.exception.DemoRpcRunTimeException;
import org.sky.platform.util.DubboResponse;
import org.sky.vo.ProductVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.transaction.annotation.Transactional;
 
@Service(version = "1.0.0", interfaceClass = ProductService.class, timeout = 120000)
public class ProductServiceImpl extends BaseService implements ProductService {
	@Autowired
	JdbcTemplate jdbcTemplate;
 
	@Override
	@Transactional
	public DubboResponse<ProductVO> addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {
		DubboResponse<ProductVO> response = null;
		int newProdId = 0;
		String prodSql = "insert into t_product(product_name)values(?)";
		String stockSql = "insert into t_stock(product_id,stock)values(?,?)";
		try {
			if (prod != null) {
				KeyHolder keyHolder = new GeneratedKeyHolder();
 
				jdbcTemplate.update(new PreparedStatementCreator() {
					@Override
					public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
						PreparedStatement ps = connection.prepareStatement(prodSql, new String[] { "id" });
						ps.setString(1, prod.getProductName());
						return ps;
					}
				}, keyHolder);
				newProdId = keyHolder.getKey().intValue();
				logger.info("======>insert into t_product with product_id:" + newProdId);
				if (newProdId > 0) {
					jdbcTemplate.update(stockSql, newProdId, prod.getStock());
					logger.info("======>insert into t_stock with successful");
					ProductVO returnData = new ProductVO();
					returnData.setProductId(newProdId);
					returnData.setProductName(prod.getProductName());
					returnData.setStock(prod.getStock());
					response = new DubboResponse(HttpStatus.OK.value(), "success", returnData);
					//throw new Exception("Mk throwed exception to enforce rollback[insert into t_stock]");
					return response;
				}
 
			} else {
				throw new DemoRpcRunTimeException("error occured on ProductVO is null");
			}
		} catch (Exception e) {
			logger.error("error occured on Dubbo Service Side: " + e.getMessage(), e);
			throw new DemoRpcRunTimeException("error occured on Dubbo Service Side: " + e.getMessage(), e);
		}
		return response;
	}
 
}

這個(gè)類(lèi)目前是正常狀態(tài),我們先調(diào)用一把正常的provider到service端的過(guò)程然后接下來(lái)就來(lái)演示如何把exception遠(yuǎn)程傳遞到consumer端。

nacos-consumer工程搭建說(shuō)明

先上pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-consumer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>nacos-service</name>
	<description>消費(fèi)者 Demo project for Spring Boot dubbo nacos</description>
	<parent>
		<groupId>org.sky.demo</groupId>
		<artifactId>nacos-parent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
 
 
	<dependencies>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-core</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-spring</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions><!-- 去掉默認(rèn)配置 -->
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>com.lmax</groupId>
			<artifactId>disruptor</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>
		<!-- Dubbo Registry Nacos -->
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo-registry-nacos</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.nacos</groupId>
			<artifactId>nacos-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<!-- import sky common package -->
		<dependency>
			<groupId>org.sky.demo</groupId>
			<artifactId>skycommon</artifactId>
			<version>${skycommon.version}</version>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<testSourceDirectory>src/test/java</testSourceDirectory>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<directory>src/main/webapp</directory>
				<targetPath>META-INF/resources</targetPath>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>application.properties</include>
					<include>application-${profileActive}.properties</include>
				</includes>
			</resource>
		</resources>
	</build>
</project>

nacos-consumer端的application.properties

 
server.port=8082
server.tomcat.max-connections=50
server.tomcat.max-threads=50
server.tomcat.uri-encoding=UTF-8
server.tomcat.max-http-post-size=0
 
#Dubbo provider configuration
dubbo.application.name=nacos-consumer
dubbo.registry.address=nacos://127.0.0.1:8848
#dubbo.consumer.time=120000
 
 
logging.config=classpath:log4j2.xml

同樣,consumer端也需要連上本地的nacos實(shí)例。

另外多說(shuō)一點(diǎn)的是,不要在consumer端去做類(lèi)似dubbo通訊超時(shí)或者是一些個(gè)性化的dubbo參數(shù)設(shè)置。因?yàn)閐ubbo有3個(gè)核心參數(shù)集,provider, protocol, consumer。而在consumer做的設(shè)置由于這3者的優(yōu)先級(jí)問(wèn)題,它是會(huì)覆蓋掉provider端的設(shè)置。如果是在大規(guī)模微服務(wù)開(kāi)發(fā)場(chǎng)景中,每個(gè)consumer都做自己的個(gè)性化設(shè)置,這不利于全局上對(duì)系統(tǒng)性能進(jìn)行集中統(tǒng)一的管控,因此這需要公司的架構(gòu)師對(duì)這些規(guī)范進(jìn)行provider端的統(tǒng)一管控,一定盡量避免在consumer端去設(shè)置本該屬于central(provider)端的一些參數(shù)。

consumer端的Application.java

package org.sky;
 
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
 
 
 
@EnableDubbo
@ComponentScan(basePackages = { "org.sky" })
@EnableAutoConfiguration
public class Application {
 
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

和provider端的naco-service沒(méi)多少區(qū)別,注意要@EnableDubbo,要不然spring不會(huì)在項(xiàng)目啟動(dòng)時(shí)把consumer端給注冊(cè)到nacos的注冊(cè)中心去。

consumer端的Controller

這個(gè)consumer端正是我第一張圖中的無(wú)狀態(tài)的API層,這一層會(huì)有一堆tomcat/netty/jboss一類(lèi)的東西,它們做的事就是路由API,以json格式向客戶(hù)端(手機(jī)、網(wǎng)頁(yè)、小程序)進(jìn)行返回。這一層是不會(huì)去和DB、NOSQL、緩存一類(lèi)的打交道的,它們要做的就是調(diào)用“后端”微服務(wù)的dubbo服務(wù),因此我們?cè)谶@一端基本以spring中的controller為主。

為了讓consumer端可以調(diào)用provider端的service方法,必須在注入時(shí)加上@Reference注解,這樣dubbos的consumer在注冊(cè)進(jìn)“注冊(cè)中心”,如:nacos這一類(lèi)東西時(shí)就知道要找哪個(gè)provider的service(殘根-stub)了(尋址作用)。

package org.sky.controller;
 
import org.springframework.web.bind.annotation.RestController;
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
 
import org.springframework.web.bind.annotation.RequestMapping;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.annotation.Resource;
 
import org.apache.dubbo.config.annotation.Reference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sky.platform.util.AppConstants;
import org.sky.platform.util.DubboResponse;
import org.sky.platform.util.ResponseResult;
import org.sky.platform.util.ResponseStatusEnum;
import org.sky.platform.util.ResponseUtil;
import org.sky.service.HelloNacosService;
import org.sky.service.ProductService;
import org.sky.vo.ProductVO;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
 
@RestController
@RequestMapping("nacosconsumer")
public class DemoDubboConsumer extends BaseController {
 
	@Reference(version = "1.0.0",loadbalance="roundrobin")
	private HelloNacosService helloNacosService;
 
	@Reference(version = "1.0.0")
	private ProductService productService;
 
	@PostMapping(value = "/sayHello", produces = "application/json")
	public ResponseEntity<String> sayHello(@RequestBody String params) throws Exception {
		ResponseEntity<String> response;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		Map<String, Object> resultMap = new HashMap<>();
		JSONObject requestJsonObj = JSON.parseObject(params);
		try {
			String name = getHelloNameFromJson(requestJsonObj);
			String answer = helloNacosService.sayHello(name);
			logger.info("answer======>" + answer);
			Map<String, String> result = new HashMap<>();
			result.put("result", answer);
			String resultStr = JSON.toJSONString(result);
			response = new ResponseEntity<>(resultStr, headers, HttpStatus.OK);
		} catch (Exception e) {
			logger.error("dubbo-clinet has an exception occured: " + e.getMessage(), e);
			String resultStr = e.getMessage();
			response = new ResponseEntity<>(resultStr, headers, HttpStatus.EXPECTATION_FAILED);
		}
		return response;
 
	}
 
	@PostMapping(value = "/addProductAndStock", produces = "application/json")
	public ResponseEntity<String> addProduct(@RequestBody String params) throws Exception {
		ResponseEntity<String> response = null;
		DubboResponse<ProductVO> dubboResponse;
		String returnResultStr;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		JSONObject requestJsonObj = JSON.parseObject(params);
		Map<String, Object> result = new HashMap<>();
		try {
			ProductVO inputProductPara = getProductFromJson(requestJsonObj);
			dubboResponse = productService.addProductAndStock(inputProductPara);
			ProductVO returnData = dubboResponse.getData();
			if (returnData != null && dubboResponse.getCode() == HttpStatus.OK.value()) {
				result.put("code", HttpStatus.OK.value());
				result.put("message", "add a new product successfully");
				result.put("productid", returnData.getProductId());
				result.put("productname", returnData.getProductName());
				result.put("stock", returnData.getStock());
				returnResultStr = JSON.toJSONString(result);
				response = new ResponseEntity<>(returnResultStr, headers, HttpStatus.OK);
			} else {
				result.put("message", "dubbo service ProductService get nullpoint exception");
				returnResultStr = JSON.toJSONString(result);
				response = new ResponseEntity<>(returnResultStr, headers, HttpStatus.EXPECTATION_FAILED);
			}
		} catch (Exception e) {
			logger.error("add a new product with error: " + e.getMessage(), e);
			result.put("message", "add a new product with error: " + e.getMessage());
			returnResultStr = JSON.toJSONString(result);
			response = new ResponseEntity<>(returnResultStr, headers, HttpStatus.EXPECTATION_FAILED);
		}
		return response;
	}
 
	private String getHelloNameFromJson(JSONObject requestObj) {
		String helloName = requestObj.getString("name");
		return helloName;
	}
 
	private ProductVO getProductFromJson(JSONObject requestObj) {
		String productName = requestObj.getString("productname");
		int stock = requestObj.getIntValue("stock");
		ProductVO prod = new ProductVO();
		prod.setProductName(productName);
		prod.setStock(stock);
		return prod;
	}
 
}

這個(gè)consumer相當(dāng)?shù)暮?jiǎn)單,直接通過(guò)遠(yuǎn)程接口調(diào)用dubbo得到一個(gè)返回。

運(yùn)行例子

確保我們的nacos1.1.4運(yùn)行在那。

然后先運(yùn)行nacos-service的Application.java再運(yùn)行nacos-consumer的Application.java

nacos-service運(yùn)行示例:

nacos-consumer運(yùn)行示例:

然后我們?nèi)acos的管理界面查看一下,就能發(fā)現(xiàn)provider和consumer都注冊(cè)成功了。

接著我們使用postman對(duì)consumer發(fā)一個(gè)json請(qǐng)求

得到返回如下所示

再看數(shù)據(jù)庫(kù)中

這說(shuō)明我們的dubbo+nacos搭建完全運(yùn)行正常,接下來(lái)就要演示兩種Exception的拋出了。

第1種:直接從provider端拋RuntimeException到consumer端

在provider端我們對(duì)ProductServiceImpl進(jìn)行一個(gè)小修改如下:

我們寫(xiě)了一句:

throw new Exception("Mk throwed exception to enforce rollback[insert into t_stock]");

我們前文說(shuō)過(guò),在provider端的service里一定要拋出RuntimeException才會(huì)讓數(shù)據(jù)庫(kù)事物回滾,但是我們也不用擔(dān)心,還記得我們?cè)趎acos-service中已經(jīng)注入了一個(gè)aop的攔截器叫“DemoRpcRuntimeExceptionHandler”嗎?

它的作用就是攔住一切Exception然后把它轉(zhuǎn)化成RuntimeException。

好,我們加完這一句話后重新依次運(yùn)行nacose-service和nacos-consumer。然后同樣通過(guò)postman來(lái)訪問(wèn)http://localhost:8082/nacosconsumer/addProductAndStock,然后我們使用新的產(chǎn)品品名,post請(qǐng)求體內(nèi)的報(bào)文如下所 示:

{"productname":"coffee","stock":10000}

看,我們這次請(qǐng)求過(guò)去后直接在response中出現(xiàn)的是什么?

來(lái)看nacos-service端的日志,這是我們?cè)趐rovider端人為手工拋出的一條日志:

來(lái)看nacos-consumer端的日志,我們可以看到provider端的異常甚至包括它的stackTrace信息都已經(jīng)傳遞到了consumer端了:

這樣的話consumer端的開(kāi)發(fā)人員一看傳過(guò)來(lái)了這個(gè)錯(cuò)誤就會(huì)跑到dubbo開(kāi)發(fā)團(tuán)隊(duì)處吼一下:喂,生產(chǎn)上有一個(gè)bug,你看這就是你們provider端拋出來(lái)的,改吧!

為了確保我們的ExceptionHandler攔截的是否成功,我們來(lái)看數(shù)據(jù)庫(kù)端:

t_product表沒(méi)有插入coffee的記錄

t_stock表也沒(méi)有插入相關(guān)coffee的庫(kù)存

說(shuō)明Exception確實(shí)是被轉(zhuǎn)成了RuntimeException并被spring框架所捕捉然后進(jìn)行了一次回滾。

第2種:把一切Exception包裝成json返回報(bào)文不向consumer端輸出異常具體信息

我們希望把provider端的Exception包裝成如下這種json報(bào)文:

{
    "message" : "exception",
    "code" : "500",
    "add new product failed",
    "productid" : xxx,
    "productname" : xxx,
    "stock" : xxx
}

轉(zhuǎn)而把:

異常的stackTrace以log方式記錄在provider端,在出了問(wèn)題讓provider端的開(kāi)發(fā)人員通過(guò)日志查詢(xún)和定位問(wèn)題即可。

為什么還有這種做法?

很簡(jiǎn)單,因?yàn)閟tackTrace是異常追溯,調(diào)用到了jvm的棧內(nèi)信息了,這個(gè)是“很重”的一件活 。我們把一堆的異常Exception通過(guò)provider和consumer端拋來(lái)拋去,本來(lái)我們用dubbo就是用來(lái)做微服務(wù)的、就是為了應(yīng)對(duì)大規(guī)模的并發(fā)請(qǐng)求的、就是為了做系統(tǒng)的彈性伸縮和高冗余的,你還在用這么大一陀stackTrace在兩端傳來(lái)傳去不說(shuō),還要加上傳時(shí)序列化、接到時(shí)反序列化,這不是增加了系統(tǒng)的開(kāi)銷(xiāo)嗎?

下面直接show me the code,在nacos-service的org.sky.config處增加一個(gè)aop叫“ServiceExceptionHandler”,代碼如下:

package org.sky.config;
 
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.sky.platform.util.DubboResponse;
import org.springframework.stereotype.Component;
 
import com.google.common.base.Throwables;
 
@Component
@Aspect
public class ServiceExceptionHandler {
	protected Logger logger = LogManager.getLogger(this.getClass());
 
	/**
	 * 返回值類(lèi)型為Response的Service
	 */
	@Pointcut(value = "execution(* org.sky.service.*.*(..))")
	private void servicePointcut() {
	}
 
	/**
	 * 任何持有@Transactional注解的方法
	 */
	@Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
	private void transactionalPointcut() {
	}
 
	/**
	 * 異常處理切面 將異常包裝為Response,避免dubbo進(jìn)行包裝
	 *
	 * @param pjp 處理點(diǎn)
	 * @return Object
	 */
	@Around("servicePointcut() && !transactionalPointcut()")
	public Object doAround(ProceedingJoinPoint pjp) {
		Object[] args = pjp.getArgs();
		try {
			return pjp.proceed();
		} catch (Exception e) {
			processException(pjp, args, e);
			return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());
		} catch (Throwable throwable) {
			processException(pjp, args, throwable);
			return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());
		}
	}
 
	/**
	 * 任何持有@Transactional注解的方法異常處理切面 將自定義的業(yè)務(wù)異常轉(zhuǎn)為RuntimeException:
	 * 1.規(guī)避dubbo的包裝,讓customer可以正常獲取message 2.拋出RuntimeException使事務(wù)可以正確回滾 其他異常不處理
	 *
	 * @param pjp 處理點(diǎn)
	 * @return Object
	 */
	@Around("servicePointcut() && transactionalPointcut()")
	public Object doTransactionalAround(ProceedingJoinPoint pjp) throws Throwable {
		try {
			return pjp.proceed();
		} catch (Exception e) {
			Object[] args = pjp.getArgs();
			// dubbo會(huì)將異常捕獲進(jìn)行打印,這里就不打印了
			processException(pjp, args, e);
			// logger.error("service with @Transactional exception occured on dubbo service
			// side: " + e.getMessage(), e);
			throw new RuntimeException(e.getMessage(), e);
		}
	}
 
	/**
	 * 處理異常
	 *
	 * @param joinPoint 切點(diǎn)
	 * @param args      參數(shù)
	 * @param throwable 異常
	 */
	private void processException(final ProceedingJoinPoint joinPoint, final Object[] args, Throwable throwable) {
		String inputParam = "";
		if (args != null && args.length > 0) {
			StringBuilder sb = new StringBuilder();
			for (Object arg : args) {
				sb.append(",");
				sb.append(arg);
			}
			inputParam = sb.toString().substring(1);
		}
		logger.error("\n 方法: {}\n 入?yún)? {} \n 錯(cuò)誤信息: {}", joinPoint.toLongString(), inputParam,
				Throwables.getStackTraceAsString(throwable));
	}
}

它的作用就是:

  • 把一切Exception使用一個(gè)叫DubboResponse的請(qǐng)求體來(lái)返回provider端的service報(bào)文;
  • 如果provider端出錯(cuò),那么也把錯(cuò)誤的系統(tǒng)code與系統(tǒng)message“包”在DubboResponse內(nèi)

等等等等。。。。。。出問(wèn)題了!此處還沒(méi)全完,為什么?

一切Exception?這樣一來(lái)那么包完后在Service層豈不是沒(méi)有Exception被拋出了?如果Service方法涉及到數(shù)據(jù)庫(kù)操作沒(méi)有拋RuntimeException時(shí)數(shù)據(jù)庫(kù)事務(wù)怎么回滾?

這才有了我們?cè)谶@個(gè)handler類(lèi)中有這么一段內(nèi)容,它的作用就是對(duì)一切有@Transactional注解的Service方法在其出錯(cuò)時(shí),還是照樣要拋"RuntimeException",對(duì)于其它的就都包成DubboResponse返回給調(diào)用者了(如下對(duì)于非事務(wù)型Service方法的異常的統(tǒng)一包裝):

	@Around("servicePointcut() && !transactionalPointcut()")
	public Object doAround(ProceedingJoinPoint pjp) {
		Object[] args = pjp.getArgs();
		try {
			return pjp.proceed();
		} catch (Exception e) {
			processException(pjp, args, e);
			return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());
		} catch (Throwable throwable) {
			processException(pjp, args, throwable);
			return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());
		}
	}

好了,然后我們現(xiàn)在重新啟動(dòng)我們的系統(tǒng),我們?cè)賮?lái)看下面的運(yùn)行示例。。。。。。

等!

忘記一件事,下面我給出位于“common”工程中的ProductVO和DubboResponse這兩個(gè)類(lèi)的結(jié)構(gòu)先,我寫(xiě)博文不喜歡“藏”一手。

ProductVO.java

package org.sky.vo;
 
import java.io.Serializable;
 
public class ProductVO implements Serializable {
 
	private int stock = 0;
 
	public int getStock() {
		return stock;
	}
 
	public void setStock(int stock) {
		this.stock = stock;
	}
 
	public String getProductName() {
		return productName;
	}
 
	public int getProductId() {
		return productId;
	}
 
	public void setProductId(int productId) {
		this.productId = productId;
	}
 
	public void setProductName(String productName) {
		this.productName = productName;
	}
 
	private int productId = 0;
	private String productName = "";
}

DubboResponse.java

package org.sky.platform.util;
 
import java.io.Serializable;
 
import org.springframework.http.HttpStatus;
 
import com.alibaba.fastjson.JSON;
 
public class DubboResponse<T> implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
 
	/**
	 * 狀態(tài)碼
	 */
	private int code;
 
	/**
	 * 返回信息
	 */
	private String message;
 
	/**
	 * 
	 * 返回json對(duì)象
	 */
	private T data;
 
	public DubboResponse(int code, String message) {
		this.code = code;
		this.message = message;
	}
 
	public DubboResponse(int code, String message, T data) {
		this.code = code;
		this.message = message;
		this.data = data;
	}
 
	public T getData() {
		return data;
	}
 
	public void setData(T data) {
		this.data = data;
	}
 
	public static <T> DubboResponse success(String message, T data) {
		String resultStr = JSON.toJSONString(data);
		return new DubboResponse(HttpStatus.OK.value(), message, data);
	}
 
	public static DubboResponse success(String message) {
		return success(message, null);
	}
 
	public static DubboResponse error(String message) {
		return new DubboResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, null);
	}
 
	public static DubboResponse error(int code, String message) {
		return new DubboResponse(code, message, null);
	}
 
	public int getCode() {
		return code;
	}
 
	public void setCode(int code) {
		this.code = code;
	}
 
	public String getMessage() {
		return message;
	}
 
	public void setMessage(String message) {
		this.message = message;
	}
}

“正常”據(jù)有@Transactional的Service方法拋異常演示:

現(xiàn)在我們把nacose-service和nacos-consumer運(yùn)行起來(lái)看效果,試圖插入一個(gè)新的prouct:

得到返回:

再來(lái)看nacos-service端、nacos-consumer端以及數(shù)據(jù)庫(kù)

可以看到provider與consumer端都正確拋錯(cuò)且數(shù)據(jù)庫(kù)中沒(méi)有插進(jìn)去值。

“不正常”的不含有Transactional的(普通)Service方法拋異常被封裝演示:

我們現(xiàn)在做點(diǎn)小手腳,我們把provider端的“addProductAndStock(ProductVO prod)”方法上的@Transactional拿走來(lái)看看效果。

	@Override
	public DubboResponse<ProductVO> addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {
		DubboResponse<ProductVO> response = null;
		int newProdId = 0;
		String prodSql = "insert into t_product(product_name)values(?)";
		String stockSql = "insert into t_stock(product_id,stock)values(?,?)";
		try {
			if (prod != null) {
				KeyHolder keyHolder = new GeneratedKeyHolder();
 
				jdbcTemplate.update(new PreparedStatementCreator() {
					@Override

請(qǐng)像上面這樣的代碼片端

我們?cè)僭趎acos-consume端做一個(gè)小小的修改,如下所示,讓consumer端直接把provider端組裝好的{ "message" : "xxxx..."}顯示在“最前端”(一切通過(guò) nginx端來(lái)訪問(wèn)consumer,consumer再通過(guò)provider調(diào)用數(shù)據(jù)庫(kù),在這邊我們使用的是postman)。

然后我們來(lái)運(yùn)行起來(lái)看一下效果:

我們可以看到,這一次在去除了@Transactional注解后,當(dāng)Service方法拋錯(cuò)時(shí),請(qǐng)求端拿到的是我們經(jīng)過(guò)包裝過(guò)的DubboResponse內(nèi)的東西

provider端包裝普通Service拋出的異常的核心代碼:

	@Around("servicePointcut() && !transactionalPointcut()")
	public Object doAround(ProceedingJoinPoint pjp) {
		Object[] args = pjp.getArgs();
		try {
			return pjp.proceed();
		} catch (Exception e) {
			processException(pjp, args, e);
			return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());
		} catch (Throwable throwable) {
			processException(pjp, args, throwable);
			return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());
		}
	}

我們查看我們的Provider端,它正是通過(guò)上述代碼catch(Exception e)中的這一段來(lái)進(jìn)行服務(wù)端日志的記錯(cuò)和把錯(cuò)誤包裝后返回給到consumer端的,就是下面這兩句:

processException(pjp, args, e);
return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());

來(lái)看看nacos-service端的日志輸出

來(lái)看看nacos-consumer端的日志輸出

哈哈,這次nacos-consumer端無(wú)任何拋錯(cuò),因?yàn)殄e(cuò)誤已經(jīng)被provider端包裝起來(lái)了。

當(dāng)然,當(dāng)我們看我們的DB端時(shí),肯定,是有數(shù)據(jù)插入成功的。

因?yàn)榍拔恼f(shuō)了,對(duì)于無(wú)@Transactional注解的方法,我們的aop handler類(lèi)會(huì)把一切錯(cuò)誤 “吃掉”,在后臺(tái)僅作記錄然后包成正常返回結(jié)果給到consumer端的,那么provider端的Service方法既無(wú)RuntimeException拋出,何來(lái)回滾?

當(dāng)然是插入成功的!

t_product表

t_stock表

總結(jié)

  1. 所以在dubbo的provider端的RuntimeExeption并且是"implements Serializable"的就可以連著stackTrace拋到遠(yuǎn)程的consumer端;
  2. 實(shí)際項(xiàng)目中dubbo的provider(dubbo群)與dubbo的consumer(一堆無(wú)狀態(tài)的tomcat為容器布署的api controller)間如果只是為了傳stackTrace來(lái)消耗網(wǎng)絡(luò)硬件等資源只是為了“排查定位問(wèn)題”方便,這么做是不值的,那么就要包一層,包時(shí)不要包的太過(guò)了,記得涉及數(shù)據(jù)庫(kù)事務(wù)的方法一定要拋RuntimeException,要不然就是插進(jìn)去一堆臟數(shù)據(jù);

到此這篇關(guān)于阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式的文章就介紹到這了,更多相關(guān)nacos+springboot+dubbo統(tǒng)一處理異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)經(jīng)典游戲打磚塊游戲的示例代碼

    Java實(shí)現(xiàn)經(jīng)典游戲打磚塊游戲的示例代碼

    這篇文章主要介紹了如何利用Java實(shí)現(xiàn)經(jīng)典的游戲—打磚塊。玩家操作一根螢?zāi)簧纤降摹鞍糇印?,讓一顆不斷彈來(lái)彈去的“球”在撞擊作為過(guò)關(guān)目標(biāo)消去的“磚塊”的途中不會(huì)落到螢?zāi)坏紫隆8信d趣的小伙伴可以了解一下
    2022-02-02
  • J2ee 高并發(fā)情況下監(jiān)聽(tīng)器實(shí)例詳解

    J2ee 高并發(fā)情況下監(jiān)聽(tīng)器實(shí)例詳解

    這篇文章主要介紹了J2ee 高并發(fā)情況下監(jiān)聽(tīng)器實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • java二路歸并排序示例分享

    java二路歸并排序示例分享

    這篇文章主要介紹了java二路歸并排序示例,需要的朋友可以參考下
    2014-02-02
  • 完美解決IDEA Ctrl+Shift+f快捷鍵突然無(wú)效的問(wèn)題

    完美解決IDEA Ctrl+Shift+f快捷鍵突然無(wú)效的問(wèn)題

    這篇文章主要介紹了完美解決IDEA Ctrl+Shift+f快捷鍵突然無(wú)效的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02
  • java實(shí)現(xiàn)全局監(jiān)聽(tīng)鍵盤(pán)詳解

    java實(shí)現(xiàn)全局監(jiān)聽(tīng)鍵盤(pán)詳解

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)全局監(jiān)聽(tīng)鍵盤(pán)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下
    2024-01-01
  • springboot如何獲取yml里面的屬性值

    springboot如何獲取yml里面的屬性值

    這篇文章主要介紹了springboot如何獲取yml里面的屬性值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java Swing JList列表框的實(shí)現(xiàn)

    Java Swing JList列表框的實(shí)現(xiàn)

    這篇文章主要介紹了Java Swing JList列表框的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • Java Swing窗體關(guān)閉事件的調(diào)用關(guān)系

    Java Swing窗體關(guān)閉事件的調(diào)用關(guān)系

    這篇文章主要為大家詳細(xì)介紹了Java Swing窗體關(guān)閉事件的調(diào)用關(guān)系,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • java并發(fā)編程專(zhuān)題(一)----線程基礎(chǔ)知識(shí)

    java并發(fā)編程專(zhuān)題(一)----線程基礎(chǔ)知識(shí)

    這篇文章主要介紹了java并發(fā)編程線程的基礎(chǔ)知識(shí),文中講解非常詳細(xì),幫助大家更好的學(xué)習(xí)JAVA并發(fā)編程,感興趣想學(xué)習(xí)JAVA的可以了解下
    2020-06-06
  • 詳解Java的按位操作符

    詳解Java的按位操作符

    Java的位操作符用來(lái)操作整數(shù)基本數(shù)據(jù)類(lèi)型中的單個(gè)“比特”(bit),即代進(jìn)制位。下面通過(guò)本文給大家分享Java的按位操作符,感興趣的朋友一起看看吧
    2017-09-09

最新評(píng)論