网站首页 > java教程 正文
本文来学一下openFeign,openFeign也是微服务中常用的一个服务调用方式,接下来我们结合下面这五个大方面来学习一下。生命不止,学习不止~~
1、概述
OpenFeign是什么
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
能干嘛
Feign旨在使编写Java Http客户端变得更容易。也就是说,feign来简化我们发起远程调用的代码的,那简化到什么程度呢?简化成就像调用本地方法那样简单。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它,在相关的微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定。
Feign和OpenFeign两者区别
OpenFeign 组件的前身是 Netflix Feign 项目,它最早是作为 Netflix OSS 项目的一部分,由 Netflix 公司开发。后来 Feign 项目被贡献给了开源组织,于是才有了我们今天使用的 Spring Cloud OpenFeign 组件。
Feign 和 OpenFeign 有很多大同小异之处,不同的是 OpenFeign 支持 MVC 注解。可以认为 OpenFeign 为 Feign 的增强版。
2、项目演示
本文我们主要是为了演示openfeign是如何调用的,所以我们还以我们原来搭建的服务为基础,使用eureka作为服务注册中心。
前面文章我们已经学习过如何搭建eureka服务中心,如何注册服务到eureka,所以在这个地方就不在着重进行介绍。只进行一个简单的说明。
服务提供者配置
server:
servlet:
context-path: producer-service
port: 8001
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
# 集群版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: producer-8001
#访问路径可以显示IP地址
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
#lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#lease-expiration-duration-in-seconds: 2
服务消费者
pom
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug
启动类
增加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class FeignConsumer {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
服务调用
@FeignClient(value = "producer-service", fallbackFactory = DemoProviderFeignClientFallbackFactory.class)
@RequestMapping("/service-a/test")
public interface TestApi {
@GetMapping("/get/{id}")
TestVO testGet(@PathVariable(name = "id") Long id);
}
请求端
@RestController
@RequestMapping("/sample")
public class SampleController {
@Autowired
TestApi testApi;
@GetMapping("/get/{id}")
public TestVO testGet(@PathVariable(name = "id")Long id) {
return testApi.testGet(id);
}
}
以上就搭演示完了我们使用openfeign调用的简易demo,当然啦,openfeign肯定提供的不单单只有这么多功能。我们先来看一下openfeign都有哪些常用配置,后面我们再详细的进行介绍。
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 1000
readTimeout: 1000
logger-level: BASIC
producer-service:
connectTimeout: 10000
readTimeout: 10000
logger-level: FULL
# Feign Apache HttpClient 配置项,对应 FeignHttpClientProperties 配置属性类
httpclient:
enabled: true # 是否开启。默认为 true
max-connections: 200 # 最大连接数。默认为 200
max-connections-per-route: 50 # 每个路由的最大连接数。默认为 50。router = host + port
okhttp:
enabled: false
## 开启压缩
compression:
request:
enabled: true
## 开启压缩的阈值,单位字节,默认2048,即是2k,这里为了演示效果设置成1字节
min-request-size: 1
mime-types: text/xml,application/xml,application/json
response:
enabled: true
sentinel:
enabled: true
3、超时控制
在上面的配置中我们已经写了如何配置超时时间了,下面我们就来详细介绍一下。
openFeign设置超时时间非常简单,只需要在配置文件中配置,如下:
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 1000
readTimeout: 1000
default设置的是全局超时时间,对所有的openFeign接口服务都生效,如果有的服务调用相对来说比较慢,我们也可以针对服务进行单独的配置。
feign:
client:
config:
## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
default:
connectTimeout: 5000
readTimeout: 5000
## 为producer-service这个服务单独配置超时时间
producer-service:
connectTimeout: 30000
readTimeout: 30000
4、开启日志增强
openFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。
openFeign的日志级别如下:
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
配置起来也很简单,步骤如下:自定义一个配置类,在其中设置日志级别,如下:
@Configuration
@ConditionalOnProperty(value = "spring.profiles.active", havingValue = "dev")
@Slf4j
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
log.info("配置feign日志");
return Logger.Level.FULL;
}
}
如果你仔细观察我们在上面列出的常用配置就会发现,日志增加我们也可以通过配置文件进行配置。
5、底层设计原理
前面我们说的都是openFeign如何使用的,接下来我们来说一下openFeign的底层实现。
我们先来看一下http的请求过程,因为 OpenFeign 是声明式的 HTTP 客户端,提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
5.1 http请求流程:
openFeign的本质也是通过http进行调用的,采用的动态代理的方式来生成了实现类。我们先在这里换一下openFeign的工作流程,然后再来一步一步的进行解析底层实现原理。
5.2 openFeign工作流程:
- 在 Spring 项目启动阶段,服务 A 的OpenFeign 框架会发起一个主动的扫包流程。从指定的目录下扫描并加载所有被 @FeignClient 注解修饰的接口,然后将这些接口转换成 Bean,统一交给 Spring 来管理。
- 根据这些接口会经过 MVC Contract 协议解析,将方法上的注解都解析出来,放到 MethodMetadata 元数据中。
- 基于上面加载的每一个 FeignClient 接口,会生成一个动态代理对象,指向了一个包含对应方法的 MethodHandler 的 HashMap。生成的动态代理对象会被添加到 Spring 容器中,并注入到对应的服务里。
- 调用方调用接口,准备发起远程调用。从动态代理对象 Proxy 中找到一个 MethodHandler 实例,生成 Request,包含有服务的请求 URL(不包含服务的 IP)。
- 经过负载均衡算法找到一个服务的 IP 地址,拼接出请求的 URL。
- 拦截器负责对请求和返回进行装饰处理。
- 发送Http请求,服务提供者进行业务逻辑处理。
上述的流程只是其中一些比较重要的步骤,并不是完整的的处理流程。如果对完整的流程的同学可以自己去看看源码,接下来我们就针对上述的一些步骤进行一下深度的学习。
5.3 OpeFeign 包扫描 @EnableFeignClients
我们在使用openfeign的时候,会在启动类上加上这样一个注解:@EnableFeignClients,我们从字面意思也可以看出来这个注解是开启openfeign功能的。
1> 点进去@EnableFeignClients注解我们会发现它使用了@Spring 框架的 @Import 注解导入了 FeignClientsRegistrar 类,开始了 OpenFeign 组件的加载。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
2> FeignClientsRegistrar 类中的 registerBeanDefinitions 方法负责 Feign 接口的加载。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册默认配置
registerDefaultConfiguration(metadata, registry);
//注册feignClients
registerFeignClients(metadata, registry);
}
3> registerFeignClients 扫描指定,查找指定路径 basePackage 的所有带有 @FeignClients 注解的带有 @FeignClient 注解的类、接口。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
//指定包的扫描路径
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
//判断是否是带有注解的 Bean。
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
//判断是否是接口
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取feignclient的接口合集
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
5.4 注册 FeignClient 到 Spring
还是在 registerFeignClients 方法中,当 FeignClient 扫描完后,就要为这些 FeignClient 接口生成一个动态代理对象。进到这个方法里面,可以看到这一段代码:
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject();
});
核心就是 FeignClientFactoryBean 类,根据类的名字我们可以知道这是一个工厂类,用来创建 FeignClient Bean 的。
我们先来通过下面一个示例来看下是如何创建feignClient的。
@FeignClient(name = ServerConstant.FLOW_SERVICE, fallbackFactory = RemoteFlowFallbackFactory.class)
public interface RemoteFlowService {
/**
* 流程退回
*
* @param token
*/
@PostMapping(value = "/backCommit")
void backCommit(@RequestBody Map<String, Object> data,
@RequestHeader(value = "token", required = true) String token) throws Throwable;
/**
* 发起流程
**/
@PostMapping(value = "/startFlow")
R startFlow(@RequestBody FlowStartParam flowStartParam);
/**
* 流程审核通过
**/
@PostMapping(value = "/auditPass")
R auditPass(@RequestParam("uid") Integer uid, @RequestParam("utype") Integer utype);
}
过程大致:
- 解析 @FeignClient 定义的属性。
- 将注解@FeignClient 的属性 + 接口 RemoteFlowService 的信息构造成一个 RemoteFlowService 的 beanDefinition。
- 然后将 beanDefinition 转换成一个 BeanDefinitionHolder,这个 holder 就是包含了 beanDefinition, alias, beanName 信息。
- 最后将这个 holder 注册到 Spring 容器中。
源码如下:
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean
.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class
? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(),
null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
String alias = contextId + "FeignClient";
//生成beanDefinition
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
// 转换成holder,包含了beanDefinition,alias,beanName信息
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
//注册到 Spring 上下文中。
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
后面服务要调用接口的时候,就可以直接用 FeignClient 的接口方法了,但是我们并没有细讲这个 FeignClient 的创建细节,下面我们看下 FeignClient 的创建细节,这个也是 OpenFeign 核心原理。
5.5 动态代理分析
上面的源码解析中我们也提到了是由这个工厂类 FeignClientFactoryBean 来创建 FeignCient Bean,在创建 FeignClient Bean 的过程中就会去生成动态代理对象。调用接口时,其实就是调用动态代理对象的方法来发起请求的。
分析动态代理的入口方法为 getObject( ),接着调用 target 方法
@Override
public Object getObject() {
return getTarget();
}
<T> T getTarget() {
FeignContext context = beanFactory != null
? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
//判断feignclient的url属性是否为空
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
...
}
因为我们在上述那个 @FeignClient 注解的例子中是使用 name 而不是 url,所以会执行负载均衡策略的分支。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
Client: Feign 发送请求以及接收响应等都是由 Client 完成,该类默认 Client.Default,另外支持 HttpClient、OkHttp 等客户端
DefaultTargeter 和 HystrixTargeter。而不论是哪种 target,都需要去调用 Feign.java 的 builder 方法去构造一个 feign client。
在构造的过程中,依赖 ReflectiveFeign 去构造。源码如下:
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
...
}
public <T> T target(Target<T> target) {
return this.build().newInstance(target);
}
//创建反射类 ReflectiveFeign,然后执行创建实例类
public Feign build() {
Client client = (Client)Capability.enrich(this.client, this.capabilities);
Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {
return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
}).collect(Collectors.toList());
Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
Options options = (Options)Capability.enrich(this.options, this.capabilities);
Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
ReflectiveFeign 做的工作就是为带有 @FeignClient 注解的接口,创建出接口方法的动态代理对象。
this.build().newInstance(target)方法对 @FeignClient 修饰的接口中 SpringMvc 等配置进行解析转换,对接口类中的方法进行归类,生成动态代理类。
public <T> T newInstance(Target<T> target) {
//处理 @FeignCLient 注解(SpringMvc 注解等)封装为 MethodHandler 包装类
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//方法 和 MethodHandler 的对应关系
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//根据target 和 methodToHandler 创建 InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//创建 代理类 proxy
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
原理图:
- 解析 FeignClient 接口上各个方法级别的注解,比如远程接口的 URL、接口类型(Get、Post 等)、各个请求参数等。
- 然后将解析到的数据封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理。相当于把服务的请求地址、接口类型等都帮我们封装好了。这些 MethodHandler 方法会放到一个 HashMap 中。
- 然后会生成一个 InvocationHandler 用来管理这个 hashMap,其中 Dispatch 指向这个 HashMap。
- 然后使用 Java 的 JDK 原生的动态代理,实现了 FeignClient 接口的动态代理 Proxy 对象。这个 Proxy 会添加到 Spring 容器中。
- 当要调用接口方法时,其实会调用动态代理 Proxy 对象的 methodHandler 来发送请求。
6、总结
到这里我们也就可以 openFeign 的工作方式了。大致如下:
- 在我们调用 @FeignClient 接口时,会被 FeignInvocationHandler#invoke 拦截,并在动态代理方法中执行下述逻辑。
- 接口注解信息封装为 HTTP Request。
- 对服务列表进行负载均衡调用(服务名转换为 ip+port)。
- 请求调用后,将返回的数据封装为 HTTP Response,继而转换为接口中的返回类型。
猜你喜欢
- 2025-09-29 《深入理解Spring》事务管理——数据一致性的守护者
- 2025-09-29 俄称今年已控制205个居民点 乌称对俄港口及战机发动袭击
- 2025-09-29 山东首例!高支模监测系统护航邹济上跨京沪铁路立交桥成功转体
- 2025-09-29 新华全媒+丨追梦空天 制胜未来——写在空军航空开放活动和长春航空展闭幕之际
- 2025-09-29 SpringBoot实现RPC调用的本地代理模式
你 发表评论:
欢迎- 最近发表
-
- JUC系列之《CompletableFuture:Java异步编程的终极武器》
- SpringBoot+Jasync异步化改造狂降90%耗时,百万并发下的性能杀戮
- Java异步编程神器:CompletableFuture实战技巧
- Spring Boot 异步请求 + 虚拟线程性能提升?结果很意外
- 异步可以单线程,但高并发的异步肯定要用线程池
- Java线程实现原理及相关机制_java线程的实现
- java线程终止 interrupt 关键字详解
- Java处理百万级消息积压方案_java 实时处理亿级数据
- 阻塞模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?
- 安卓7系统设置永不休眠_android 设置永不休眠
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)