目录

写在前面

服务间调用常见的方案

方案一:HttpClient

方案二:RestTemplate

方案三:Feign

框架版本问题及可能遇到的坑

搭建服务注册中心

搭建服务1——service-a

搭建服务2——service-b

在service-b中通过Feign调用service-a

进阶:Feign服务间调用—传递各种参数

再次进阶:引入Hystrix熔断器

总结


写在前面

在之前的 Spring Cloud微服务入门级教程(零基础,最详细,可运行) 文章中,我们搭建了一套基于Spring Cloud的微服务项目。在那个项目中,我们有两个相同的服务提供者和一个网关(Gateway),然后客户端直接访问我们的网关,由网关转发到两个相同的服务节点。这种情况其实只适用于所有API接口都混合在同一个服务下的项目,我们只是提供了多个服务实例来达到负载均衡,并没有真正的按照功能进行拆分。

在真正的微服务项目开发中,我们绝对不只是提供多个服务实例去负载均衡,要不然直接用Nginx不就行了吗?大部分情况是根据业务维度进行拆分,所以在特定的业务场景下就会出现服务间相互调用的需求。比如服务A需要调用服务B的接口,那么我们应该如何实现呢?

服务间调用常见的方案

方案一:HttpClient

这里说的HttpClient,指的是一类产品或方案,比如还有OkHttp系列的。这种方案,是最暴力的实现方式,直接通过Java代码模拟浏览器去调用接口,也是在最早的分布式系统中所使用的,因为那时候没有服务治理框架,只能这样去调用。但是这种方式,暴力不代表不合理。在当下,依赖第三方接口的开发只能用这种方式去调用,比如微信公众号开发中的相关API接口。

这种方式呢,调用一个接口比较繁琐,大体的流程一致:先获取一个client对象,然后要构建request对象、接收response对象,如果有请求头还需要Header对象,如果是https的还要处理SSL。传参的话也不是很灵活,get请求还好说,post请求简直就是噩梦,特别是写到请求体里的body传参方式……

方案二:RestTemplate

在微服务的架构下,我们有服务治理中心,当然不会通过上面的方式去调用啦……

RestTemplate是Spring Cloud提供的一种服务间调用解决方案,它虽然利用了服务治理中心来调取服务,但是我感觉它就是穿着华丽外衣的HttpClient,看一下它的调用方式:

它在调用接口的时候,依然会让你传入一个url,只不过通过HttpClient调用接口时传入的url是一个可以外网访问的唯一http地址,而RestTemplate传入的url,是把域名换成了服务名,然后根据服务名去注册中心找对应的服务。

什么意思呢?下面看一个例子。

假如我们有一个服务,在注册中心的服务名叫做service-a,这个service-a服务下面有一个接口,地址是/student/list,假设域名www.test.com可以直接定位到service-a服务。同时注册中心有service-b,需要调用service-a的这个接口,那么如果我们通过HttpClient去调用这个服务接口,那传入的url就是http://www.test.com/student/list,这样显然没有通过我们的注册中心来获取服务;如果我们通过RestTemplate去调用,那就是http://service-a/student/list。

上面的例子,RestTemplate就会根据服务名称去注册中心寻找对应的服务,将请求转发过去。如果运用了Ribbon的话,还会实现负载均衡的效果:注册中心将所有此名称的服务实例清单返回,然后从这些服务清单中任选一个服务进行请求转发。

RestTemplate的传参、请求体的设置、请求头的设置,和HttpClient的流程如出一辙,比较繁琐,不够优雅,区别就是可以利用服务治理中心来达到了负载均衡。

方案三:Feign

接下来说说我们今天的主角,Feign。它是对RestTemplate和Ribbon的封装,让我们的服务间调用更加方便、更加简洁,实现声明式的服务调用,具体什么意思呢?

我们在使用Feign进行服务间调用时,只需要在调用方建一个interface接口,并且加上@FeignClient注解将其声明为一个服务调用客户端,并且进行相关的注解赋值,就可以调用其他服务了!

实践出真知,下面我们就来代码实战。

框架版本问题及可能遇到的坑

笔者使用的Spring Boot版本是2.2.2.RELEASE,Spring Cloud版本是Hoxton.RELEASE,JDK版本是10,不同版本的框架所使用的maven依赖可能是不同的,如果你的版本不对,可能会导致依赖下载不下来。

