分类目录Java

链路监控 Spring Cloud Sleuth & Zipkin

链路监控 Spring Cloud Sleuth

        <!--<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>-->
        <!--已经包括sleuth和zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

Zipkin

docker run -itd --name zipkin -p 9411:9411 openzipkin/zipkin

spring:
  zipkin:
    base-url: http://192.168.88.108:9411
 sleuth:
    sampler:
      probability: 1

Spring Cloud Stream 分组

服务 介绍
stream-group-sender 消息发送者服务
stream-group-receiverA 消息接收者服务
stream-group-receiverB 消息接收者服务

创建stream-group-sender 服务

spring.application.name=stream-sender
//对应 MQ 是 exchange outputProduct自定义的信息
spring.cloud.stream.bindings.outputProduct.destination=exchangeProduct
1. 定义发送接口

public interface ISendeService {
    String OUTPUT="outputProduct";
    /**
     * 指定输出的交换器名称
     * @return
     */
    @Output(OUTPUT)
    MessageChannel send();
}
  1. 在启动类增加注解
    // 绑定我们刚刚创建的发送消息的接口类型
    @EnableBinding(value={ISendeService.class})
  2. 测试发送
@RunWith(SpringRunner.class)
@SpringBootTest(classes=StreamSenderStart.class)
public class StreamTest {
    @Autowired
    private ISendeService sendService;

    @Test
    public void testStream(){
        Product p = new Product(666, "stream test ...");
        // 将需要发送的消息封装为Message对象
        Message message = MessageBuilder
                                .withPayload(p)
                                .build();
        sendService.send().send(message );
    }
}

创建stream-group-receiverA服务

spring.application.name=stream-group-receiverA
// 对应 MQ 是 exchange 和消息发送者的 交换器是同一个
spring.cloud.stream.bindings.inputProduct.destination=exchangeProduct
// 具体分组 对应 MQ 是 队列名称 并且持久化队列 inputProduct 自定义
spring.cloud.stream.bindings.inputProduct.group=groupProduct
1. 定义接收口

public interface IReceiverService {
    String INPUT = "inputProduct";
    /**
     * 指定接收的交换器名称
     * @return
     */
    @Input(INPUT)
    SubscribableChannel receiver();
}
  1. 消息的具体处理
@Service
@EnableBinding(IReceiverService.class)
public class ReceiverService {
    @StreamListener(IReceiverService.INPUT)
    public void onReceiver(Product p){
        System.out.println("消费者A:"+p);
    }
}
  1. 在启动类添加注解
    @EnableBinding(value={IReceiverService.class})

创建stream-group-receiverB服务

 此服务和stream-group-receiverA一样,复制一份只需修改配制中的服务名称,端口,group设置不一样
在stream-group-receiverA和stream-group-receiverB服务的group不一致的情况下都收到了消息
改为同组的情况下只有其中一个受到消息。避免了消息重复消费

RabbitMq使用

用于异步处理,日志处理,流量削峰,应用解耦

引用依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

参数配制

默认配制
spring:
 rabbitmq:
   host: localhost
   port: 5672
   username: guest
   password: guest

发送消息

public class MqSend {
    @Autowired
    private AmqpTemplate amqpTemplate;

     /**
     * 发送Mq测试消息
     */
    public void send(){
        amqpTemplate.convertAndSend("myQueue","now "+new Date());
    }

    /**
     * 发送数据供应商分组Mq测试消息
     */
    public void sendOrder(){
        amqpTemplate.convertAndSend("myQueue","computer","now "+new Date());
    }

}

接收消息

@Slf4j
@Component
public class MqReceiver {

    //1. 接收手动创建的消息,需先手动创建myQueue队列名
    /*@RabbitListener(queues = "myQueue")
    public void processtest(String message){
        log.info("MQReceiver: {}",message);
    }*/

    //2. 自动创建队列
    // @RabbitListener(queuesToDeclare = @Queue("myQueue"))

