SpringMvc 项目转 SringBoot

本文记录了将原有的SpringMvc项目迁移到SpringBoot的过程中,如何引入Hystrix实现分布式系统的限流、熔断和降级。首先介绍了项目结构,然后逐步讲解了如何配置SpringBoot,如何将XML配置迁移,并展示了通过Hystrix进行并发控制的测试和结果。最后提供了相关代码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

个人实践 和 备忘

项目结构 :

这里写图片描述

分布式项目, 利用maven构建了多模块, 开发还是 ssm , 项目中 有mq 和 redis , memcached。都是通过 配置文件加载,开头是web.xml 引入spring-x.xml , spring-x.xml 再去引入 其他配置文件,znf4-common-config 用来配置 *.properties 文件,mq ,jdbc , redis 系统等相关配置。

现引入Boot 先关jar包 :

我是一次性引入了很多个相关jar , 有些也没用上,你们自行去除就行。


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>1.3.1.RELEASE</spring-boot.version>
        <tomcat.version>8.0.28</tomcat.version>
    </properties>


    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <!-- spring-boot start -->
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <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-starter-jdbc</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-remote-shell</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <version>${spring-boot.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${tomcat.version}</version>
            <scope>provided</scope>
        </dependency>
        <!--spring boot end-->

加入启动模块 :

这里写图片描述

ApplicationStarter .java

package com.znf4.app;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;

/**
 * Created by agui on 15/10/26.
 */
@SpringBootApplication
@ImportResource("classpath:spring/spring-context.xml")
public class ApplicationStarter {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ApplicationStarter.class);
        app.setAdditionalProfiles();
        app.setBannerMode(Banner.Mode.LOG);
        app.run(args);


    }

}

上面类中 注解 :@ImportResource(“classpath:spring/spring-context.xml”) ,用来引入:

这里写图片描述

说明 :spring-contet-service.xml 这个配置文件在我其他模块,该模块通过它又引入了一些其他 xml 配置。

JvmConfiger.java



package com.znf4.app;

import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.ResourceBundle;

/**
 * Created by agui on 15/11/2.
 */
public class JvmConfiger {
    public final static ResourceBundle RESOURCE = ResourceBundle.getBundle("application");


    public static String getJvmConfig() {
        StringBuilder buffer = new StringBuilder();
        append(buffer, RESOURCE, "jvm.mem");
        append(buffer, RESOURCE, "jvm.log");
        append(buffer, RESOURCE, "jvm.gc");
        append(buffer, RESOURCE, "jvm.others");
        append(buffer, RESOURCE, "jvm.args");
        return buffer.toString();
    }

    public static String getRun() {
        StringBuilder buffer = new StringBuilder();
        buffer.append("nohup java ");
        buffer.append(String.format("`java -jar %s -jvm`",getPackageName()));
        buffer.append(" -jar ");
        buffer.append(getPackageName());
        buffer.append(" > ");
        buffer.append("./console.log");
        buffer.append(" 2>&1 &");
        return buffer.toString();
    }

    private static String getPackageName(){
        return getAppConfig("project.name") + "." + getAppConfig("project.package");
    }

    private static StringBuilder append(StringBuilder buffer, ResourceBundle resource, String key) {
        if (resource.containsKey(key)) {
            String value = resource.getString(key);
            if (value.contains("hostname=%s")) {
                value = String.format(value, getLocalAddress());
            }
            return buffer.append(" " + value);
        }
        return buffer;
    }

    public static String getAppConfig(String key) {
        if (RESOURCE.containsKey(key)) {
            return RESOURCE.getString(key);
        }
        return null;
    }