另外笔者用的是IDEA自带的maven,不是自己安装配置的,所以如果你也要使用的话,请在如下地方进行设置:

如果大家在搭建过程中遇到其他问题,请在下方进行评论留言或私信,我会及时查看。

搭建服务注册中心

开发工具是IDEA,怎样创建项目我就不说了,在Spring Cloud微服务入门级教程(零基础,最详细,可运行)里写的很详细。需要注意的是,在创建项目的时候要勾选上eureka依赖:

项目创建好之后,目录结构如下:

然后我们在启动类上面添加注解@EnableEurekaServer,表明这是一个服务注册中心:

package com.demo.feign.register;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication
@EnableEurekaServer
public class DemoRegisterApplication {public static void main(String[] args) {SpringApplication.run(DemoRegisterApplication.class, args);}}

然后在配置文件填写如下配置(注意我的后缀是yml,如果你的是properties请修改格式):

# 端口8080
server:port: 8080# 禁止注册自己
eureka:client:fetch-registry: falseregister-with-eureka: falseservice-url:defaultZone: http://localhost:8080/eureka/

搭建服务1——service-a

这里我们将service-a作为一个服务,待会建一个service-b来调用service-a。

这里我们选择的依赖有Web、eureka-client、lombok(需单独在IDEA进行安装),对应的依赖如下:

然后我们需要在启动类添加注解,表明这是一个服务客户端:

package com.cloud.feign.servicea;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@EnableEurekaClient
@SpringBootApplication
public class ServiceAApplication {public static void main(String[] args) {SpringApplication.run(ServiceAApplication.class, args);}}

然后在配置文件进行如下配置:

# 端口 8081
server:port: 8081# 服务名称 service-a
spring:application:name: service-a# 注册到服务中心
eureka:client:service-url:defaultZone: http://localhost:8080/eureka/

为了良好的工程习惯,我们新建一个back包,并且在包下新建Back.java类,用于统一返回数据格式:

package com.cloud.feign.servicea.back;import lombok.*;
import lombok.experimental.Accessors;import java.io.Serializable;/***  <p>*      统一返回数据*  </p>** @author 秋枫艳梦* @date 2020-01-11* */
@Builder
@ToString
@Accessors(chain = true)
@AllArgsConstructor
public class Back<T> implements Serializable {private static final long serialVersionUID = 1L;/*** 运行成功*/final String RUN_SUCCESS = "0";/*** 运行失败*/final String RUN_ERROR = "1";/*** 业务失败*/final Boolean BACK_ERROR = false;/*** 业务成功*/final Boolean BACK_SUCCESS = true;@Getter@Setterprivate String code = RUN_SUCCESS;@Getter@Setterprivate String msg = "success";@Getter@Setterprivate Boolean state = BACK_SUCCESS;@Getter@Setterprivate Long count = null;@Getter@Setterprivate T data;public Back() {super();}public Back(T data) {super();this.data = data;}public Back<T> error(String msg) {this.state = BACK_ERROR;this.msg = msg;return this;}public Back<T> msg(String msg) {this.msg = msg;return this;}
}

然后我们新建entity包,并且在此包中新建Student.java实体类:

package com.cloud.feign.servicea.entity;import lombok.Data;/***  <p>*      学生实体*  </p>** @author 秋枫艳梦* @date 2020-02-25* */
@Data
public class Student {//姓名private String name;//年龄private Integer age;
}

然后我们新建controller包,并在包下面新建StudentController.java类:

package com.cloud.feign.servicea.controller;import com.cloud.feign.servicea.back.Back;
import com.cloud.feign.servicea.entity.Student;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;/***  <p>*      学生模块,控制器*  </p>** @author 秋枫艳梦* @date 2020-02-25* */
@RestController
@RequestMapping(value = "/student")
public class StudentController {/***  简单的查询,不带任何参数* @return 响应数据* */@GetMapping(value = "/get")public Back<Student> get () {Student student = new Student();student.setName("张三");student.setAge(18);return new Back<>(student).msg("success");}
}

最终的项目结构:

搭建服务2——service-b

现在我们搭建另一个服务service-b,在此服务中将通过Feign服务间调用的方式去调用我们在service-a当中编写的接口。

新建项目时除了service-a的那些依赖,因为我们要用到Feign,所以要勾选OpenFeign依赖,所有的依赖如下:

接下来在启动类添加@EnableEurekaClient和@EnableFeignClients,前者 表明这是一个服务客户端,后者表明此客户端需要通过Feign的方式调用其他的服务,通过此注解开启对Feign功能的支持。

package com.cloud.feign.serviceb;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class ServiceBApplication {public static void main(String[] args) {SpringApplication.run(ServiceBApplication.class, args);}}

然后我们在配置文件进行如下配置:

server:port: 8082spring:application:name: service-beureka:client:service-url:defaultZone : http://localhost:8080/eureka/

至此,基本的环境就搭建好了,我们马上开始进行调用。

在service-b中通过Feign调用service-a

首先我们先在service-b项目中建一个包,叫service,并且在里面新建一个StudentService接口,用于调用service-a服务:

package com.cloud.feign.serviceb.service;import com.cloud.feign.serviceb.fallback.StudentBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;/***  <p>*      调用其他服务的声明式接口、Feign客户端*      我们使用@FeignClient注解将此接口声明为一个Feign客户端,并且通过*      value属性指定此接口要请求的服务名称,这里即是service-a*  </p>* * @author 秋枫艳梦* @date 2020-02-27* */
@FeignClient(value = "service-a")
public interface StudentService {/***  使用@GetMapping注解,表明此方法要请求service-a服务下的/student/get接口,*  相当于RestTemplate中的 http://service-a/student/get*  *  由于此处我们并没有处理service-a服务返回的结果,只是做了一下类似于请求转发,*  返回结果是service-a决定的,所以在这里指定为Object* */@GetMapping(value = "/student/get")Object get ();
}

上面的代码注释,笔者已经解释得很清楚了,当我们在service-b中的其他类(比如控制器)中调用StudentService的get()方法,那么Feign就会请求service-a服务下的/student/get接口,并将结果返回给调用者。

接下来,我们在service-b中新建一个包controller,并且在里面新建一个控制器,作为我们测试的入口:

package com.cloud.feign.serviceb.controller;import com.cloud.feign.serviceb.service.StudentService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;/***  <p>*      测试入口*  </p>* * @author 秋枫艳梦* @date 2020-02-27* */
@RestController
@RequestMapping(value = "/test")
@AllArgsConstructor
public class TestController {//装配Feign服务间调用组件private StudentService service;/***  获取数据,调用StudentService的get()方法* */@GetMapping(value = "/get")public Object get () {return service.get();}
}

现在让我们将服务注册中心、service-a、service-b全部启动,可以看到注册中心已经将服务注册进来:

我们访问在service-b中的接口,即localhost:8082/test/get ,如果没问题的话应该会把service-a中的接口数据返回给我们,看一下效果:

可以看到我们已经通过Feign实现了简单的服务间调用,而且是不是比RestTemplate简单好多? 而且,Feign也集成来Ribbon,默认是支持负载均衡的,如果你有多个service-a实例,并且让这些实例返回不同的数据,你就会看到效果。

进阶:Feign服务间调用—传递各种参数

上面我们只是实现了最简单的get请求,而且什么参数都没有传,真正的项目环境中可要复杂的多,我们的service-b服务在调用service-a服务的时候可能要传递各种参数,下面我们就实现各种传参的调用示例。

先总结一下我们常用的传参方式:

  • RequestParam,也是最基本的传参,直接跟在路径后面,如http://www.xxx.com/list?id=1&name=张三
  • RequestBody,经常用于POST或PUT请求,新增或编辑数据时使用,是一种JSON请求体的参数
  • PathVariable,在RESTful风格的路径中比较常见,比如删除id为1的学生:http://www.xxx.com/student/del/1
  • RequestHeader,即将参数写到请求头中,多用于token、身份验证数据或敏感数据

Feign中各个服务之间传递参数的方式,跟平常的controller层入参基本一样,所以特别浅显易懂。接下来,笔者将带领大家实现这四种传参方式,基本可以满足开发中95%以上的使用场景。

首先,我们在service-a中的controller层扩展几个方法,分别涵盖上面的四种参数传递方式:

注意这里的代码是service-a中的StudentController.java!!!