    //3. 自动创建, Exchange和Queue绑定
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("myQueue"),
            exchange = @Exchange("myExchange")
    ))
    public void process(String message) {
        log.info("MqReceiver: {}", message);
    }
     /**
     * 数码供应商服务 接收分组消息
     * @param message
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange("myOrder"),
            key = "computer",
            value = @Queue("computerOrder")
    ))
    public void processComputer(String message) {
        log.info("computer MqReceiver: {}", message);
    }

    /**
     * 水果供应商服务 接收分组消息
     * @param message
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange("myOrder"),
            key = "fruit",
            value = @Queue("fruitOrder")
    ))
    public void processFruit(String message) {
        log.info("fruit MqReceiver: {}", message);
    }
}

Feign/RestTemplate

  1. 增加依赖
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. 在启动主类增加注解
@EnableFeignClients
  1. 定义feign调用商品微服务的接口
访问PRODUCT服务下的接口
@FeignClient(name = "PRODUCT")
public interface ProductClient {

    /**
     * 从商品微服务中获取一个测试信息
     * @return
     */
    @GetMapping("/msg")
    String productMsg();

    /**
     * 根据一组商品ID获取商品列表
     * @param productIdList
     * @return
     */
    @PostMapping("/listForOrder")
    List<ProductInfoOutput> listForOrder(@RequestBody List<String> productIdList);

    /**
     * 扣库存
     * @param decreaseStockInputList
     */
    @PostMapping("/decreaseStock")
    void decreaseStock(@RequestBody List<DecreaseStockInput> decreaseStockInputList);
}

  1. 调用feign接口
@Api(tags="RestTemplate/feign调用微服务API")
@RestController
@Slf4j
public class ClientController {
    /*第二种方式*/
   /* @Autowired
    private LoadBalancerClient loadBalancerClient;*/

    /* 第三种方式
     @Autowired
    private RestTemplate restTemplate;*/

    /*第四种方式feign*/
    @Autowired
    private ProductClient productClient;

    @ApiOperation("获取一个测试信息")
    @GetMapping("/msg")
    public String getProductMsg(){
        //第一种方式 直接使用RestTemplate,URL固定
        //RestTemplate restTemplate = new RestTemplate();
        //String response = restTemplate.getForObject("http://localhost:8080/productserver/msg",String.class);

        //第二种方式 直接使用RestTemplate,URL利用LoadBalancerClient获取
        /*RestTemplate restTemplate = new RestTemplate();
        ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
        String url = String.format("http://%s:%s/msg",serviceInstance.getHost(),serviceInstance.getPort());
        String response = restTemplate.getForObject(url,String.class);*/

        //第三种,将 RestTemplate 作为一个Bean 配制到项目,使用注解@LoadBalanced,在restTemplate直接使用应用名字
        //String response = restTemplate.getForObject("http://PRODUCT/msg",String.class);
        //4.feign
        String response = productClient.productMsg();
        log.info("response={}",response);
        return response;
    }
    }
    第三种方式使用注解@LoadBalanced的配制文件
    @Component
    public class RestTemplateConfig {
     @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    or
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        //return new RestTemplate();
        return new RestTemplateBuilder().basicAuthentication("oauth", "123").build();
    }
    }

HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
map.add(“grant_type”, “password”);
map.add(“client_id”, “oauth”);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, header);
MyToken myToken = restTemplate.postForObject(“http://192.168.88.108:30002/oauth/token”,request,MyToken.class);

    // build http headers
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization","bearer "+myToken.getAccess_token());
    ResponseEntity<ClientUser[]> responseEntity = restTemplate.exchange(RBAC_SERVER+POST_ALL_USER, HttpMethod.GET,new HttpEntity<String>(headers),ClientUser[].class);

Ribbon 客户端负载均衡

更改负载均衡规则

PRODUCT:
  ribbon:
    NFLoadBalancerRuleClassName: com.loadbalancer.RandomRule

PRODUCT为应用名,此处改为随机方式,注意写完整的Class路径,不改默认为轮询,一般也够用了,其它参考官方说明文档

Hystrix熔断

具有服务降级(HystrixCommand注解指定/fallbackMethod回退函数实现降级逻辑),服务熔断,依赖隔离,监控的作用,防雪崩的利器

        <!--添加Hystrix依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
           <!-- <version>2.0.2.RELEASE</version>-->
        </dependency>
        <!--添加Hystrix dashboard依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            <!-- <version>2.0.2.RELEASE</version>-->
        </dependency>
        <!--如果有了不用再引入-->
        <!--<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>-->
在主函数上添加注解
@EnableCircuitBreaker //启动Hystrix
@EnableHystrixDashboard

@HystrixCommand
如果我们使用的是@HystrixCommand注解,那么可以在注解中直接指定超时时间,如下:

@HystrixCommand(fallbackMethod="fallback",
    commandProperties = {
         @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" )
    }
)

当然也可以指定commandKey,然后在配置文件中配置超时时间,如下:

@HystrixCommand(fallbackMethod="fallback",commandKey="userGetKey")
配置文件给commandKey配置超时时间:
hystrix.command.userGetKey.execution.isolation.thread.timeoutInMilliseconds = 13000

全局配置
如果只是想全局的配置,可以配置默认的超时时间:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000

接口级别配置
假如我们的Feign Client定义如下:

@FeignClient(value = "user-service", fallbackFactory = UserRemoteClientFallbackFactory.class)
public interface UserRemoteClient {

    @GetMapping("/user/get")
    public ResponseData<UserDto> getUser(@RequestParam("id") Long id);

}

那么配置如下:

hystrix.command.UserRemoteClient#getUser(Long).execution.isolation.thread.timeoutInMilliseconds = 300

为什么要配置成上面的方式呢?

其实就是对commandKey进行配置,只要我们知道commandKey的生成规则就可以对接口级别进行配置,接口级别的规则是 Client名称#方法名(参数类型)

源码在feign.hystrix.SetterFactory.Default中:

String commandKey = Feign.configKey(target.type(), method);
服务级别配置
1.在Zuul中针对服务级别的话,直接配置service-id,如下:

hystrix.command.service-id.execution.isolation.thread.timeoutInMilliseconds=3000
Zuul中之所以要配置service-id原因是commandKey就是用的service-id, 通过源码分析可以得到结论。

首先进入的是RibbonRoutingFilter中的run方法,然后我们看核心的forward方法:

ClientHttpResponse response = forward(commandContext);
在forward中有下面的代码:

RibbonCommand command = this.ribbonCommandFactory.create(context);
通过create可以定位到具体的实现,这边就看你用的什么Http客户端,默认有三种实现,默认定位到org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory.create(RibbonCommandContext)方法。
所以service-id就是commandKey。

2.在Feign中针对服务级别的话,需要对commandKey进行定制,可以用service-id, 也可以用Feign Client Name,如下:

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
    return HystrixFeign.builder().setterFactory(new SetterFactory() {

        @Override
        public Setter create(Target<?> target, Method method) {
            String groupKey = target.name();
            String commandKey = Feign.configKey(target.type(), method);
            return HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
                        //.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
                        //.andCommandKey(HystrixCommandKey.Factory.asKey(groupKey))
                        .andCommandKey(HystrixCommandKey.Factory.asKey(target.type().getSimpleName()));
            }
    });
}
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
默认的接口方式

.andCommandKey(HystrixCommandKey.Factory.asKey(groupKey))
service-id方式

.andCommandKey(HystrixCommandKey.Factory.asKey(target.type().getSimpleName()));
Feign Client Name方式

配置的话根据不同的配置填写不通的commandKey就可以了:

hystrix.command.Feign Client Name.execution.isolation.thread.timeoutInMilliseconds=3000

swagger2

1、pom.xml 添加 Maven 依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
​
 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
       <version>2.9.2</version>
    </dependency>
</dependencies>

2、创建 Swagger2Configuration.java

@Configuration
@EnableSwagger2
public class Swagger2Configuration {
    //api接口包扫描路径
    public static final String SWAGGER_SCAN_BASE_PACKAGE = "com.hzdtech.order.server";
    public static final String VERSION = "1.0.0";

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage(SWAGGER_SCAN_BASE_PACKAGE))
                .paths(PathSelectors.any())// 可以根据url路径设置哪些请求加入文档,忽略哪些请求
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("订单微服务") //设置文档的标题
                .description("单词计数服务 API 接口文档") // 设置文档的描述
                .version(VERSION) // 设置文档的版本信息-> 1.0.0 Version information
                .termsOfServiceUrl("http://shanpai.video") // 设置文档的License信息->1.3 License information
                .build();

    }
}