    public static String getLocalAddress() {
        try {
            // Traversal Network interface to get the first non-loopback and non-private address
            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
            ArrayList<String> ipv4Result = new ArrayList<String>();
            ArrayList<String> ipv6Result = new ArrayList<String>();
            while (enumeration.hasMoreElements()) {
                final NetworkInterface networkInterface = enumeration.nextElement();
                final Enumeration<InetAddress> en = networkInterface.getInetAddresses();
                while (en.hasMoreElements()) {
                    final InetAddress address = en.nextElement();
                    if (!address.isLoopbackAddress()) {
                        if (address instanceof Inet6Address) {
                            ipv6Result.add(normalizeHostAddress(address));
                        } else {
                            ipv4Result.add(normalizeHostAddress(address));
                        }
                    }
                }
            }

            // prefer ipv4
            if (!ipv4Result.isEmpty()) {
                for (String ip : ipv4Result) {
                    if (ip.startsWith("127.0") || ip.startsWith("192.168")) {
                        continue;
                    }

                    return ip;
                }

                return ipv4Result.get(ipv4Result.size() - 1);
            } else if (!ipv6Result.isEmpty()) {
                return ipv6Result.get(0);
            }
            //If failed to find,fall back to localhost
            final InetAddress localHost = InetAddress.getLocalHost();
            return normalizeHostAddress(localHost);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static String normalizeHostAddress(final InetAddress localHost) {
        if (localHost instanceof Inet6Address) {
            return "[" + localHost.getHostAddress() + "]";
        } else {
            return localHost.getHostAddress();
        }
    }
}

继续引入Boot相关配置:

值得说的是 : 我把 znf4-common-config 中的jdbc.properties 中的 相关数据库配置远方不动的粘贴到了 boot 约定下的 application.properties

# sys config
isDebug = true
application.message=znf4

# 容器端口
server.port=9999
server.sessionTimeout= 3600
server.contextPath=


# tomcat config
#url = http://localhost:9999/merchant/trade/free?id=11111111111111&type=free 
server.tomcat.compression=2048 # is compression enabled (off, on, or an integer content length limit)
server.tomcat.compressable-mime-types=text/html,text/xml,text/plain,text/javascript,application/json,application/xml
tomcat.accessLogEnabled= false
tomcat.protocolHeader=x-forwarded-proto
tomcat.remoteIpHeader=x-forwarded-for
tomcat.basedir=
tomcat.backgroundProcessorDelay=30 # secs
server.tomcat.max-threads = 500
server.tomcat.uri-encoding = UTF-8



#############################
##JDBC Setting
#############################

##common
jdbc.validationQuery=select current_timestamp()
jdbc.maxWait=60000

##web
web.jdbc.driver=com.mysql.jdbc.Driver
web.jdbc.url=jdbc:mysql://123.26.6.2:3306/znf4?useUnicode=true&characterEncoding=UTF-8
web.jdbc.username=root
web.jdbc.password=T5U+tbyGJ6mm6UVSZfo9qEDcmZdAx3Pk81KsYtpdhiN08WuPLwrVilyEK0yezprsCz25ghR2L11QJKQP/2+41w==
web.jdbc.initialSize=2
web.jdbc.minIdle=2
web.jdbc.maxActive=10

spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

#classpath中存在velocity 忽略spring-boot的VelocityAutoConfiguration
spring.velocity.enabled=false
spring.velocity.checkTemplateLocation=true

最好以上我们就完成了 原生Springmvc SM 下的项目加进boot 相关组件,尝试启动一下 :

这里写图片描述

没有任何 问题 , 解释一下我为什么要引入boot :

之前设计这个项目就是根据需求直接设计成分布式的项目,各种中间件都是通过spring -xml 文件配置的,因为一些三方接口不是很给力,之前上网上找了一个 通过redis 对接口限流的方法,感觉还是有很大局限性,维护那不容易。之前在学Springcloud 中全家桶组件时,也是基于boot , 这把要用hystrix组件, 就想到项目通过boot 启动 , 用boot 去 引入 sringcloud 中的其他组件(现有的东西不动),后续也可以上 微服务(虽然对dubbo 情有独钟,还是选择了cloud)。 下面会展示 boot 引入 hystrix ,并且进行测试的相关代码和图片说明。

jar准备:

我是在partent 中下载jar, core 模块去继承, server 模块去引入core , merchant模块作为入口,去引用 core , server 。

直白说:引入下面这个jar 就行了,上面说的那些都跟我设计的maven多模块有关,跟jar没直接关系。

<!-- 限流,熔断,降级 -->
<dependency> 
    <groupId>com.netflix.hystrix</groupId> 
    <artifactId>hystrix-core</artifactId> 
    <version>1.5.13</version> 
</dependency>

这里写图片描述

启动一个测试服务,模拟三方服务 :

这里写图片描述

我们在我们的项目中加入测试代码 :

这里写图片描述

我们通过jemter 工具模拟并发,访问merchant 模块,merchant逻辑中有通过http 访问上面 9001 服务的代码,我们的目的就是通过 boot 去配置 hystrix 实现, 限流,降级,熔断。

我们先压测一下我们测试服务器 9001 ,直接 50 个线程 10次循环,共500次请求 :

这里写图片描述

结果 :

这里写图片描述

蹦了, 那么基本上可以肯定,我们通过merchant 去访问测试服务器,500次去访问也是扛不住的,我们不可能因为一个三方服务性能不好,出先了异常,导致我们的系统也用不了,那么我对他进行处理 :

SmsSendCommand.java 类

package com.znf4.test;

import java.io.InputStream;
import java.net.URL;

import org.apache.tomcat.util.http.fileupload.IOUtils;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class SmsSendCommand extends HystrixCommand<String>{

    protected SmsSendCommand() {
        super(HystrixCommandGroupKey.Factory.asKey("smsGroup"));
    }

    @Override
    protected String run() throws Exception {
        URL url = new URL("http://localhost:9001/send/agui");
        byte[] result = new byte[3];
        InputStream input = url.openStream();
        IOUtils.readFully(input, result);
        // 远程服务不稳定或网络抖动时暂时关闭
        return new String(result);
    }

    @Override
    protected String getFallback() {
        // 降级 策略  再次查询, 查询备用接口 缓存 mock值
        // 根据业务自定义
        return "降级";
    }

}

测试 :

@RequestMapping("test_hystrix")
public String test_hystrix() throws IOException{
    return new SmsSendCommand().execute();
}

config.properties 文件 跟 apllication.properties 文件放在同一个目录下。 配置如下

注意 : hystrix.threadpool.smsGroup.coreSize=20 中的 smsGroup 为 上述 代码中的key : HystrixCommandGroupKey.Factory.asKey(“smsGroup”) 。

# Hystrix 默认加载的配置文件 - 限流、 熔断示例

# 超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000000

# 信号量 netflix分布式配置中心 archaius
# 线程池大小
hystrix.threadpool.default.coreSize=10
hystrix.threadpool.default.maxQueueSize=-1
hystrix.threadpool.default.queueSizeRejectionThreshold=5

# 限流策略
hystrix.threadpool.smsGroup.coreSize=20

hystrix.threadpool.smsGroup.maxQueueSize=1000
# 超过就报错
hystrix.threadpool.smsGroup.queueSizeRejectionThreshold=800

结果 :

这里写图片描述

解释 : 我们 通过jmeter 直接 80 * 10 次访问我们限流后的 接口, 可以发现服务器 并没有被压垮。 证明我门的配置好用。 继续我们补充一下降级,熔断。

降级 :

降级我们要么通过 SmsSendCommand 类中的 :

进行降级逻辑,可以 在此查询,查询别用接口,缓存mock 值等。

说明一下: 我们的限流 或者熔断 报错之后都会进行降级 。

@Override
    protected String getFallback() {
        // 降级 策略  再次查询, 查询备用接口 缓存 mock值
        // 根据业务自定义
        return "降级";
    }

熔断 :

继续追加到config.properties 文件 , 都有注释,就不解释了。

# 熔断策略
# 启用/禁用熔断机制
hystrix.command.default.circuitBreaker.enabled=true
# 强制开启 禁止访问相关接口
hystrix.command.default.circuitBreaker.forceOpen=false
# 强制关闭 
hystrix.command.default.circuitBreaker.forceClosed=false

# 在一定前提条件下,暂停接口访问
# 10秒统计一次
hystrix.command.default.metrics.rollingStats.timeInMilliseconds=10000
# 令牌桶数量
# hystrix.command.default.metrics.rollingStats.numBuckets=10
# 前提条件,时间内发起一定数量的请求。  也就是10秒钟内至少请求5次,熔断器才发挥起作用
hystrix.command.default.circuitBreaker.requestVolumeThreshold=5
# 错误百分比。达到或超过这个百分比,启用熔断  不会访问接口
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
# 10秒后,半打开状态
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=10000

测试 :

完善测试服务器代码,加入随机数,对2取余,满足就响应一个错误,来触发熔断 ,。

这里写图片描述

重启测试服务9001 , 继续访问 80 * 10 。看结果 :

这里写图片描述

上面可以看到,我们 800次请求,:
错误百分比。达到或超过这个百分比,启用熔断 不会访问接口
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50 时候我们这个接口就不会在继续接收请求,以进行了熔断。

以上,我们我们完成了 boot 的引入,顺便 我用boot 引入了hystrix 进行了 限流,熔断,降级的演示。

实例代码 : 链接: https://pan.baidu.com/s/1o8n6pw2 密码: dnsf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值