package com.cloud.feign.servicea.controller;import com.cloud.feign.servicea.back.Back;
import com.cloud.feign.servicea.entity.Student;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;/***  <p>*      学生模块,控制器*  </p>** @author 秋枫艳梦* @date 2020-02-25* */
@RestController
@RequestMapping(value = "/student")
public class StudentController {/***  简单的查询,不带任何参数* @return 响应数据* */@GetMapping(value = "/get")public Back<Student> get () {Student student = new Student();student.setName("张三");student.setAge(18);return new Back<>(student).msg("success");}/***  RequestParam参数* @param id 接收到的id* @return 响应数据* */@GetMapping(value = "/get/by/id")public Back<Integer> getById (@RequestParam(value = "id") Integer id) {//为了方便测试观察,直接将id返回去return new Back<>(id).msg("根据ID查询成功");}/***  RequestBody请求体参数* @param student 学生实体* @return 响应数据* */@PostMapping(value = "/save")public Back<Student> save (@RequestBody Student student) {//为了方便测试观察,直接将实体返回去,你也可以在控制台进行打印输出return new Back<>(student).msg("保存成功");}/***  PathVariable参数* @param id 接收到的id* @return 响应数据* */@DeleteMapping(value = "/delete/{id}")public Back<Integer> delete (@PathVariable Integer id) {return new Back<>(id).msg("删除成功");}/***  请求头参数,从请求头中获取相应的参数* @param request 请求对象* @return 响应数据* */@GetMapping(value = "/token")public Back<String> header (HttpServletRequest request) {return new Back<>(request.getHeader("token")).msg("success");}
}

接下来我们在service-b中的StudentService.java,也扩展一下,和上面的对应上,注意看注释:

这里需要将service-a中的实体Student,拿到service-b中一份

package com.cloud.feign.serviceb.service;import com.cloud.feign.serviceb.entity.Student;
import com.cloud.feign.serviceb.fallback.StudentBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;/***  <p>*      调用其他服务的声明式接口、Feign客户端*      我们使用@FeignClient注解将此接口声明为一个Feign客户端,并且通过*      value属性指定此接口要请求的服务名称,这里即是service-a*  </p>** @author 秋枫艳梦* @date 2020-02-27* */
@FeignClient(value = "service-a")
public interface StudentService {/***  使用@GetMapping注解,表明此方法要请求service-a服务下的/student/get接口,*  相当于RestTemplate中的 http://service-a/student/get**  由于此处我们并没有处理service-a服务返回的结果,只是做了一下类似于请求转发,*  返回结果是service-a决定的,所以在这里指定为Object* */@GetMapping(value = "/student/get")Object get ();/***  Feign调用其他服务时,@RequestParam必须显式声明参数名,否则会报错。*  我们在参数前面加上注解,就像Controller层接收参数一样,这样Feign在*  调用服务时就会以对应的注解方式进行传参,假设id为1,这里的效果等价于:*  http://service-a/student/get/by/id?id=1*  * @param id ID* */@GetMapping(value = "/student/get/by/id")Object getById (@RequestParam(value = "id") Integer id);/***  由于我们此处用的注解是@PostMapping,那么这个方法被调用时,其实就是以*  POST的形式调用了 http://service-a/student/save 接口,并且将接收到的*  实体以JSON请求体的方式传递过去*  * @param student 数据实体* */@PostMapping(value = "/student/save")Object save (@RequestBody Student student);/***  同上,Feign会先识别出此处的方法注解是@DeleteMapping,就会以delete方式*  请求 http://service-a/student/delete/接口,又因为我们使用了@PathVariable*  注解,所以假设当参数为1时,等价于:*  http://service-a/student/delete/1*  * @param id ID* */@DeleteMapping(value = "/student/delete/{id}")Object delete (@PathVariable Integer id);/***  由于此处使用了@RequestHeader注解,Feign在调用/student/token接口*  时会把token写到请求头中,并且被调用者可以通过HttpServletRequest对*  象获取到这个参数* * @param token token* */@GetMapping(value = "/student/token")Object headers (@RequestHeader(value = "token") String token);
}

然后我们继续修改service-b中的TestController.java:

package com.cloud.feign.serviceb.controller;import com.cloud.feign.serviceb.entity.Student;
import com.cloud.feign.serviceb.service.StudentService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;/***  <p>*      测试入口*  </p>** @author 秋枫艳梦* @date 2020-02-27* */
@RestController
@RequestMapping(value = "/test")
@AllArgsConstructor
public class TestController {//装配Feign服务间调用组件private StudentService service;/***  获取数据,调用StudentService的get()方法* */@GetMapping(value = "/get")public Object get () {return service.get();}@GetMapping(value = "/get/by/id")public Object getById (@RequestParam(value = "id") Integer id) {return service.getById(id);}@PostMapping(value = "/save")public Object save (@RequestBody Student student) {return service.save(student);}@DeleteMapping(value = "/delete/{id}")public Object delete (@PathVariable Integer id) {return service.delete(id);}@GetMapping(value = "/token")public Object header (HttpServletRequest request) {return service.headers(request.getHeader("token"));}
}

现在让我们开始使用Postman进行测试各个接口:

通过上面的结果,我们已经大功告成啦!

再次进阶:引入Hystrix熔断器

我们已经通过Feign实现了服务间的调用,但是服务和服务之间的这种依赖关系,可能一个服务挂了,另一个依赖它的服务也会受影响。我们要有一种机制,如果在调用某个服务的时候请求出错或者超时,要及时熔断,避免大量请求被阻塞,同时我们可能希望记录一下日志,或者将数据回滚,就像try-catch那样。

在之前的文章中笔者介绍过Gateway网关配合Hystrix的使用,但是我们这里没有走网关,走的是服务间内部调用,那该怎么办呢?强大的Feign对Hystrix也有很好的支持,就拿本文的例子来说:

我们可以声明一个类,比如叫StudentBack,用来执行service-a中的student相关接口异常时的回滚操作,我们只需要用这个类实现我们的Feign客户端StudentService接口,并且在注解上通过fallback指定StudentBack.class,那么出现异常或者超时,将会执行StudentBack中的相关方法。看一个例子!

我们在service-b中引入Back类,用于返回信息,同时建一个fallback包,然后建一个处理类StudentBack.java:

package com.cloud.feign.serviceb.fallback;import com.cloud.feign.serviceb.back.Back;
import com.cloud.feign.serviceb.entity.Student;
import com.cloud.feign.serviceb.service.StudentService;
import org.springframework.stereotype.Component;/***  <p>*      调用service-a失败时的熔断处理方法*      使用@Component注解声明这个类为Bean*  </p>** @author 秋枫艳梦* @date 2020-02-27* */
@Component
public class StudentBack  implements StudentService {/***  调用http://service-a/student/get失败时的处理方法** */@Overridepublic Object get() {return new Back<>().error("内部系统异常");}@Overridepublic Object getById(Integer id) {return null;}@Overridepublic Object save(Student student) {return null;}@Overridepublic Object delete(Integer id) {return null;}@Overridepublic Object headers(String token) {return null;}
}

上面只以一个get()方法为例。因为StudentService是一个interface接口,而这里的StudentBack是一个实现了StudentService接口的类,所以每当一个StudentService中的方法执行失败时(也就是调用其他服务的接口失败时),都会执行StudentBack中相应的实现方法,这样我们就可以为每一个请求的失败进行定制化处理,记录日志、重试、数据回滚……

然后不要忘了在我们的StudentService中的注解上指定fallback:

最后还需要在配置文件中开启Feign对Hystrix的支持:

然后我们在service-a中的接口,制造一个异常:

重启服务,进行测试,访问service-b中的接口:

以上,我们就实现了异常情况下的处理。

如果要配置超时熔断,则将service-b的配置文件修改如下:

server:port: 8082spring:application:name: service-beureka:client:service-url:defaultZone : http://localhost:8080/eureka/feign:hystrix:enabled: true# ribbon级超时配置
ribbon:ReadTimeout: 3000ConnectTimeout: 3000# hystrix级超时配置
hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 6000

以上就是关于超时的配置,如果service-a的controller中睡眠8秒,就可以看到效果,关于这里的超时配置,参考的是下面的这篇文章,这个博主说的很好:

https://blog.csdn.net/east123321/article/details/82385816

总结

今天的文章到这里就结束了,希望可以帮助到大家。如果有什么问题,可以给我评论留言或者私信,我会及时查看,欢迎大家多来交流。

另外,给大家贴出来Spring Cloud Feign的官方文档,里面有一些新的玩法大家可以研究一下:

https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html

Spring Cloud微服务之Feign——声明式服务间调用相关推荐

