Skip to content

Commit

Permalink
Functional context (#37)
Browse files Browse the repository at this point in the history
* Use Spring Fu project to improve the startup time and make the Spring Boot configuration more explicit

* remove the ResourceLoader

* Add -noverify

* Update to Spring Boot 2.1.0.RC1
  • Loading branch information
bsideup committed Oct 25, 2018
1 parent 7f50794 commit 78419bb
Show file tree
Hide file tree
Showing 33 changed files with 460 additions and 460 deletions.
22 changes: 16 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
FROM openjdk:8-jdk AS workspace

COPY . /root/project

WORKDIR /root/project
COPY gradle gradle/
COPY gradlew ./
RUN ./gradlew --no-daemon --version

ENV TERM=dumb

COPY build.gradle ./
RUN ./gradlew --info --no-daemon --console=plain downloadDependencies

RUN ./gradlew --no-daemon build -x check
COPY . .

FROM openjdk:8-jre
RUN ./gradlew --no-daemon --info --console=plain build -x check

FROM openjdk:11-jre

WORKDIR /app

RUN java -Xshare:dump

COPY --from=workspace /root/project/app/build/libs/app.jar app.jar
COPY --from=workspace /root/project/plugins/*/build/libs/*.jar plugins/

ENV JAVA_OPTS=""
ENV JAVA_MEMORY_OPTS="-XX:+ExitOnOutOfMemoryError -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:+UseG1GC -XshowSettings:vm"
ENV JAVA_MEMORY_OPTS="-XX:+ExitOnOutOfMemoryError -XshowSettings:vm -noverify"

CMD ["sh", "-c", "java $JAVA_MEMORY_OPTS $JAVA_OPTS -jar app.jar"]
CMD ["sh", "-c", "java -Xshare:on $JAVA_MEMORY_OPTS $JAVA_OPTS -jar app.jar"]
5 changes: 0 additions & 5 deletions api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
plugins {
id "java"
}

dependencies {
compileOnly 'org.projectlombok:lombok'
compileOnly 'org.springframework.boot:spring-boot-starter'

compile 'org.reactivestreams:reactive-streams'

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

24 changes: 4 additions & 20 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,38 +1,22 @@
plugins {
id "java"
}

apply plugin: 'org.springframework.boot'

test.testLogging {
displayGranularity 1
showStackTraces = true
exceptionFormat = 'full'
events "STARTED", "PASSED", "FAILED", "SKIPPED"
}

dependencies {
compileOnly 'org.projectlombok:lombok'

compile project(":api")
compile project(":protocol")

compile 'org.lognet:grpc-spring-boot-starter'
compile 'io.grpc:grpc-netty'

compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'io.micrometer:micrometer-registry-prometheus'
compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'org.springframework.fu:spring-fu-jafu'

compile 'io.grpc:grpc-netty'
compile 'io.prometheus:simpleclient_common'
compile 'org.pf4j:pf4j'

testCompileOnly 'org.projectlombok:lombok'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.testcontainers:kafka'
testCompile 'org.assertj:assertj-core'
testCompile 'org.awaitility:awaitility'

testCompile 'io.projectreactor.kafka:reactor-kafka'
}

def plugins = rootProject.allprojects.findAll { it.projectDir.parentFile.name == "plugins" }
Expand Down
125 changes: 89 additions & 36 deletions app/src/main/java/com/github/bsideup/liiklus/Application.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
package com.github.bsideup.liiklus;

import com.github.bsideup.liiklus.config.LiiklusConfiguration;
import com.github.bsideup.liiklus.config.GRPCConfiguration;
import com.github.bsideup.liiklus.config.LayersConfiguration;
import com.github.bsideup.liiklus.config.MetricsConfiguration;
import com.github.bsideup.liiklus.monitoring.MetricsCollector;
import com.github.bsideup.liiklus.plugins.LiiklusPluginManager;
import io.prometheus.client.exporter.common.TextFormat;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerInitializer;
import org.springframework.boot.autoconfigure.web.reactive.ResourceCodecInitializer;
import org.springframework.boot.autoconfigure.web.reactive.StringCodecInitializer;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.Profiles;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;

import java.nio.file.*;
import java.util.stream.Stream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Paths;
import java.util.Collections;

@Slf4j
@SpringBootApplication
Expand Down Expand Up @@ -47,47 +71,76 @@ public static SpringApplication createSpringApplication(String[] args) {
val pluginsDir = environment.getProperty("plugins.dir", String.class, "./plugins");
val pathMatcher = environment.getProperty("plugins.pathMatcher", String.class, "*.jar");

Path pluginsRoot = Paths.get(pluginsDir).toAbsolutePath().normalize();
val pluginsRoot = Paths.get(pluginsDir).toAbsolutePath().normalize();
log.info("Loading plugins from '{}' with matcher: '{}'", pluginsRoot, pathMatcher);

val pluginManager = new LiiklusPluginManager(pluginsRoot, pathMatcher);

pluginManager.loadPlugins();
pluginManager.startPlugins();

val application = new SpringApplication(
new DefaultResourceLoader() {
@Override
public ClassLoader getClassLoader() {
return new ClassLoader(Thread.currentThread().getContextClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
// FIXME X_X
for (val pluginWrapper : pluginManager.getResolvedPlugins()) {
try {
return pluginWrapper.getPluginClassLoader().loadClass(name);
} catch (ClassNotFoundException __) {
continue;
}
}

throw e;
}
val binder = Binder.get(environment);
val application = new SpringApplication(Application.class) {
@Override
protected void load(ApplicationContext context, Object[] sources) {
// We don't want the annotation bean definition reader
}
};
application.setWebApplicationType(WebApplicationType.REACTIVE);
application.setApplicationContextClass(ReactiveWebServerApplicationContext.class);
application.setEnvironment(environment);

application.addInitializers(
new StringCodecInitializer(false),
new ResourceCodecInitializer(false),
new ReactiveWebServerInitializer(
binder.bind("server", ServerProperties.class).orElseGet(ServerProperties::new),
binder.bind("spring.resources", ResourceProperties.class).orElseGet(ResourceProperties::new),
binder.bind("spring.webflux", WebFluxProperties.class).orElseGet(WebFluxProperties::new),
new NettyReactiveWebServerFactory()
),
new GRPCConfiguration(),
new LayersConfiguration(),
new MetricsConfiguration(),
(GenericApplicationContext applicationContext) -> {
applicationContext.registerBean(RouterFunctionMapping.class, () -> {
val router = RouterFunctions.route();
router.GET("/health", __ -> ServerResponse.ok().syncBody("OK"));

if (environment.acceptsProfiles(Profiles.of("exporter"))) {
val metricsCollector = applicationContext.getBean(MetricsCollector.class);
router.GET("/prometheus", __ -> {
return metricsCollector.collect()
.collectList()
.flatMap(metrics -> {
try {
val writer = new StringWriter();
TextFormat.write004(writer, Collections.enumeration(metrics));
return ServerResponse.ok()
.contentType(MediaType.valueOf(TextFormat.CONTENT_TYPE_004))
.syncBody(writer.toString());
} catch (IOException e) {
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
});
});
}
return new RouterFunctionMapping(router.build());
});
}
);

application.addInitializers(
pluginManager.getExtensionClasses(ApplicationContextInitializer.class).stream()
.map(it -> {
try {
return it.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
},
Stream
.concat(
pluginManager.getExtensionClasses(LiiklusConfiguration.class).stream(),
Stream.of(Application.class)
)
.toArray(Class[]::new)
})
.toArray(ApplicationContextInitializer[]::new)
);
application.setEnvironment(environment);

return application;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,73 @@
package com.github.bsideup.liiklus.config;

import com.github.bsideup.liiklus.service.ReactorLiiklusServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.netty.NettyServerBuilder;
import io.netty.channel.nio.NioEventLoopGroup;
import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer;
import org.springframework.context.annotation.Configuration;
import lombok.Data;
import lombok.val;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.Profiles;
import reactor.core.scheduler.Schedulers;

import java.util.concurrent.TimeUnit;

@Configuration
@GatewayProfile
public class GRPCConfiguration extends GRpcServerBuilderConfigurer {
public class GRPCConfiguration implements ApplicationContextInitializer<GenericApplicationContext> {

@Override
public void configure(ServerBuilder<?> serverBuilder) {
serverBuilder.directExecutor();

if (serverBuilder instanceof NettyServerBuilder) {
((NettyServerBuilder) serverBuilder)
.workerEventLoopGroup(new NioEventLoopGroup(Schedulers.DEFAULT_POOL_SIZE))
.permitKeepAliveTime(150, TimeUnit.SECONDS)
.permitKeepAliveWithoutCalls(true);
public void initialize(GenericApplicationContext applicationContext) {
val environment = applicationContext.getEnvironment();

if (!environment.acceptsProfiles(Profiles.of("gateway"))) {
return;
}

val binder = Binder.get(environment);

val serverProperties = binder.bind("grpc", GRpcServerProperties.class).orElseGet(GRpcServerProperties::new);

applicationContext.registerBean(ReactorLiiklusServiceImpl.class);

applicationContext.registerBean(
Server.class,
() -> {
ServerBuilder<?> serverBuilder;

if (serverProperties.isEnabled()) {
serverBuilder = NettyServerBuilder
.forPort(serverProperties.getPort())
.workerEventLoopGroup(new NioEventLoopGroup(Schedulers.DEFAULT_POOL_SIZE))
.permitKeepAliveTime(150, TimeUnit.SECONDS)
.permitKeepAliveWithoutCalls(true);
} else {
serverBuilder = InProcessServerBuilder.forName(serverProperties.getInProcessServerName());
}

return serverBuilder
.directExecutor()
.addService(applicationContext.getBean(ReactorLiiklusServiceImpl.class))
.build();
},
it -> {
it.setInitMethodName("start");
it.setDestroyMethodName("shutdownNow");
}
);
}

@Data
static class GRpcServerProperties {

int port = 6565;

boolean enabled = true;

String inProcessServerName;

}

}
Loading

0 comments on commit 78419bb

Please sign in to comment.