3、API 接口编写

@RestController
@Api(tags="接口所在的类")
@RequestMapping ("/my")
public class MyController {

    @RequestMapping(value="/list", method=RequestMethod.POST)
    @ApiOperation(value = "接口名", notes = "接口描述", httpMethod = "POST")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "length",value = "参数1", required = true, paramType = "path"),
            @ApiImplicitParam(name = "size",value = "参数2", required = true, paramType = "query"),
            @ApiImplicitParam(name = "page",value = "参数3", required = true, paramType = "header"),
            @ApiImplicitParam(name = "total",value = "参数4", required = true, paramType = "form"),
            @ApiImplicitParam(name = "start",value = "参数5",dataType = "string", paramType = "body")
    })
    public String register(){
        return "has permission";
    }
}
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiImplicitParam:描述一个请求参数,可以配置参数的中文含义,还可以给参数设置默认值
@ApiImplicitParams:描述由多个 @ApiImplicitParam 注解的参数组成的请求参数列表

4、启动 SpringBoot 应用

SpringBoot 启动成功后,访问 http://localhost:8080/swagger-ui.html

###5 5、在 Security 中的配置

Spring Boot 项目中如果集成了 Spring Security,在不做额外配置的情况下,Swagger2 文档会被拦截。解决方法是在 Security 的配置类中重写 configure 方法添加白名单即可:

@Override
public void configure ( WebSecurity web) throws Exception {
    web.ignoring()
      .antMatchers("/swagger-ui.html")
      .antMatchers("/v2/**")
      .antMatchers("/swagger-resources/**");
}

springboot 多模块化项目打包

打成war包,只需在web子项目中的pom文件中添加

 <packaging>war</packaging>

 <build>
        <!-- 为jar包取名 -->
        <finalName>separate-demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.3.0.RELEASE</version>
            </plugin>
        </plugins>
    </build>

打成jar包,要在web和父的pom文件中添加

a) 父pom

复制代码

 <!--将这段放开,并指定入口,则打jar包,如果注释这一段,并在子项目web中将<packaging>jar</packaging>改成war,则打成war包-->
    <build>
        <plugins>
            <plugin>
                <!-- The plugin rewrites your manifest -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.3.0.RELEASE</version>
                <configuration><!-- 指定该Main Class为全局的唯一入口 -->
                    <mainClass>com.yangwj.separate.SeparateApplication</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->
                        </goals>
                        <!--可以生成不含依赖包的不可执行Jar包-->
                        <!-- configuration>
                          <classifier>exec</classifier>
                        </configuration> -->
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

b) web pom文件

<packaging>jar</packaging>  <!--指定为jar包,如果想指定为war包,则改为war包-->

<!--让多模块化拆分之后还能打成完整的可执行jar包-->
    <build>
        <!-- 为jar包取名 -->
        <finalName>separate-demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.3.0.RELEASE</version>
            </plugin>
        </plugins>
    </build>

Tomcat

Tomcat官网

wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.29/bin/apache-tomcat-9.0.29.tar.gz

tar -xvf apache-tomcat-9.0.29.tar.gz

mv apache-tomcat-9.0.29 tomcat9

修改tomcat 端口号
root@ser88:/opt/tomcat/tomcat9/conf# vi server.xml

    <!--port default 8080 change 8081 -->
    <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

Docker

docker pull tomcat:9.0.30-jdk8

9.0.30-jdk8-openjdk9.0-jdk8-openjdk9-jdk8-openjdk9.0.30-jdk89.0-jdk89-jdk8

jenkins# docker run -itd –name jenkins-tomcat -p 8080:8080 -v $PWD:/usr/local/tomcat/webapps tomcat:9.0.30-jdk8