  1. SpringCloud Feign声明式服务调用

    SpringCloud Feign声明式服务调用 1. 加入pom依赖 2. Application.java上声明@EnableFeignClients 3. @FeignClient声明接口调用服 ...

  2. spring cloud 微服务调用--ribbon和feign调用

    这里介绍ribbon和feign调用两种通信服务调用方式,同时介绍如何引入第三方服务调用.案例包括了ribbon负载均衡和hystrix熔断--服务降级的处理,以及feign声明式服务调用.例子包括s ...

  3. idea 构建java 微服务_使用 IDEA 从 0 开始搭建 Spring Cloud 微服务

    以下内容均来源于一个微服务初学者的实践,仅供参考. 微服务架构 首先启动 Spring Cloud Eureka 注册中心,其他部分都作为服务注册到 Eureka ,并通过注册的服务名互相访问.Spr ...

  4. 实战系列-Spring Cloud微服务中三把利器Feign、Hystrix、Ribbon

    导语   在之前的分享中分享过关于Fegin的底层实现原理,以及Spring Cloud OpenFegin的启动原理.在这次的分享中主要总结一下Spring Cloud 微服务架构的三把利器.对于F ...

  5. 通过Feign实现Spring Cloud微服务调用

    我们在上一篇文章通过restTemplate实现Spring cloud微服务的调用中介绍了spring cloud微服务的一种调用方式,本文我们介绍另一种调用spring cloud微服务的方式-- ...

  6. Spring Cloud微服务系列文,服务调用框架Feign

    之前博文的案例中,我们是通过RestTemplate来调用服务,而Feign框架则在此基础上做了一层封装,比如,可以通过注解等方式来绑定参数,或者以声明的方式来指定请求返回类型是JSON.    这种 ...

  7. spring cloud微服务分布式云架构 - Spring Cloud集成项目简介

    Spring Cloud集成项目有很多,下面我们列举一下和Spring Cloud相关的优秀项目,我们的企业架构中用到了很多的优秀项目,说白了,也是站在巨人的肩膀上去整合的.在学习Spring Clo ...

  8. Spring Cloud微服务实战pdf

    下载地址:网盘下载 内容提要 编辑 <Spring Cloud微服务实战>从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件 ...

  9. Spring Cloud 微服务架构的五脏六腑!

    来源:webfe.kujiale.com/spring-could-heart/ 整理:Java技术栈(公众号ID:javastack) Spring Cloud 是一个基于 Spring Boot ...

最新文章

  1. java中utilities类_Java PHUtilities类代码示例
  2. android 新浪微博的点赞功能实现,Android PraiseTextView实现朋友圈点赞功能
  3. pyinstaller打包exe在其他机器无法运行_详解pyinstaller selenium python3 chrome打包问题!解决率100%...
  4. JavaScript 笔记Day1
  5. CSS的样式合并与模块化
  6. java session 使用_浅谈Session的使用(原创)
  7. IDEA安装MySQL版本以及驱动jar包下载问题
  8. 利用RemoteJoy进行usb视频输出教程(无需IRshell)
  9. linux如何查询内存型号,查看linux 查看内存型号
  10. wifi分析仪怎么看哪个信道好_WiFi信道扫描仪:通过NetSpot选择最佳WiFi信道
  11. linux下打开.mpp文件(微软project)
  12. 天地图2021版正式发布,清晰程度非常了得
  13. 什么是配置管理?配置管理由专人负责吗?
  14. Java体系知识之ElementUI
  15. 固态硬盘能不能提高计算机速度,固态硬盘—提升电脑速度最有效的利器!
  16. java 小球抛物线_vue 2.0 购物车小球抛物线
  17. RabbitMQ 安装使用,Centos系统安装RabbitMQ、Docker安装启动RabbitMQ
  18. 随机变量的相互独立性
  19. 2bc-gskew:De-aliased hybrid branch predictors(1999)
  20. AI如何实现安全生产智能监控

热门文章

  1. 公司注册需要什么印章?企业刻印印章的目的是什么?
  2. UVa 1025 (DAG 上的动态规划,有固定终点的最短时间,逆推法)
  3. 家用电器的CCC认证流程
  4. 自顶向下计算机网络 传输层
  5. 信号与系统_第1章 信号与系统
  6. 计算机基础——进制与数据编码
  7. [bzoj1812][ioi2005]riv(树上dp)
  8. STM32----FLASH和EEPROM的区别
  9. 北邮计算机学院研究生信息官网,北京邮电大学
  10. 使用Hexo免费搭建个人博客教程