docker run -itd –name jenkins-tomcat -p 8080:8080 -v /opt/jenkins:/usr/local/tomcat/webapps tomcat:9.0.30-jdk8

Java 安装配制

Java,OpenJDK和Oracle Java有两个主要的实现,几乎没有区别,只是Oracle Java有一些额外的商业功能。

Ubuntu系统

  • OpenJDK
    • 安装OpenJDK 10 JDK
      $sudo apt install default-jdk

    • 安装OpenJDK 8 JDK
      $sudo apt install openjdk-8-jdk
      install path /usr/lib/jvm/java-8-openjdk-amd64

    • 安装OpenJDK 8 JRE
      apt install openjdk-8-jre-headless or apt install default-jre

  • Oracle java

    • 进入官网下载
    • 选择需要的版本,进入下载页面
    • 下载完成解压
    • 创建安装目录
      本人安装目录是/usr/java下,需要cd /usr进入usr目录
      创建java文件夹,命令sudo mkdir java
      为了后续方便,将java目录赋予最高权限,命令sudo chmod 777 java
    • 复制并解压
      将下载的文件移动到java目录下
      tar -zxvf jdk-8u144-linux-x64.gz
    • 配置java
    输入sudo vim /etc/profile
    #Java安装目录
    export JAVA_HOME=/usr/java/jdk1.8.0_144
    #下面都一样啦
    export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
    export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
    export JRE_HOME=$JAVA_HOME/jre
    
    • 启用java
      source /etc/profile
    • 检查java
      java -version
  • Apt安装
    • 安装工具
      $sudo apt install software-properties-common dirmngr
    • 添加WebUpd8存储库并安装Oracle Java:
      $sudo add-apt-repository ppa:webupd8team/java
      $sudo apt update
    • 安装Oracle Java 8
      $apt-get install oracle-java8-installer
    • 多版本切换
      $sudo update-alternatives –config java

MAC多版本

vi ~/.bash_profile
#JAVA
# 实际安装路径 /Library/Java/JavaVirtualMachines
#JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home
export JAVA_8_HOME="$(/usr/libexec/java_home -v 1.8)"
export JAVA_10_HOME="$(/usr/libexec/java_home -v 10.0)"

alias jdk8='export JAVA_HOME=$JAVA_8_HOME'
alias jdk10='export JAVA_HOME=$JAVA_10_HOME'

# 默认使用JDK8
export JAVA_HOME=$JAVA_8_HOME

PATH=$JAVA_HOME/bin:$PATH:.
CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.

export PATH
export CLASSPATH

source ~/.bash_profile
use jdk8 ro jdk10  change version

Windows

JAVA_HOME:C:\Program Files\Java\jdk1.8.0_202(jdk目录路径)
CLASSPATH:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
Path:%JAVA_HOME%\bin;%PATH%
注意:如果是Windows10系统,分开写
%Java_Home%\bin
%Java_Home%\jre\bin

显示jvaa安装路径
whereis java (java安装路径)
which java (java执行路径)

echo $JAVA_HOME(java环境变量)
echo $PATH (环境变量路径)
update-alternatives –config java
jrunscript -e ‘java.lang.System.out.println(java.lang.System.getProperty(“java.home”));’ echo $JAVA_HOME

卸载

sudo apt-get autoremove default-jdk
如果不能卸载干净,用下面的方法:
jdk彻底卸载:
(1) apt-get update
(2) apt-cache search java | awk ‘{print($1)}’ | grep -E -e ‘^(ia32-)?(sun|Oracle)-java’ -e ‘^openjdk-’ -e ‘^icedtea’ -e ‘^(default|gcj)-j(re|dk)’ -e ‘^gcj-(.*)-j(re|dk)’ -e ‘java-common’ | xargs sudo apt-get -y remove
(3) apt-get -y autoremove

2、清除配置信息: dpkg -l | grep ^rc | awk ‘{print($2)}’ | xargs sudo apt-get -y purge

3、清除java配置及缓存: bash -c ‘ls -d /home/*/.java’ | xargs sudo rm -rf

4、手动清除JVMs: rm -rf /usr/lib/jvm/*

5、java -version 查看,卸载成功