├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .gitpod.yml ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.adoc ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── example │ │ ├── boot │ │ └── BootApplication.java │ │ ├── config │ │ ├── ApplicationBuilder.java │ │ ├── BeanCountingApplicationListener.java │ │ ├── LazyInitBeanFactoryPostProcessor.java │ │ ├── ShutdownApplicationListener.java │ │ └── StartupApplicationListener.java │ │ ├── demo │ │ └── DemoApplication.java │ │ ├── empt │ │ └── EmptyApplication.java │ │ ├── func │ │ ├── BuncApplication.java │ │ ├── CuncApplication.java │ │ └── FuncApplication.java │ │ ├── manual │ │ └── ManualApplication.java │ │ ├── micro │ │ └── MicroApplication.java │ │ ├── mini │ │ └── MiniApplication.java │ │ └── reactor │ │ └── ReactorApplication.java └── resources │ ├── META-INF │ ├── spring.factories │ ├── thin-actj.properties │ ├── thin-actr.properties │ ├── thin-empt.properties │ ├── thin-jack.properties │ ├── thin-jdbc.properties │ └── thin.properties │ └── application.properties └── test └── java └── com └── example ├── bench ├── CdsBenchmark.java ├── MicroBenchmark.java ├── ProcessLauncherState.java ├── ProcessLauncherStateTests.java ├── ProfileBenchmark.java ├── VirtualMachineMetrics.java └── VirtualMachineMetricsTests.java ├── demo ├── DemoApplicationTests.java ├── RestConfiguration.java └── StaticApplicationTests.java ├── micro └── MicroApplicationTests.java └── mini └── MiniApplicationTests.java /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/java/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 11, 17, 11-bullseye, 17-bullseye, 11-buster, 17-buster 4 | ARG VARIANT=17-bullseye 5 | FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} 6 | 7 | ARG USER=vscode 8 | 9 | VOLUME /home/$USER/.m2 10 | 11 | RUN bash -lc '. /usr/local/sdkman/bin/sdkman-init.sh && sdk install java 19-amzn && sdk use java 19-amzn' 12 | RUN sudo mkdir /home/$USER/.m2 && sudo chown $USER:$USER /home/$USER/.m2 13 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loom-playground", 3 | "dockerFile": "Dockerfile", 4 | "runArgs": [ 5 | "--cap-add=SYS_PTRACE", 6 | "--security-opt", 7 | "seccomp=unconfined", 8 | "--mount", 9 | "type=bind,source=${env:HOME}/.m2,target=/home/vscode/.m2" 10 | ], 11 | "initializeCommand": "mkdir -p ${env:HOME}/.m2", 12 | "postCreateCommand": "sudo chown vscode:vscode /home/vscode/.m2", 13 | "remoteUser": "vscode", 14 | "features": { 15 | "docker-in-docker": "latest" 16 | }, 17 | "extensions": [ 18 | "vscjava.vscode-java-pack", 19 | "redhat.vscode-xml", 20 | "Pivotal.vscode-boot-dev-pack", 21 | "mhutchie.git-graph", 22 | // N.B. might need to install the pre-release version manually 23 | "redhat.java" 24 | ], 25 | "settings": { 26 | "java.server.launchMode": "Standard", 27 | "java.jdt.ls.java.home": "/usr/local/sdkman/candidates/java/current" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### VSCode ### 5 | .vscode/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .attach_* 15 | .sts4-cache/ 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | nbproject/private/ 25 | build/ 26 | nbbuild/ 27 | dist/ 28 | nbdist/ 29 | .nb-gradle/ 30 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-mysql 2 | tasks: 3 | - init: ./mvnw install -DskipTests 4 | vscode: 5 | extensions: 6 | - vscjava.vscode-java-pack@0.8.1:LRImBn//d5JhH4PUEI1BaQ== 7 | - vscjava.vscode-java-debug@0.23.0:3ARqL3kPh1J1SwpVjYUjqw== 8 | - vscjava.vscode-java-test@0.22.0:BlvjRRJyZszeJzIS+xEHIA== 9 | - redhat.java@0.54.2:Q60n5quUtfd1EcwzkRq96A== 10 | - vscjava.vscode-maven@0.20.1:gdyOPlzH3PU5IkrigIg85g== 11 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsyer/spring-boot-micro-apps/b7be681d44217139bbea26cc8d580c6bdbeaeb27/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | [.lead] 2 | This project shows how to build "micro" applications with Spring and Spring Boot - the smallest, functional units, with the fewest dependencies and fastest start times 3 | 4 | image::https://gitpod.io/button/open-in-gitpod.svg[Open in Gitpod,link="https://gitpod.io/#https://github.com/dsyer/spring-boot-micro-apps"] 5 | 6 | == Building the Apps 7 | 8 | Build the jar: 9 | 10 | ``` 11 | $ ./mvnw clean install -DskipTests 12 | ``` 13 | 14 | Run it 15 | 16 | ``` 17 | $ java -jar target/micro-0.0.1-SNAPSHOT.jar 18 | ... 19 | 2018-05-23 10:03:41.776 [main] INFO com.example.demo.DemoApplication - Started DemoApplication in 1.885 seconds (JVM running for 3.769) 20 | ... 21 | ``` 22 | 23 | The default app is relatively full-bodied - it uses Spring Boot and auto-configuration. 24 | 25 | You can run the smaller apps using command line flags: 26 | 27 | ``` 28 | $ java -jar target/micro-0.0.1-SNAPSHOT.jar --thin.main=com.example.mini.MiniApplication 29 | ... 30 | May 23, 2018 10:05:41 AM reactor.util.Loggers$Slf4JLogger info 31 | INFO: Started HttpServer on /127.0.0.1:8080 32 | ``` 33 | 34 | The main class could be one of 35 | 36 | |=== 37 | | Main | Description | 38 | | com.example.demo.DemoApplication | (The default). Spring Boot with Webflux and full autoconfiguration | 39 | | com.example.manual.ManualApplication| Spring Boot with Webflux but manually importing autoconfiguration using `@ImportAutoConfiguration` | 40 | | com.example.boot.BootApplication | Spring Boot with Webflux and `RouterFunction` but no autoconfiguration | 41 | | com.example.func.FuncApplication | Spring (not Boot) with Webflux and `RouterFunction` using manual functional bean registration | 42 | | com.example.func.BuncApplication | Spring Boot with Webflux and `RouterFunction` using manual functional bean registration | 43 | | com.example.mini.MiniApplication | Raw Spring, no autoconfiguration, but using `@Configuration` and `RouterFunction` | 44 | | com.example.micro.MicroApplication | Raw Spring with no `@Configuration` | 45 | 46 | |=== 47 | 48 | The `thin.profile` could be empty (the default) for just `spring-boot-starter-webflux` dependencies, or you can try `jack` for a more "normal" set of dependencies, e.g. with `hibernate-validator`, `jackson`, and `logback`. Other choices add more dependencies (e.g. `actr` for `spring-boot-starter-actuator`). You will need the "jack" profile to use actuators, so `--thin.profile=actr,jack`. 49 | 50 | The apps are already systematically benchmarked here with regular JVMs: https://github.com/dsyer/spring-boot-startup-bench/blob/master/flux/README.adoc. The point of this project is just to extract them out of the JMH benchmarks, and make them easily executable as standalone apps. In the benchmarks, the "jack" profile is the default, and "jlog" is the faster reduced set of dependencies. 51 | 52 | == Benchmarks 53 | 54 | Spring Boot 3.0.0-SNAPSHOT (Java 17): 55 | 56 | ``` 57 | class method sample beans classes heap memory median mean range 58 | MicroBenchmark main empt 43.000 4081.000 6.982 33.689 0.568 0.593 0.046 59 | MicroBenchmark main demo 122.000 5662.000 10.669 48.869 0.849 0.901 0.085 60 | MicroBenchmark main manl 57.000 5035.000 9.142 44.431 0.633 0.665 0.031 61 | MicroBenchmark main func 44.000 3921.000 7.310 36.758 0.435 0.464 0.023 62 | CdsBenchmark main empt 43.000 4161.000 7.077 24.739 0.376 0.399 0.037 63 | CdsBenchmark main demo 121.000 6081.000 11.122 30.677 0.497 0.521 0.047 64 | CdsBenchmark main manl 57.000 5669.000 9.871 29.126 0.393 0.410 0.021 65 | ``` 66 | 67 | with AOT: 68 | 69 | ``` 70 | class method sample beans classes heap memory median mean range 71 | MicroBenchmark main demo 118.000 5296.000 9.448 53.407 0.785 0.814 0.026 72 | CdsBenchmark main demo 118.000 5917.000 10.576 30.216 0.418 0.440 0.032 73 | ``` 74 | 75 | Java 19: 76 | 77 | ``` 78 | class method sample beans classes heap memory median mean range 79 | CdsBenchmark main empt 43.000 4313.000 8.173 26.725 0.363 0.387 0.040 80 | CdsBenchmark main demo 121.000 6193.000 10.888 30.987 0.496 0.523 0.032 81 | CdsBenchmark main manl 57.000 5817.000 10.675 30.585 0.376 0.410 0.056 82 | ``` 83 | 84 | Spring Boot 2.7.3 (Java 17): 85 | 86 | ``` 87 | class method sample beans classes heap memory median mean range 88 | MicroBenchmark main empt 43.000 4025.000 6.862 33.436 0.562 0.586 0.030 89 | MicroBenchmark main demo 122.000 5548.000 10.442 48.377 0.848 0.902 0.056 90 | MicroBenchmark main manl 57.000 4998.000 9.005 44.235 0.634 0.659 0.024 91 | CdsBenchmark main empt 43.000 4109.000 6.917 24.652 0.365 0.391 0.033 92 | CdsBenchmark main demo 121.000 6023.000 10.882 30.494 0.492 0.514 0.020 93 | CdsBenchmark main manl 57.000 5626.000 9.683 29.026 0.382 0.402 0.021 94 | ``` 95 | 96 | Spring Boot 2.5.4 (Java 17): 97 | 98 | ``` 99 | class method sample beans classes heap memory median mean range 100 | MicroBenchmark main empt 43.000 4003.000 6.796 33.240 0.544 0.564 0.022 101 | MicroBenchmark main demo 117.000 5462.000 10.169 46.527 0.817 0.870 0.095 102 | MicroBenchmark main manl 58.000 4935.000 8.848 42.608 0.616 0.650 0.053 103 | CdsBenchmark main empt 43.000 4094.000 6.857 24.568 0.351 0.380 0.032 104 | CdsBenchmark main demo 116.000 5938.000 10.663 29.255 0.471 0.496 0.033 105 | CdsBenchmark main manl 58.000 5565.000 9.513 27.864 0.385 0.396 0.012 106 | ``` 107 | 108 | Spring Boot 2.4.3: 109 | 110 | ``` 111 | class method sample beans classes heap memory median mean range 112 | MicroBenchmark main empt 37.000 3492.000 5.047 36.515 0.528 0.543 0.012 113 | MicroBenchmark main react ≈ 0 2191.000 6.269 31.371 0.235 0.240 0.009 114 | MicroBenchmark main micro 6.000 3240.000 7.416 40.326 0.330 0.341 0.010 115 | MicroBenchmark main mini 28.000 3827.000 7.370 43.582 0.473 0.480 0.009 116 | MicroBenchmark main func 45.000 3419.000 7.707 40.650 0.466 0.481 0.017 117 | MicroBenchmark main bunc 46.000 4129.000 6.200 43.422 0.554 0.581 0.047 118 | MicroBenchmark main cunc 52.000 4320.000 6.661 44.926 0.588 0.618 0.045 119 | MicroBenchmark main boot 29.000 3435.000 6.571 41.350 0.413 0.421 0.011 120 | MicroBenchmark main manl 57.000 4478.000 6.753 45.894 0.655 0.675 0.021 121 | MicroBenchmark main demo 108.000 4891.000 8.656 50.341 0.818 0.830 0.019 122 | ProfileBenchmark main jack 106.000 5270.000 10.205 54.235 0.851 0.874 0.029 123 | ProfileBenchmark main actr 186.000 5137.000 9.889 53.202 0.928 0.965 0.049 124 | ProfileBenchmark main jdbc 143.000 5119.000 9.801 52.861 0.884 0.914 0.028 125 | ProfileBenchmark main actj 227.000 5375.000 11.170 55.669 1.003 1.023 0.022 126 | ``` 127 | 128 | JDK 18 (EA): 129 | 130 | ``` 131 | class method sample beans classes heap memory median mean range 132 | MicroBenchmark main empt 43.000 3991.000 6.397 32.698 0.550 0.561 0.016 133 | MicroBenchmark main demo 117.000 5451.000 9.655 45.752 0.818 0.843 0.027 134 | MicroBenchmark main manl 58.000 4925.000 9.328 42.846 0.624 0.643 0.019 135 | CdsBenchmark main empt 43.000 4084.000 6.469 24.158 0.342 0.352 0.013 136 | CdsBenchmark main demo 116.000 5932.000 10.228 28.771 0.471 0.491 0.033 137 | CdsBenchmark main manl 58.000 5556.000 9.106 27.410 0.356 0.371 0.020 138 | ``` 139 | 140 | JDK 17: 141 | 142 | ``` 143 | class method sample beans classes heap memory median mean range 144 | MicroBenchmark main empt 37.000 3854.000 6.133 31.948 0.490 0.511 0.020 145 | MicroBenchmark main demo 108.000 5262.000 9.216 44.461 0.751 0.772 0.029 146 | MicroBenchmark main manl 57.000 4864.000 8.269 41.515 0.625 0.656 0.037 147 | CdsBenchmark main empt 37.000 3942.000 6.184 23.773 0.302 0.325 0.017 148 | CdsBenchmark main demo 107.000 5709.000 9.715 27.987 0.434 0.444 0.016 149 | CdsBenchmark main manl 57.000 5470.000 8.897 26.995 0.346 0.365 0.021 150 | ``` 151 | 152 | Spring Boot 2.3.0: 153 | 154 | ``` 155 | class method sample beans classes heap memory median mean range 156 | MicroBenchmark main empt 38.000 3344.000 5.556 36.461 0.515 0.523 0.010 157 | MicroBenchmark main react ≈0 2163.000 5.995 30.828 0.223 0.228 0.007 158 | MicroBenchmark main micro 6.000 3168.000 6.750 38.088 0.311 0.317 0.009 159 | MicroBenchmark main mini 27.000 3731.000 6.625 41.262 0.450 0.455 0.008 160 | MicroBenchmark main func 44.000 3325.000 7.117 39.532 0.447 0.455 0.008 161 | MicroBenchmark main bunc 45.000 3955.000 6.859 43.211 0.505 0.521 0.023 162 | MicroBenchmark main cunc 51.000 4145.000 6.057 43.402 0.545 0.571 0.046 163 | MicroBenchmark main boot 28.000 3397.000 6.204 40.375 0.399 0.404 0.005 164 | MicroBenchmark main manl 54.000 4301.000 6.323 44.583 0.612 0.619 0.012 165 | MicroBenchmark main demo 105.000 4729.000 7.878 48.748 0.788 0.802 0.020 166 | ProfileBenchmark main jack 103.000 5095.000 9.230 52.456 0.835 0.848 0.011 167 | ProfileBenchmark main actr 190.000 5196.000 9.701 53.370 1.009 1.020 0.014 168 | ProfileBenchmark main jdbc 140.000 5135.000 9.240 52.510 0.928 0.941 0.012 169 | ProfileBenchmark main actj 230.000 5401.000 8.676 53.516 1.090 1.114 0.036 170 | ``` 171 | 172 | Spring Boot 2.2.1: 173 | 174 | ``` 175 | class method sample beans classes heap memory median mean range 176 | MicroBenchmark main react ≈ 0 2166.000 5.897 30.746 0.219 0.224 0.007 177 | MicroBenchmark main empt 32.000 3269.000 6.366 36.951 0.469 0.481 0.015 178 | MicroBenchmark main micro 6.000 3156.000 6.638 37.913 0.305 0.312 0.008 179 | MicroBenchmark main mini 27.000 3734.000 6.660 41.262 0.444 0.450 0.008 180 | MicroBenchmark main func 45.000 3298.000 6.870 39.068 0.435 0.444 0.012 181 | MicroBenchmark main bunc 46.000 3915.000 6.806 42.849 0.503 0.537 0.040 182 | MicroBenchmark main cunc 52.000 4101.000 5.868 42.908 0.529 0.577 0.054 183 | MicroBenchmark main boot 28.000 4043.000 6.012 42.109 0.387 0.395 0.011 184 | MicroBenchmark main manl 53.000 4231.000 6.105 43.980 0.574 0.583 0.011 185 | MicroBenchmark main demo 100.000 4671.000 7.352 47.825 0.731 0.756 0.031 186 | ProfileBenchmark main jack 98.000 5431.000 9.899 54.039 0.778 0.795 0.015 187 | ProfileBenchmark main actr 188.000 5704.000 9.418 55.329 0.945 0.957 0.014 188 | ProfileBenchmark main jdbc 138.000 5625.000 8.262 53.747 0.880 0.980 0.137 189 | ProfileBenchmark main actj 228.000 5884.000 10.758 57.704 1.030 1.142 0.116 190 | ``` 191 | 192 | Snapshots after 2.2.0.M4: 193 | 194 | ``` 195 | class method sample beans classes heap memory median mean range 196 | MicroBenchmark main empt 29.000 3272.000 6.388 36.948 0.461 0.491 0.051 197 | MicroBenchmark main micro 6.000 3135.000 6.554 37.536 0.296 0.301 0.007 198 | MicroBenchmark main mini 27.000 3697.000 6.546 40.860 0.426 0.438 0.011 199 | MicroBenchmark main func 45.000 3317.000 6.955 39.155 0.427 0.438 0.022 200 | MicroBenchmark main bunc 46.000 3922.000 6.745 42.758 0.471 0.482 0.011 201 | MicroBenchmark main cunc 52.000 4082.000 5.819 42.736 0.510 0.524 0.029 202 | MicroBenchmark main demo 96.000 4632.000 7.120 47.307 0.712 0.738 0.060 203 | ``` 204 | 205 | Earlier results: 206 | 207 | ``` 208 | class method sample beans classes heap memory median mean range 209 | MainBenchmark main empt 24.000 3230.000 5.103 38.769 0.546 0.555 0.018 210 | MainBenchmark main jlog 80.000 3598.000 6.141 43.006 0.667 0.679 0.019 211 | MainBenchmark main demo 93.000 4365.000 8.024 49.564 0.766 0.773 0.011 212 | MainBenchmark main actr 174.000 5172.000 8.538 54.216 0.902 0.911 0.020 213 | MainBenchmark main jdbc 131.000 5261.000 9.174 55.252 0.883 0.902 0.031 214 | MainBenchmark main actj 214.000 5510.000 9.007 56.571 0.995 1.021 0.065 215 | ``` 216 | 217 | ``` 218 | class method sample beans classes heap memory median mean range 219 | MiniBenchmark boot jlog 28.000 3336.000 7.082 41.949 0.588 0.597 0.014 220 | MiniBenchmark boot demo 28.000 4012.000 6.508 45.566 0.703 0.710 0.011 221 | MiniBenchmark first jlog 2.000 2176.000 6.556 38.574 0.416 0.418 0.004 222 | MiniBenchmark first demo 2.000 2913.000 5.647 42.091 0.515 0.523 0.008 223 | MiniBenchmark micro jlog 2.000 2176.000 4.608 32.886 0.336 0.345 0.013 224 | MiniBenchmark micro demo 2.000 2913.000 7.318 40.454 0.438 0.451 0.016 225 | MiniBenchmark mini jlog 27.000 3059.000 5.487 38.953 0.534 0.545 0.018 226 | MiniBenchmark mini demo 27.000 3732.000 5.969 43.726 0.631 0.636 0.007 227 | ``` 228 | 229 | == Building a Native Image 230 | 231 | Checkout the "native" branch for details of how to build a native image using a buildpack and Spring Boot tooling. You can probably do it by hand with GraalVM as well if you need to, and use the thin jar to calculate its classpath for the build. 232 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example 8 | micro 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | micro 13 | Demo project for Spring Boot 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 3.1.3 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 17 25 | 1.0.27.RELEASE 26 | 1.33 27 | com.example.demo.DemoApplication 28 | 29 | 30 | 31 | 32 | org.springframework 33 | spring-context-indexer 34 | true 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-webflux 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-logging 43 | 44 | 45 | org.hibernate.validator 46 | hibernate-validator 47 | 48 | 49 | io.netty 50 | netty-transport-native-epoll 51 | 52 | 53 | jakarta.validation 54 | jakarta.validation-api 55 | 56 | 57 | 58 | 59 | org.slf4j 60 | slf4j-jdk14 61 | 62 | 63 | com.google.code.gson 64 | gson 65 | 66 | 67 | com.github.mp911de.microbenchmark-runner 68 | microbenchmark-runner-junit5 69 | 0.3.0.RELEASE 70 | test 71 | 72 | 73 | com.github.mp911de.microbenchmark-runner 74 | microbenchmark-runner-extras 75 | 0.3.0.RELEASE 76 | test 77 | 78 | 79 | org.springframework.boot.experimental 80 | spring-boot-thin-launcher 81 | ${thin.version} 82 | test 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-test 87 | test 88 | 89 | 90 | 91 | 92 | 93 | tools.jar 94 | 95 | [1.8,1.9) 96 | 97 | 98 | 99 | com.sun 100 | tools 101 | 1.8 102 | system 103 | ${java.home}/../lib/tools.jar 104 | 105 | 106 | 107 | 108 | java11 109 | 110 | [11,23) 111 | 112 | 113 | 114 | 115 | maven-surefire-plugin 116 | 117 | -Djdk.attach.allowAttachSelf=true --add-opens java.base/java.lang=ALL-UNNAMED 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | org.apache.maven.plugins 129 | maven-compiler-plugin 130 | 131 | 132 | 133 | org.openjdk.jmh 134 | jmh-generator-annprocess 135 | ${jmh.version} 136 | 137 | 138 | ${java.version} 139 | ${java.version} 140 | 141 | 142 | 143 | org.springframework.boot 144 | spring-boot-maven-plugin 145 | 146 | 147 | org.springframework.boot.experimental 148 | spring-boot-thin-layout 149 | ${thin.version} 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | jitpack.io 159 | https://jitpack.io 160 | 161 | 162 | spring-snapshots 163 | Spring Snapshots 164 | https://repo.spring.io/snapshot 165 | 166 | true 167 | 168 | 169 | 170 | spring-milestones 171 | Spring Milestones 172 | https://repo.spring.io/milestone 173 | 174 | false 175 | 176 | 177 | 178 | sonatype-snapshots 179 | https://oss.sonatype.org/content/repositories/snapshots/ 180 | 181 | true 182 | 183 | 184 | 185 | 186 | 187 | spring-snapshots 188 | Spring Snapshots 189 | https://repo.spring.io/snapshot 190 | 191 | true 192 | 193 | 194 | 195 | spring-milestones 196 | Spring Milestones 197 | https://repo.spring.io/milestone 198 | 199 | false 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/main/java/com/example/boot/BootApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.boot; 17 | 18 | import java.util.Collections; 19 | 20 | import com.example.config.ApplicationBuilder; 21 | import reactor.core.publisher.Mono; 22 | 23 | import org.springframework.boot.SpringBootConfiguration; 24 | import org.springframework.boot.WebApplicationType; 25 | import org.springframework.boot.builder.SpringApplicationBuilder; 26 | import org.springframework.context.ConfigurableApplicationContext; 27 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 28 | import org.springframework.context.annotation.Bean; 29 | import org.springframework.web.reactive.config.EnableWebFlux; 30 | import org.springframework.web.reactive.function.server.RouterFunction; 31 | 32 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 33 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 34 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 35 | 36 | /** 37 | * @author Dave Syer 38 | * 39 | */ 40 | @SpringBootConfiguration(proxyBeanMethods = false) 41 | @EnableWebFlux 42 | // @Import(LazyInitBeanFactoryPostProcessor.class) 43 | public class BootApplication { 44 | 45 | public static void main(String[] args) throws Exception { 46 | long t0 = System.currentTimeMillis(); 47 | SpringApplicationBuilder builder = new SpringApplicationBuilder( 48 | BootApplication.class).web(WebApplicationType.NONE) 49 | .contextFactory(type -> new AnnotationConfigApplicationContext()) 50 | .registerShutdownHook(false); 51 | builder.application().setListeners(Collections.emptyList()); 52 | try (ConfigurableApplicationContext context = builder.run(args)) { 53 | ApplicationBuilder.start(context, b -> { 54 | System.err.println("Started HttpServer: " 55 | + (System.currentTimeMillis() - t0) + "ms"); 56 | }); 57 | } 58 | } 59 | 60 | @Bean 61 | public RouterFunction userEndpoints() { 62 | return route(GET("/"), request -> ok().body(Mono.just("Hello"), String.class)); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/ApplicationBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.config; 17 | 18 | import java.lang.management.ManagementFactory; 19 | import java.time.Duration; 20 | import java.util.function.Consumer; 21 | 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | 25 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 26 | import org.springframework.context.ConfigurableApplicationContext; 27 | import org.springframework.context.support.AbstractApplicationContext; 28 | import org.springframework.http.server.reactive.HttpHandler; 29 | import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; 30 | import org.springframework.web.server.adapter.WebHttpHandlerBuilder; 31 | 32 | import reactor.netty.DisposableServer; 33 | import reactor.netty.http.server.HttpServer; 34 | 35 | /** 36 | * @author Dave Syer 37 | * 38 | */ 39 | public class ApplicationBuilder { 40 | 41 | private static final String SHUTDOWN_LISTENER = "SHUTDOWN_LISTENER"; 42 | public static final String STARTUP = "Benchmark app started"; 43 | private static Log logger = LogFactory.getLog(StartupApplicationListener.class); 44 | 45 | public static void start(ConfigurableApplicationContext context) { 46 | start(context, null); 47 | } 48 | 49 | public static void start(ConfigurableApplicationContext context, 50 | Consumer callback) { 51 | if (!hasListeners(context)) { 52 | ((DefaultListableBeanFactory) context.getBeanFactory()) 53 | .registerDisposableBean(SHUTDOWN_LISTENER, 54 | new ShutdownApplicationListener()); 55 | new BeanCountingApplicationListener().log(context); 56 | logger.info(STARTUP); 57 | } 58 | 59 | HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build(); 60 | ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); 61 | HttpServer httpServer = HttpServer.create().host("localhost").port( 62 | context.getEnvironment().getProperty("server.port", Integer.class, 8080)) 63 | .handle(adapter); 64 | httpServer.bindUntilJavaShutdown(Duration.ofSeconds(60), callback(callback)); 65 | } 66 | 67 | private static Consumer callback( 68 | Consumer callback) { 69 | return context -> { 70 | try { 71 | double uptime = ManagementFactory.getRuntimeMXBean().getUptime(); 72 | System.err.println("JVM running for " + uptime + "ms"); 73 | } 74 | catch (Throwable e) { 75 | } 76 | if (callback != null) { 77 | callback.accept(context); 78 | } 79 | }; 80 | } 81 | 82 | private static boolean hasListeners(ConfigurableApplicationContext context) { 83 | if (context.getBeanNamesForType(ShutdownApplicationListener.class).length != 0) { 84 | return true; 85 | } 86 | if (context instanceof AbstractApplicationContext) { 87 | if (((AbstractApplicationContext) context).getApplicationListeners().stream() 88 | .anyMatch(l -> l instanceof ShutdownApplicationListener)) { 89 | return true; 90 | } 91 | } 92 | if ((DefaultListableBeanFactory) context.getBeanFactory() 93 | .getSingleton(SHUTDOWN_LISTENER) != null) { 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/BeanCountingApplicationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.config; 17 | 18 | import java.lang.management.ManagementFactory; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | 26 | import org.springframework.beans.BeansException; 27 | import org.springframework.boot.context.event.ApplicationReadyEvent; 28 | import org.springframework.context.ApplicationContext; 29 | import org.springframework.context.ApplicationContextAware; 30 | import org.springframework.context.ApplicationListener; 31 | import org.springframework.context.ConfigurableApplicationContext; 32 | 33 | /** 34 | * @author Dave Syer 35 | * 36 | */ 37 | public class BeanCountingApplicationListener 38 | implements ApplicationListener, ApplicationContextAware { 39 | 40 | private static Log logger = LogFactory.getLog(BeanCountingApplicationListener.class); 41 | private ApplicationContext context; 42 | 43 | @Override 44 | public void setApplicationContext(ApplicationContext context) throws BeansException { 45 | this.context = context; 46 | } 47 | 48 | @Override 49 | public void onApplicationEvent(ApplicationReadyEvent event) { 50 | if (!event.getApplicationContext().equals(this.context)) { 51 | return; 52 | } 53 | ConfigurableApplicationContext context = event.getApplicationContext(); 54 | log(context); 55 | } 56 | 57 | public void log(ConfigurableApplicationContext context) { 58 | int count = 0; 59 | String id = context.getId(); 60 | List names = new ArrayList<>(); 61 | while (context != null) { 62 | count += context.getBeanDefinitionCount(); 63 | names.addAll(Arrays.asList(context.getBeanDefinitionNames())); 64 | context = (ConfigurableApplicationContext) context.getParent(); 65 | } 66 | logger.info("Bean count: " + id + "=" + count); 67 | logger.debug("Bean names: " + id + "=" + names); 68 | try { 69 | logger.info("Class count: " + id + "=" + ManagementFactory 70 | .getClassLoadingMXBean().getTotalLoadedClassCount()); 71 | } 72 | catch (Throwable e) { 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/LazyInitBeanFactoryPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.config; 17 | 18 | import org.springframework.beans.BeansException; 19 | import org.springframework.beans.factory.config.BeanDefinition; 20 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 21 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 22 | import org.springframework.stereotype.Component; 23 | 24 | @Component 25 | public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 26 | 27 | private Class[] exclusionList; 28 | 29 | public LazyInitBeanFactoryPostProcessor() { 30 | } 31 | 32 | public LazyInitBeanFactoryPostProcessor(Class[] exclusionList) { 33 | this.exclusionList = exclusionList; 34 | } 35 | 36 | @Override 37 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 38 | throws BeansException { 39 | 40 | // Iterate over all bean, mark them as lazy if they are not in the exclusion list. 41 | for (String beanName : beanFactory.getBeanDefinitionNames()) { 42 | if (isLazy(beanName, beanFactory)) { 43 | BeanDefinition definition = beanFactory.getBeanDefinition(beanName); 44 | definition.setLazyInit(true); 45 | } 46 | } 47 | } 48 | 49 | private boolean isLazy(String beanName, ConfigurableListableBeanFactory beanFactory) { 50 | if (exclusionList == null || exclusionList.length == 0) { 51 | return true; 52 | } 53 | for (Class clazz : exclusionList) { 54 | if (beanFactory.isTypeMatch(beanName, clazz)) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | } -------------------------------------------------------------------------------- /src/main/java/com/example/config/ShutdownApplicationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.config; 17 | 18 | import java.util.LinkedHashSet; 19 | import java.util.Set; 20 | 21 | import org.springframework.beans.BeansException; 22 | import org.springframework.beans.factory.DisposableBean; 23 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 24 | import org.springframework.boot.SpringBootConfiguration; 25 | import org.springframework.boot.context.event.ApplicationReadyEvent; 26 | import org.springframework.context.ApplicationContext; 27 | import org.springframework.context.ApplicationContextAware; 28 | import org.springframework.context.ApplicationListener; 29 | import org.springframework.core.Ordered; 30 | import org.springframework.core.annotation.AnnotatedElementUtils; 31 | import org.springframework.core.annotation.Order; 32 | import org.springframework.util.ClassUtils; 33 | 34 | /** 35 | * @author Dave Syer 36 | * 37 | */ 38 | @Order(Ordered.HIGHEST_PRECEDENCE) 39 | public class ShutdownApplicationListener 40 | implements ApplicationListener, DisposableBean, 41 | ApplicationContextAware { 42 | 43 | private static final String SHUTDOWN_LISTENER = "SHUTDOWN_LISTENER"; 44 | 45 | public static final String MARKER = "Benchmark app stopped"; 46 | 47 | private ApplicationContext context; 48 | 49 | @Override 50 | public void setApplicationContext(ApplicationContext context) throws BeansException { 51 | this.context = context; 52 | } 53 | 54 | @Override 55 | public void onApplicationEvent(ApplicationReadyEvent event) { 56 | if (!event.getApplicationContext().equals(this.context)) { 57 | return; 58 | } 59 | if (isSpringBootApplication(sources(event))) { 60 | ((DefaultListableBeanFactory) event.getApplicationContext().getBeanFactory()) 61 | .registerDisposableBean(SHUTDOWN_LISTENER, this); 62 | } 63 | } 64 | 65 | @Override 66 | public void destroy() throws Exception { 67 | try { 68 | System.out.println(MARKER); 69 | } 70 | catch (Exception e) { 71 | } 72 | } 73 | 74 | private boolean isSpringBootApplication(Set> sources) { 75 | for (Class source : sources) { 76 | if (AnnotatedElementUtils.hasAnnotation(source, 77 | SpringBootConfiguration.class)) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | private Set> sources(ApplicationReadyEvent event) { 85 | Set objects = event.getSpringApplication().getAllSources(); 86 | Set> result = new LinkedHashSet<>(); 87 | for (Object object : objects) { 88 | if (object instanceof String) { 89 | object = ClassUtils.resolveClassName((String) object, null); 90 | } 91 | result.add((Class) object); 92 | } 93 | return result; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/StartupApplicationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.config; 17 | 18 | import java.util.LinkedHashSet; 19 | import java.util.Set; 20 | 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | 24 | import org.springframework.beans.BeansException; 25 | import org.springframework.boot.SpringBootConfiguration; 26 | import org.springframework.boot.context.event.ApplicationReadyEvent; 27 | import org.springframework.context.ApplicationContext; 28 | import org.springframework.context.ApplicationContextAware; 29 | import org.springframework.context.ApplicationListener; 30 | import org.springframework.core.annotation.AnnotatedElementUtils; 31 | import org.springframework.util.ClassUtils; 32 | 33 | /** 34 | * @author Dave Syer 35 | * 36 | */ 37 | public class StartupApplicationListener 38 | implements ApplicationListener, ApplicationContextAware { 39 | 40 | public static final String MARKER = "Benchmark app started"; 41 | 42 | private static Log logger = LogFactory.getLog(StartupApplicationListener.class); 43 | 44 | private ApplicationContext context; 45 | 46 | @Override 47 | public void setApplicationContext(ApplicationContext context) throws BeansException { 48 | this.context = context; 49 | } 50 | 51 | @Override 52 | public void onApplicationEvent(ApplicationReadyEvent event) { 53 | System.err.println("Event: " + event); 54 | if (!event.getApplicationContext().equals(this.context)) { 55 | return; 56 | } 57 | if (isSpringBootApplication(sources(event))) { 58 | try { 59 | logger.info(MARKER); 60 | } 61 | catch (Exception e) { 62 | } 63 | } 64 | } 65 | 66 | private boolean isSpringBootApplication(Set> sources) { 67 | for (Class source : sources) { 68 | if (AnnotatedElementUtils.hasAnnotation(source, 69 | SpringBootConfiguration.class)) { 70 | return true; 71 | } 72 | } 73 | return false; 74 | } 75 | 76 | private Set> sources(ApplicationReadyEvent event) { 77 | Set objects = event.getSpringApplication().getAllSources(); 78 | Set> result = new LinkedHashSet<>(); 79 | for (Object object : objects) { 80 | if (object instanceof String) { 81 | object = ClassUtils.resolveClassName((String) object, null); 82 | } 83 | result.add((Class) object); 84 | } 85 | return result; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/example/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.web.reactive.function.server.RouterFunction; 7 | 8 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 9 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 10 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 11 | 12 | import reactor.core.publisher.Mono; 13 | 14 | @SpringBootApplication(proxyBeanMethods = false) 15 | public class DemoApplication { 16 | 17 | @Bean 18 | public RouterFunction userEndpoints() { 19 | return route(GET("/"), request -> ok().body(Mono.just("Hello"), String.class)); 20 | } 21 | 22 | public static void main(String[] args) throws Exception { 23 | new SpringApplicationBuilder(DemoApplication.class).run(args); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/empt/EmptyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.empt; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | 6 | @SpringBootApplication(proxyBeanMethods = false) 7 | public class EmptyApplication { 8 | 9 | public static void main(String[] args) throws Exception { 10 | new SpringApplicationBuilder(EmptyApplication.class).run(args); 11 | Thread thread = new Thread() { 12 | @Override 13 | public void run() { 14 | while (true) { 15 | try { 16 | Thread.sleep(100L); 17 | } 18 | catch (InterruptedException e) { 19 | Thread.currentThread().interrupt(); 20 | } 21 | } 22 | } 23 | }; 24 | thread.setDaemon(false); 25 | thread.start(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/example/func/BuncApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.func; 18 | 19 | import java.util.Collections; 20 | 21 | import org.springframework.boot.SpringApplication; 22 | import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; 23 | import org.springframework.context.ApplicationContext; 24 | 25 | /** 26 | * @author Dave Syer 27 | * 28 | */ 29 | public class BuncApplication extends FuncApplication { 30 | 31 | public static void main(String[] args) throws Exception { 32 | long t0 = System.currentTimeMillis(); 33 | BuncApplication bean = new BuncApplication(); 34 | bean.run(); 35 | System.err.println( 36 | "Started HttpServer: " + (System.currentTimeMillis() - t0) + "ms"); 37 | if (Boolean.getBoolean("demo.close")) { 38 | bean.close(); 39 | } 40 | } 41 | 42 | @Override 43 | public void run() { 44 | SpringApplication application = new SpringApplication(BuncApplication.class) { 45 | @Override 46 | protected void load(ApplicationContext context, Object[] sources) { 47 | // We don't want the annotation bean definition reader 48 | // super.load(context, sources); 49 | } 50 | }; 51 | application.setRegisterShutdownHook(false); 52 | application.setDefaultProperties(Collections.singletonMap("boot.active", "true")); 53 | application.addInitializers(this); 54 | application.setApplicationContextFactory( 55 | webApplicationType -> new ReactiveWebServerApplicationContext()); 56 | application.run(); 57 | System.err.println(MARKER); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/example/func/CuncApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.func; 18 | 19 | import java.util.Collections; 20 | 21 | import org.springframework.boot.SpringApplication; 22 | import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; 23 | 24 | /** 25 | * @author Dave Syer 26 | * 27 | */ 28 | public class CuncApplication extends FuncApplication { 29 | 30 | public static void main(String[] args) throws Exception { 31 | long t0 = System.currentTimeMillis(); 32 | CuncApplication bean = new CuncApplication(); 33 | bean.run(); 34 | System.err.println( 35 | "Started HttpServer: " + (System.currentTimeMillis() - t0) + "ms"); 36 | if (Boolean.getBoolean("demo.close")) { 37 | bean.close(); 38 | } 39 | } 40 | 41 | @Override 42 | public void run() { 43 | SpringApplication application = new SpringApplication(CuncApplication.class); 44 | application.setRegisterShutdownHook(false); 45 | application.setDefaultProperties(Collections.singletonMap("boot.active", "true")); 46 | application.addInitializers(this); 47 | application.setApplicationContextFactory( 48 | webApplicationType -> new ReactiveWebServerApplicationContext()); 49 | application.run(); 50 | System.err.println(MARKER); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/example/func/FuncApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.func; 2 | 3 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 4 | import static org.springframework.web.reactive.function.server.RequestPredicates.POST; 5 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 6 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 7 | 8 | import java.io.Closeable; 9 | import java.io.IOException; 10 | import java.lang.management.ManagementFactory; 11 | import java.lang.reflect.Constructor; 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | import org.apache.commons.logging.Log; 20 | import org.apache.commons.logging.LogFactory; 21 | import org.springframework.beans.factory.ObjectProvider; 22 | import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; 23 | import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; 24 | import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; 25 | import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; 26 | import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer; 27 | import org.springframework.boot.autoconfigure.gson.GsonProperties; 28 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 29 | import org.springframework.boot.autoconfigure.web.ServerProperties; 30 | import org.springframework.boot.autoconfigure.web.WebProperties; 31 | import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; 32 | import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryCustomizer; 33 | import org.springframework.boot.autoconfigure.web.reactive.ResourceHandlerRegistrationCustomizer; 34 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration.EnableWebFluxConfiguration; 35 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration.WebFluxConfig; 36 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; 37 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxRegistrations; 38 | import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration; 39 | import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; 40 | import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor; 41 | import org.springframework.boot.ssl.SslBundle; 42 | import org.springframework.boot.web.codec.CodecCustomizer; 43 | import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; 44 | import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext; 45 | import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; 46 | import org.springframework.boot.web.reactive.error.ErrorAttributes; 47 | import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; 48 | import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; 49 | import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; 50 | import org.springframework.context.ApplicationContextInitializer; 51 | import org.springframework.context.ConfigurableApplicationContext; 52 | import org.springframework.context.annotation.Bean; 53 | import org.springframework.context.support.GenericApplicationContext; 54 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 55 | import org.springframework.core.ParameterNameDiscoverer; 56 | import org.springframework.core.ReactiveAdapterRegistry; 57 | import org.springframework.core.ResolvableType; 58 | import org.springframework.core.convert.TypeDescriptor; 59 | import org.springframework.core.convert.converter.GenericConverter; 60 | import org.springframework.format.support.DefaultFormattingConversionService; 61 | import org.springframework.format.support.FormattingConversionService; 62 | import org.springframework.http.codec.ServerCodecConfigurer; 63 | import org.springframework.http.converter.StringHttpMessageConverter; 64 | import org.springframework.http.converter.json.GsonHttpMessageConverter; 65 | import org.springframework.http.server.reactive.HttpHandler; 66 | import org.springframework.lang.Nullable; 67 | import org.springframework.validation.Validator; 68 | import org.springframework.web.reactive.DispatcherHandler; 69 | import org.springframework.web.reactive.HandlerMapping; 70 | import org.springframework.web.reactive.accept.RequestedContentTypeResolver; 71 | import org.springframework.web.reactive.config.WebFluxConfigurer; 72 | import org.springframework.web.reactive.function.client.WebClient; 73 | import org.springframework.web.reactive.function.server.RouterFunction; 74 | import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter; 75 | import org.springframework.web.reactive.function.server.support.RouterFunctionMapping; 76 | import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler; 77 | import org.springframework.web.reactive.resource.ResourceUrlProvider; 78 | import org.springframework.web.reactive.result.SimpleHandlerAdapter; 79 | import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; 80 | import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; 81 | import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; 82 | import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; 83 | import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler; 84 | import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; 85 | import org.springframework.web.reactive.result.view.ViewResolver; 86 | import org.springframework.web.server.ServerWebExchange; 87 | import org.springframework.web.server.WebExceptionHandler; 88 | import org.springframework.web.server.WebFilter; 89 | import org.springframework.web.server.WebFilterChain; 90 | import org.springframework.web.server.adapter.WebHttpHandlerBuilder; 91 | import org.springframework.web.server.i18n.LocaleContextResolver; 92 | 93 | import com.example.config.LazyInitBeanFactoryPostProcessor; 94 | import com.google.gson.Gson; 95 | import com.google.gson.GsonBuilder; 96 | 97 | import reactor.core.publisher.Mono; 98 | 99 | public class FuncApplication implements Runnable, Closeable, 100 | ApplicationContextInitializer { 101 | 102 | private static Log logger = LogFactory.getLog(FuncApplication.class); 103 | 104 | public static final String MARKER = "Benchmark app started"; 105 | 106 | private GenericApplicationContext context; 107 | 108 | @Bean 109 | public RouterFunction userEndpoints() { 110 | return route(GET("/"), 111 | request -> ok().body(Mono.just("Hello"), String.class)) 112 | .andRoute(POST("/"), 113 | request -> ok().body( 114 | request.bodyToFlux(String.class) 115 | .map(value -> value.toUpperCase()), 116 | String.class)); 117 | } 118 | 119 | public static void main(String[] args) throws Exception { 120 | long t0 = System.currentTimeMillis(); 121 | FuncApplication bean = new FuncApplication(); 122 | bean.run(); 123 | System.err.println( 124 | "Started HttpServer: " + (System.currentTimeMillis() - t0) + "ms"); 125 | if (Boolean.getBoolean("demo.close")) { 126 | bean.close(); 127 | } 128 | } 129 | 130 | public void log(ConfigurableApplicationContext context) { 131 | int count = 0; 132 | String id = context.getId(); 133 | List names = new ArrayList<>(); 134 | while (context != null) { 135 | count += context.getBeanDefinitionCount(); 136 | names.addAll(Arrays.asList(context.getBeanDefinitionNames())); 137 | context = (ConfigurableApplicationContext) context.getParent(); 138 | } 139 | logger.info("Bean count: " + id + "=" + count); 140 | logger.debug("Bean names: " + id + "=" + names); 141 | try { 142 | logger.info("Class count: " + id + "=" + ManagementFactory 143 | .getClassLoadingMXBean().getTotalLoadedClassCount()); 144 | } catch (Throwable e) { 145 | } 146 | } 147 | 148 | @Override 149 | public void close() throws IOException { 150 | if (context != null) { 151 | context.close(); 152 | } 153 | } 154 | 155 | @Override 156 | public void run() { 157 | ReactiveWebServerApplicationContext context = new ReactiveWebServerApplicationContext(); 158 | context.addBeanFactoryPostProcessor(new LazyInitBeanFactoryPostProcessor()); 159 | context.setId("application"); 160 | initialize(context); 161 | context.refresh(); 162 | log(context); 163 | System.err.println(MARKER); 164 | } 165 | 166 | @Override 167 | public void initialize(GenericApplicationContext context) { 168 | this.context = context; 169 | ((AbstractAutowireCapableBeanFactory) context.getDefaultListableBeanFactory()) 170 | .setParameterNameDiscoverer(new NoopParameterNameDiscoverer()); 171 | if (context.getEnvironment().getProperty("boot.active", Boolean.class, false)) { 172 | performPreinitialization(); 173 | } 174 | context.registerBean(AutowiredAnnotationBeanPostProcessor.class); 175 | registerDemoApplication(); 176 | registerWebServerFactoryCustomizerBeanPostProcessor(); 177 | registerConfigurationProperties(); 178 | // context.registerBean(LazyInitBeanFactoryPostProcessor.class); 179 | registerPropertyPlaceholderAutoConfiguration(); 180 | registerReactiveWebServerFactoryAutoConfiguration(); 181 | registerErrorWebFluxAutoConfiguration(); 182 | registerWebFluxAutoConfiguration(); 183 | registerHttpHandlerAutoConfiguration(); 184 | registerGsonAutoConfiguration(); 185 | registerHttpMessageConvertersAutoConfiguration(); 186 | registerWebClientAutoConfiguration(); 187 | } 188 | 189 | static class DummyWebFilter implements WebFilter { 190 | 191 | @Override 192 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { 193 | return chain.filter(exchange); 194 | } 195 | 196 | } 197 | 198 | static class DummyGenericConverter implements GenericConverter { 199 | 200 | @Override 201 | public Set getConvertibleTypes() { 202 | return Collections.emptySet(); 203 | } 204 | 205 | @Override 206 | public Object convert(@Nullable Object source, TypeDescriptor sourceType, 207 | TypeDescriptor targetType) { 208 | return null; 209 | } 210 | 211 | } 212 | 213 | private void performPreinitialization() { 214 | try { 215 | Thread thread = new Thread(new Runnable() { 216 | 217 | @Override 218 | public void run() { 219 | runSafely(() -> new DefaultFormattingConversionService()); 220 | } 221 | 222 | public void runSafely(Runnable runnable) { 223 | try { 224 | runnable.run(); 225 | } catch (Throwable ex) { 226 | // Ignore 227 | } 228 | } 229 | 230 | }, "background-preinit"); 231 | thread.start(); 232 | } catch (Exception ex) { 233 | } 234 | } 235 | 236 | private void registerConfigurationProperties() { 237 | ConfigurationPropertiesBindingPostProcessor.register(context); 238 | context.registerBean(ServerProperties.class, () -> new ServerProperties()); 239 | context.registerBean(WebProperties.class, () -> new WebProperties()); 240 | context.registerBean(WebFluxProperties.class, () -> new WebFluxProperties()); 241 | context.registerBean(GsonProperties.class, () -> new GsonProperties()); 242 | } 243 | 244 | private void registerWebServerFactoryCustomizerBeanPostProcessor() { 245 | context.registerBean("webServerFactoryCustomizerBeanPostProcessor", 246 | WebServerFactoryCustomizerBeanPostProcessor.class); 247 | } 248 | 249 | private void registerPropertyPlaceholderAutoConfiguration() { 250 | context.registerBean(PropertySourcesPlaceholderConfigurer.class, 251 | () -> PropertyPlaceholderAutoConfiguration 252 | .propertySourcesPlaceholderConfigurer()); 253 | } 254 | 255 | private void registerReactiveWebServerFactoryAutoConfiguration() { 256 | ReactiveWebServerFactoryAutoConfiguration config = new ReactiveWebServerFactoryAutoConfiguration(); 257 | context.registerBean(ReactiveWebServerFactoryCustomizer.class, 258 | () -> config.reactiveWebServerFactoryCustomizer( 259 | context.getBean(ServerProperties.class), 260 | context.getDefaultListableBeanFactory() 261 | .getBeanProvider(ResolvableType.forClassWithGenerics( 262 | List.class, SslBundle.class)))); 263 | context.registerBean(NettyReactiveWebServerFactory.class, 264 | () -> new NettyReactiveWebServerFactory()); 265 | } 266 | 267 | private void registerErrorWebFluxAutoConfiguration() { 268 | context.registerBean(ErrorAttributes.class, () -> new DefaultErrorAttributes()); 269 | context.registerBean(ErrorWebExceptionHandler.class, () -> { 270 | return errorWebFluxAutoConfiguration().errorWebExceptionHandler( 271 | context.getBean(ErrorAttributes.class), 272 | context.getBean(WebProperties.class), 273 | context.getDefaultListableBeanFactory() 274 | .getBeanProvider(ResolvableType.forClassWithGenerics( 275 | List.class, ViewResolver.class)), 276 | context.getBean(ServerCodecConfigurer.class), context); 277 | }); 278 | } 279 | 280 | private ErrorWebFluxAutoConfiguration errorWebFluxAutoConfiguration() { 281 | ServerProperties serverProperties = context.getBean(ServerProperties.class); 282 | return new ErrorWebFluxAutoConfiguration(serverProperties); 283 | } 284 | 285 | private void registerWebFluxAutoConfiguration() { 286 | context.registerBean(EnableWebFluxConfigurationWrapper.class, 287 | () -> new EnableWebFluxConfigurationWrapper( 288 | context.getBean(WebFluxProperties.class), 289 | context.getBean(WebProperties.class), 290 | context.getBean(ServerProperties.class), 291 | context.getBeanProvider(WebFluxRegistrations.class))); 292 | context.registerBean(HandlerFunctionAdapter.class, 293 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 294 | .handlerFunctionAdapter()); 295 | context.registerBean(WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME, 296 | LocaleContextResolver.class, 297 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 298 | .localeContextResolver()); 299 | context.registerBean(RequestMappingHandlerAdapter.class, 300 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 301 | .requestMappingHandlerAdapter( 302 | context.getBean(ReactiveAdapterRegistry.class), 303 | context.getBean(ServerCodecConfigurer.class), 304 | context.getBean(FormattingConversionService.class), 305 | context.getBean(Validator.class))); 306 | context.registerBean(RequestMappingHandlerMapping.class, 307 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 308 | .requestMappingHandlerMapping( 309 | context.getBean(RequestedContentTypeResolver.class))); 310 | context.registerBean(ResourceUrlProvider.class, () -> context 311 | .getBean(EnableWebFluxConfigurationWrapper.class).resourceUrlProvider()); 312 | context.registerBean(HandlerMapping.class, 313 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 314 | .resourceHandlerMapping( 315 | context.getBean(ResourceUrlProvider.class))); 316 | context.registerBean(ResponseBodyResultHandler.class, 317 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 318 | .responseBodyResultHandler( 319 | context.getBean(ReactiveAdapterRegistry.class), 320 | context.getBean(ServerCodecConfigurer.class), 321 | context.getBean(RequestedContentTypeResolver.class))); 322 | context.registerBean(ResponseEntityResultHandler.class, 323 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 324 | .responseEntityResultHandler( 325 | context.getBean(ReactiveAdapterRegistry.class), 326 | context.getBean(ServerCodecConfigurer.class), 327 | context.getBean(RequestedContentTypeResolver.class))); 328 | context.registerBean(WebExceptionHandler.class, 329 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 330 | .responseStatusExceptionHandler()); 331 | context.registerBean(RouterFunctionMapping.class, 332 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 333 | .routerFunctionMapping( 334 | context.getBean(ServerCodecConfigurer.class))); 335 | context.registerBean(WebHttpHandlerBuilder.SERVER_CODEC_CONFIGURER_BEAN_NAME, 336 | ServerCodecConfigurer.class, 337 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 338 | .serverCodecConfigurer()); 339 | context.registerBean(ServerResponseResultHandler.class, 340 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 341 | .serverResponseResultHandler( 342 | context.getBean(ServerCodecConfigurer.class))); 343 | context.registerBean(SimpleHandlerAdapter.class, () -> context 344 | .getBean(EnableWebFluxConfigurationWrapper.class).simpleHandlerAdapter()); 345 | context.registerBean(ViewResolutionResultHandler.class, 346 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 347 | .viewResolutionResultHandler( 348 | context.getBean(ReactiveAdapterRegistry.class), 349 | context.getBean(RequestedContentTypeResolver.class))); 350 | context.registerBean(ReactiveAdapterRegistry.class, 351 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 352 | .webFluxAdapterRegistry()); 353 | context.registerBean(RequestedContentTypeResolver.class, 354 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 355 | .webFluxContentTypeResolver()); 356 | context.registerBean(FormattingConversionService.class, 357 | () -> context.getBean(EnableWebFluxConfigurationWrapper.class) 358 | .webFluxConversionService()); 359 | context.registerBean(Validator.class, () -> context 360 | .getBean(EnableWebFluxConfigurationWrapper.class).webFluxValidator()); 361 | context.registerBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME, 362 | DispatcherHandler.class, () -> context 363 | .getBean(EnableWebFluxConfigurationWrapper.class).webHandler()); 364 | context.registerBean(WebFluxConfigurer.class, 365 | () -> new WebFluxConfig(context.getBean(WebProperties.class), 366 | context.getBean(WebFluxProperties.class), context, 367 | context.getBeanProvider(HandlerMethodArgumentResolver.class), 368 | context.getBeanProvider(CodecCustomizer.class), 369 | context.getBeanProvider( 370 | ResourceHandlerRegistrationCustomizer.class), 371 | context.getBeanProvider(ViewResolver.class))); 372 | } 373 | 374 | private void registerHttpHandlerAutoConfiguration() { 375 | context.registerBean(HttpHandler.class, 376 | () -> WebHttpHandlerBuilder.applicationContext(context).build()); 377 | } 378 | 379 | private void registerDemoApplication() { 380 | context.registerBean(RouterFunction.class, () -> userEndpoints()); 381 | } 382 | 383 | private void registerGsonAutoConfiguration() { 384 | GsonAutoConfiguration config = new GsonAutoConfiguration(); 385 | context.registerBean(GsonBuilder.class, () -> config.gsonBuilder(new ArrayList<>( 386 | context.getBeansOfType(GsonBuilderCustomizer.class).values()))); 387 | context.registerBean(Gson.class, 388 | () -> config.gson(context.getBean(GsonBuilder.class))); 389 | context.registerBean(GsonBuilderCustomizer.class, () -> config 390 | .standardGsonBuilderCustomizer(context.getBean(GsonProperties.class))); 391 | } 392 | 393 | private void registerHttpMessageConvertersAutoConfiguration() { 394 | // TODO: re-instate default message converters 395 | context.registerBean(HttpMessageConverters.class, 396 | () -> new HttpMessageConverters(false, Collections.emptyList())); 397 | context.registerBean(StringHttpMessageConverter.class, 398 | this::stringHttpMessageConverter); 399 | context.registerBean(GsonHttpMessageConverter.class, 400 | () -> new GsonHttpMessageConverter(context.getBean(Gson.class))); 401 | } 402 | 403 | StringHttpMessageConverter stringHttpMessageConverter() { 404 | StringHttpMessageConverter converter = new StringHttpMessageConverter(context 405 | .getBean(ServerProperties.class).getServlet().getEncoding().getCharset()); 406 | converter.setWriteAcceptCharset(false); 407 | return converter; 408 | } 409 | 410 | private void registerWebClientAutoConfiguration() { 411 | context.registerBean(WebClient.Builder.class, 412 | () -> new WebClientAutoConfiguration().webClientBuilder( 413 | context.getBeanProvider(ResolvableType.forClassWithGenerics( 414 | List.class, WebClientCustomizer.class)))); 415 | } 416 | 417 | } 418 | 419 | class EnableWebFluxConfigurationWrapper extends EnableWebFluxConfiguration { 420 | 421 | public EnableWebFluxConfigurationWrapper(WebFluxProperties webFluxProperties, 422 | WebProperties webProperties, ServerProperties serverProperties, 423 | ObjectProvider webFluxRegistrations) { 424 | super(webFluxProperties, webProperties, serverProperties, webFluxRegistrations); 425 | } 426 | 427 | } 428 | 429 | class NoopParameterNameDiscoverer implements ParameterNameDiscoverer { 430 | 431 | @Override 432 | public String[] getParameterNames(Method method) { 433 | return null; 434 | } 435 | 436 | @Override 437 | public String[] getParameterNames(Constructor ctor) { 438 | return null; 439 | } 440 | 441 | } 442 | -------------------------------------------------------------------------------- /src/main/java/com/example/manual/ManualApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.manual; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | import org.springframework.boot.SpringBootConfiguration; 6 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 7 | import org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration; 8 | import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; 9 | import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; 10 | import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; 12 | import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration; 13 | import org.springframework.boot.builder.SpringApplicationBuilder; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.web.reactive.function.server.RouterFunction; 16 | 17 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 18 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 19 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 20 | 21 | @SpringBootConfiguration(proxyBeanMethods = false) 22 | @ImportAutoConfiguration({ WebFluxAutoConfiguration.class, 23 | ReactiveWebServerFactoryAutoConfiguration.class, 24 | ErrorWebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class, 25 | ConfigurationPropertiesAutoConfiguration.class, 26 | PropertyPlaceholderAutoConfiguration.class }) 27 | public class ManualApplication { 28 | 29 | @Bean 30 | public RouterFunction userEndpoints() { 31 | return route(GET("/"), request -> ok().body(Mono.just("Hello"), String.class)); 32 | } 33 | 34 | public static void main(String[] args) throws Exception { 35 | new SpringApplicationBuilder(ManualApplication.class).run(args); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/example/micro/MicroApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.micro; 17 | 18 | import com.example.config.ApplicationBuilder; 19 | import reactor.core.publisher.Mono; 20 | 21 | import org.springframework.boot.autoconfigure.web.ErrorProperties; 22 | import org.springframework.boot.autoconfigure.web.WebProperties; 23 | import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler; 24 | import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; 25 | import org.springframework.boot.web.reactive.error.ErrorAttributes; 26 | import org.springframework.context.support.GenericApplicationContext; 27 | import org.springframework.http.codec.ServerCodecConfigurer; 28 | import org.springframework.http.server.reactive.HttpHandler; 29 | import org.springframework.web.reactive.function.server.HandlerStrategies; 30 | import org.springframework.web.reactive.function.server.RouterFunction; 31 | import org.springframework.web.reactive.function.server.RouterFunctions; 32 | import org.springframework.web.server.WebExceptionHandler; 33 | 34 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 35 | import static org.springframework.web.reactive.function.server.RequestPredicates.POST; 36 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 37 | 38 | /** 39 | * @author Dave Syer 40 | * 41 | */ 42 | public class MicroApplication { 43 | 44 | public static void main(String[] args) throws Exception { 45 | long t0 = System.currentTimeMillis(); 46 | GenericApplicationContext context = new MicroApplication().run(); 47 | ApplicationBuilder.start(context, b -> { 48 | System.err.println( 49 | "Started HttpServer: " + (System.currentTimeMillis() - t0) + "ms"); 50 | }); 51 | } 52 | 53 | public GenericApplicationContext run() { 54 | GenericApplicationContext context = new GenericApplicationContext(); 55 | context.registerBean(RouterFunction.class, 56 | () -> RouterFunctions 57 | .route(GET("/"), 58 | request -> ok().body(Mono.just("Hello"), String.class)) 59 | .andRoute(POST("/"), 60 | request -> ok().body( 61 | request.bodyToMono(String.class) 62 | .map(value -> value.toUpperCase()), 63 | String.class))); 64 | context.registerBean(DefaultErrorWebExceptionHandler.class, 65 | () -> errorHandler(context)); 66 | context.registerBean("webHandler", HttpHandler.class, () -> httpHandler(context)); 67 | context.refresh(); 68 | return context; 69 | } 70 | 71 | private HttpHandler httpHandler(GenericApplicationContext context) { 72 | return RouterFunctions.toHttpHandler(context.getBean(RouterFunction.class), 73 | HandlerStrategies.empty() 74 | .exceptionHandler(context.getBean(WebExceptionHandler.class)) 75 | .codecs(config -> config.registerDefaults(true)).build()); 76 | } 77 | 78 | private DefaultErrorWebExceptionHandler errorHandler( 79 | GenericApplicationContext context) { 80 | context.registerBean(ErrorAttributes.class, () -> new DefaultErrorAttributes()); 81 | context.registerBean(ErrorProperties.class, () -> new ErrorProperties()); 82 | context.registerBean(WebProperties.class, () -> new WebProperties()); 83 | DefaultErrorWebExceptionHandler handler = new DefaultErrorWebExceptionHandler( 84 | context.getBean(ErrorAttributes.class), 85 | context.getBean(WebProperties.class).getResources(), 86 | context.getBean(ErrorProperties.class), context); 87 | ServerCodecConfigurer codecs = ServerCodecConfigurer.create(); 88 | handler.setMessageWriters(codecs.getWriters()); 89 | handler.setMessageReaders(codecs.getReaders()); 90 | return handler; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/example/mini/MiniApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.mini; 17 | 18 | import com.example.config.ApplicationBuilder; 19 | 20 | import org.springframework.boot.SpringBootConfiguration; 21 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.web.reactive.config.EnableWebFlux; 24 | import org.springframework.web.reactive.function.server.RouterFunction; 25 | 26 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 27 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 28 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 29 | 30 | import reactor.core.publisher.Mono; 31 | 32 | /** 33 | * @author Dave Syer 34 | * 35 | */ 36 | @SpringBootConfiguration(proxyBeanMethods = false) 37 | @EnableWebFlux 38 | public class MiniApplication { 39 | 40 | public static void main(String[] args) throws Exception { 41 | try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { 42 | context.register(MiniApplication.class); 43 | context.refresh(); 44 | ApplicationBuilder.start(context); 45 | } 46 | } 47 | 48 | @Bean 49 | public RouterFunction userEndpoints() { 50 | return route(GET("/"), request -> ok().body(Mono.just("Hello"), String.class)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/example/reactor/ReactorApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.reactor; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | import com.example.config.ShutdownApplicationListener; 6 | import com.example.config.StartupApplicationListener; 7 | 8 | import reactor.core.publisher.Mono; 9 | import reactor.netty.DisposableServer; 10 | import reactor.netty.http.server.HttpServer; 11 | 12 | public class ReactorApplication { 13 | 14 | public static void main(String[] args) { 15 | DisposableServer server = 16 | HttpServer.create().port(8080) 17 | .route(routes -> 18 | routes.get("/", 19 | (request, response) -> response.sendString(Mono.just("Hello World!")) 20 | )).bindNow(); 21 | System.out.println(StartupApplicationListener.MARKER); 22 | System.out.println("Class count: netty=" + ManagementFactory 23 | .getClassLoadingMXBean().getTotalLoadedClassCount()); 24 | server.onDispose() 25 | .block(); 26 | System.out.println(ShutdownApplicationListener.MARKER); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.context.ApplicationListener=\ 2 | com.example.config.BeanCountingApplicationListener,\ 3 | com.example.config.StartupApplicationListener,\ 4 | com.example.config.ShutdownApplicationListener 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin-actj.properties: -------------------------------------------------------------------------------- 1 | dependencies.spring-boot-starter-actuator=org.springframework.boot:spring-boot-starter-actuator 2 | dependencies.spring-boot-starter-web=org.springframework.boot:spring-boot-starter-webflux 3 | dependencies.spring-boot-starter-jdbc=org.springframework.boot:spring-boot-starter-jdbc 4 | dependencies.h2=com.h2database:h2 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin-actr.properties: -------------------------------------------------------------------------------- 1 | dependencies.spring-boot-starter-web=org.springframework.boot:spring-boot-starter-webflux 2 | dependencies.spring-boot-starter-actuator=org.springframework.boot:spring-boot-starter-actuator 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin-empt.properties: -------------------------------------------------------------------------------- 1 | exclusions.spring-boot-starter-webflux=org.springframework.boot:spring-boot-starter-webflux 2 | exclusions.spring-boot-starter-logging=org.springframework.boot:spring-boot-starter-logging 3 | dependencies.spring-boot-starter=org.springframework.boot:spring-boot-starter 4 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin-jack.properties: -------------------------------------------------------------------------------- 1 | dependencies.spring-boot-starter-logging=org.springframework.boot:spring-boot-starter-logging 2 | dependencies.spring-boot-starter-json=org.springframework.boot:spring-boot-starter-json 3 | dependencies.jackson-databind=com.fasterxml.jackson.core:jackson-databind 4 | dependencies.hibernate-validator=org.hibernate.validator:hibernate-validator 5 | exclusions.slf4j-jdk14=org.slf4j:slf4j-jdk14 6 | exclusions.gson=com.google.code.gson:gson -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin-jdbc.properties: -------------------------------------------------------------------------------- 1 | dependencies.spring-boot-starter-web=org.springframework.boot:spring-boot-starter-webflux 2 | dependencies.spring-boot-starter-jdbc=org.springframework.boot:spring-boot-starter-jdbc 3 | dependencies.h2=com.h2database:h2 4 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/thin.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dsyer/spring-boot-micro-apps/b7be681d44217139bbea26cc8d580c6bdbeaeb27/src/main/resources/META-INF/thin.properties -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # logging.level.org.springframework=DEBUG 2 | # logging.level.com.example=DEBUG 3 | # logging.level.org.springframework.beans.factory.support=DEBUG 4 | #management.security.enabled=false -------------------------------------------------------------------------------- /src/test/java/com/example/bench/CdsBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.bench; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.InputStreamReader; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | import com.example.demo.DemoApplication; 24 | import com.example.empt.EmptyApplication; 25 | import com.example.manual.ManualApplication; 26 | import jmh.mbr.junit5.Microbenchmark; 27 | 28 | import org.junit.platform.commons.annotation.Testable; 29 | import org.openjdk.jmh.annotations.AuxCounters; 30 | import org.openjdk.jmh.annotations.AuxCounters.Type; 31 | import org.openjdk.jmh.annotations.Benchmark; 32 | import org.openjdk.jmh.annotations.BenchmarkMode; 33 | import org.openjdk.jmh.annotations.Fork; 34 | import org.openjdk.jmh.annotations.Level; 35 | import org.openjdk.jmh.annotations.Measurement; 36 | import org.openjdk.jmh.annotations.Mode; 37 | import org.openjdk.jmh.annotations.Param; 38 | import org.openjdk.jmh.annotations.Scope; 39 | import org.openjdk.jmh.annotations.Setup; 40 | import org.openjdk.jmh.annotations.State; 41 | import org.openjdk.jmh.annotations.TearDown; 42 | import org.openjdk.jmh.annotations.Warmup; 43 | import org.openjdk.jmh.util.FileUtils; 44 | 45 | @Measurement(iterations = 5, time = 1) 46 | @Warmup(iterations = 1, time = 1) 47 | @Fork(value = 2, warmups = 0) 48 | @BenchmarkMode(Mode.AverageTime) 49 | @Microbenchmark 50 | public class CdsBenchmark { 51 | 52 | @Benchmark 53 | @Testable 54 | public void main(CdsState state) throws Exception { 55 | state.run(); 56 | } 57 | 58 | @State(Scope.Thread) 59 | @AuxCounters(Type.EVENTS) 60 | public static class CdsState extends ProcessLauncherState { 61 | 62 | public static enum Sample { 63 | 64 | empt(EmptyApplication.class), // 65 | manl(ManualApplication.class), demo; 66 | 67 | private Class config; 68 | 69 | private Sample(Class config) { 70 | this.config = config; 71 | } 72 | 73 | private Sample() { 74 | this.config = DemoApplication.class; 75 | } 76 | 77 | public Class getConfig() { 78 | return config; 79 | } 80 | 81 | } 82 | 83 | private static final String APP_JSA = "app.jsa"; 84 | 85 | @Param({ "empt", "demo", "manl" }) // ({ "demo"}) 86 | Sample sample = Sample.demo; 87 | 88 | @Override 89 | public int getClasses() { 90 | return super.getClasses(); 91 | } 92 | 93 | @Override 94 | public int getBeans() { 95 | return super.getBeans(); 96 | } 97 | 98 | @Override 99 | public double getMemory() { 100 | return super.getMemory(); 101 | } 102 | 103 | @Override 104 | public double getHeap() { 105 | return super.getHeap(); 106 | } 107 | 108 | public CdsState() { 109 | super("target", "--server.port=0"); 110 | } 111 | 112 | @Override 113 | protected void customize(List args) { 114 | args.addAll(Arrays.asList("-Xshare:on", // "-XX:+UseAppCDS", 115 | "-XX:SharedArchiveFile=" + APP_JSA)); 116 | super.customize(args); 117 | } 118 | 119 | @TearDown(Level.Invocation) 120 | public void stop() throws Exception { 121 | super.after(); 122 | } 123 | 124 | @Setup(Level.Trial) 125 | public void start() throws Exception { 126 | if (sample != Sample.demo) { 127 | setProfiles(sample.toString()); 128 | } 129 | setMainClass(sample.getConfig().getName()); 130 | Process started = exec(new String[] { "-Xshare:off", // "-XX:+UseAppCDS", 131 | "-XX:DumpLoadedClassList=app.classlist", "-cp", "" }, "--server.port=0"); 132 | output(new BufferedReader(new InputStreamReader(started.getInputStream())), "Started"); 133 | started.destroyForcibly(); 134 | Process dump = exec(new String[] { "-Xshare:dump", // "-XX:+UseAppCDS", 135 | "-XX:SharedClassListFile=app.classlist", "-XX:SharedArchiveFile=" + APP_JSA, "-cp", "" }); 136 | System.err.println(FileUtils.readAllLines(dump.getInputStream())); 137 | dump.waitFor(); 138 | System.err.println("Finished dumping class data"); 139 | super.before(); 140 | } 141 | 142 | @Override 143 | protected String getClasspath() { 144 | return getClasspath(false); 145 | } 146 | 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/MicroBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.bench; 17 | 18 | import com.example.boot.BootApplication; 19 | import com.example.demo.DemoApplication; 20 | import com.example.empt.EmptyApplication; 21 | import com.example.func.BuncApplication; 22 | import com.example.func.CuncApplication; 23 | import com.example.func.FuncApplication; 24 | import com.example.manual.ManualApplication; 25 | import com.example.micro.MicroApplication; 26 | import com.example.mini.MiniApplication; 27 | import com.example.reactor.ReactorApplication; 28 | import jmh.mbr.junit5.Microbenchmark; 29 | import org.junit.platform.commons.annotation.Testable; 30 | import org.openjdk.jmh.annotations.AuxCounters; 31 | import org.openjdk.jmh.annotations.AuxCounters.Type; 32 | import org.openjdk.jmh.annotations.Benchmark; 33 | import org.openjdk.jmh.annotations.BenchmarkMode; 34 | import org.openjdk.jmh.annotations.Fork; 35 | import org.openjdk.jmh.annotations.Level; 36 | import org.openjdk.jmh.annotations.Measurement; 37 | import org.openjdk.jmh.annotations.Mode; 38 | import org.openjdk.jmh.annotations.Param; 39 | import org.openjdk.jmh.annotations.Scope; 40 | import org.openjdk.jmh.annotations.Setup; 41 | import org.openjdk.jmh.annotations.State; 42 | import org.openjdk.jmh.annotations.TearDown; 43 | import org.openjdk.jmh.annotations.Warmup; 44 | 45 | @Measurement(iterations = 5, time = 1) 46 | @Warmup(iterations = 1, time = 1) 47 | @Fork(value = 2, warmups = 0) 48 | @BenchmarkMode(Mode.AverageTime) 49 | @Microbenchmark 50 | public class MicroBenchmark { 51 | 52 | @Benchmark 53 | @Testable 54 | public void main(MainState state) throws Exception { 55 | state.run(); 56 | } 57 | 58 | @State(Scope.Thread) 59 | @AuxCounters(Type.EVENTS) 60 | public static class MainState extends ProcessLauncherState { 61 | 62 | public static enum Sample { 63 | 64 | empt(EmptyApplication.class), react(ReactorApplication.class), micro(MicroApplication.class), mini( 65 | MiniApplication.class), func(FuncApplication.class), bunc(BuncApplication.class), cunc( 66 | CuncApplication.class), boot(BootApplication.class), manl(ManualApplication.class), demo; 67 | 68 | private Class config; 69 | 70 | private Sample(Class config) { 71 | this.config = config; 72 | } 73 | 74 | private Sample() { 75 | this.config = DemoApplication.class; 76 | } 77 | 78 | public Class getConfig() { 79 | return config; 80 | } 81 | 82 | } 83 | 84 | @Param({ "empt", "demo", "manl", "func" }) 85 | private Sample sample; 86 | 87 | public MainState() { 88 | super("target"); 89 | } 90 | 91 | @Override 92 | public int getClasses() { 93 | return super.getClasses(); 94 | } 95 | 96 | @Override 97 | public int getBeans() { 98 | return super.getBeans(); 99 | } 100 | 101 | @Override 102 | public double getMemory() { 103 | return super.getMemory(); 104 | } 105 | 106 | @Override 107 | public double getHeap() { 108 | return super.getHeap(); 109 | } 110 | 111 | @TearDown(Level.Invocation) 112 | public void stop() throws Exception { 113 | super.after(); 114 | } 115 | 116 | @Setup(Level.Trial) 117 | public void start() throws Exception { 118 | if (sample != Sample.demo) { 119 | setProfiles(sample.toString()); 120 | } 121 | super.before(); 122 | setMainClass(sample.getConfig().getName()); 123 | } 124 | 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/ProcessLauncherState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.bench; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.File; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | import java.lang.management.ManagementFactory; 23 | import java.lang.reflect.Field; 24 | import java.lang.reflect.Method; 25 | import java.net.MalformedURLException; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.LinkedHashSet; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | import java.util.concurrent.CountDownLatch; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import org.apache.commons.logging.Log; 36 | import org.apache.commons.logging.LogFactory; 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | 40 | import org.springframework.beans.BeansException; 41 | import org.springframework.beans.factory.DisposableBean; 42 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 43 | import org.springframework.boot.SpringApplication; 44 | import org.springframework.boot.SpringBootConfiguration; 45 | import org.springframework.boot.context.event.ApplicationReadyEvent; 46 | import org.springframework.boot.loader.archive.Archive; 47 | import org.springframework.boot.loader.thin.ArchiveUtils; 48 | import org.springframework.boot.loader.thin.DependencyResolver; 49 | import org.springframework.boot.loader.thin.PathResolver; 50 | import org.springframework.context.ApplicationContext; 51 | import org.springframework.context.ApplicationContextAware; 52 | import org.springframework.context.ApplicationListener; 53 | import org.springframework.context.ConfigurableApplicationContext; 54 | import org.springframework.core.Ordered; 55 | import org.springframework.core.annotation.AnnotatedElementUtils; 56 | import org.springframework.core.annotation.Order; 57 | import org.springframework.util.ClassUtils; 58 | import org.springframework.util.ReflectionUtils; 59 | 60 | public class ProcessLauncherState { 61 | 62 | private static final Logger log = LoggerFactory.getLogger(ProcessLauncherState.class); 63 | 64 | public static final String CLASS_COUNT_MARKER = "Class count"; 65 | 66 | public static final String BEAN_COUNT_MARKER = "Bean count"; 67 | 68 | private Process started; 69 | 70 | private List args = new ArrayList<>(); 71 | 72 | private List progs = new ArrayList<>(); 73 | 74 | private static List DEFAULT_JVM_ARGS = Arrays.asList("-Xmx128m", "-cp", "", 75 | "-Djava.security.egd=file:/dev/./urandom", "-noverify", 76 | "-Dspring.config.location=classpath:/application.properties", "-Dspring.main.lazy-initialization=true", 77 | "-Dspring.data.jpa.repositories.bootstrap-mode=lazy", "-Dspring.cache.type=none", 78 | "-Dspring.jmx.enabled=false"); 79 | 80 | private File home; 81 | 82 | private String mainClass; 83 | 84 | private String name = "thin"; 85 | 86 | private String[] profiles = new String[0]; 87 | 88 | private BufferedReader buffer; 89 | 90 | private CountDownLatch latch = new CountDownLatch(1); 91 | 92 | private int classes; 93 | 94 | private int beans; 95 | 96 | private long memory; 97 | 98 | private long heap; 99 | 100 | public int getClasses() { 101 | return classes; 102 | } 103 | 104 | public int getBeans() { 105 | return beans; 106 | } 107 | 108 | public double getMemory() { 109 | return memory / (1024. * 1024); 110 | } 111 | 112 | public double getHeap() { 113 | return heap / (1024. * 1024); 114 | } 115 | 116 | public ProcessLauncherState(String dir, String... args) { 117 | this.args.addAll(DEFAULT_JVM_ARGS); 118 | String vendor = System.getProperty("java.vendor", "").toLowerCase(); 119 | if (vendor.contains("ibm") || vendor.contains("j9")) { 120 | this.args.addAll(Arrays.asList("-Xms32m", "-Xquickstart", "-Xshareclasses", "-Xscmx128m")); 121 | } 122 | else { 123 | this.args.addAll(Arrays.asList("-XX:TieredStopAtLevel=1")); 124 | } 125 | if (System.getProperty("bench.args") != null) { 126 | this.args.addAll(Arrays.asList(System.getProperty("bench.args").split(" "))); 127 | } 128 | this.progs.addAll(Arrays.asList(args)); 129 | this.home = new File(dir); 130 | } 131 | 132 | public void setMainClass(String mainClass) { 133 | this.mainClass = mainClass; 134 | } 135 | 136 | public void setName(String name) { 137 | this.name = name; 138 | } 139 | 140 | public void setProfiles(String... profiles) { 141 | this.profiles = profiles; 142 | } 143 | 144 | public void addArgs(String... args) { 145 | this.args.addAll(Arrays.asList(args)); 146 | } 147 | 148 | protected String getClasspath() { 149 | return getClasspath(true); 150 | } 151 | 152 | protected String getClasspath(boolean includeTargetClasses) { 153 | PathResolver resolver = new PathResolver(DependencyResolver.instance()); 154 | Archive root = ArchiveUtils.getArchive(ProcessLauncherState.class); 155 | List resolved = resolver.resolve(root, name, profiles); 156 | StringBuilder builder = new StringBuilder(); 157 | if (includeTargetClasses) { 158 | File app = new File("target/classes"); 159 | builder.append(app.getAbsolutePath()); 160 | app = new File("target/test-classes"); 161 | builder.append(File.pathSeparator).append(app.getAbsolutePath()); 162 | } 163 | else { 164 | File path = new File("target/micro-0.0.1-SNAPSHOT.jar"); 165 | if (!path.exists()) { 166 | throw new IllegalStateException("Cannot find jar file: " + path); 167 | } 168 | builder.append(path.getAbsolutePath()); 169 | } 170 | try { 171 | for (Archive archive : resolved) { 172 | if (archive.getUrl().equals(root.getUrl())) { 173 | continue; 174 | } 175 | if (builder.length() > 0) { 176 | builder.append(File.pathSeparator); 177 | } 178 | builder.append(file(archive.getUrl().toString())); 179 | } 180 | } 181 | catch (MalformedURLException e) { 182 | throw new IllegalStateException("Cannot find archive", e); 183 | } 184 | log.debug("Classpath: " + builder); 185 | return builder.toString(); 186 | } 187 | 188 | private String file(String path) { 189 | if (path.endsWith("!/")) { 190 | path = path.substring(0, path.length() - 2); 191 | } 192 | if (path.startsWith("jar:")) { 193 | path = path.substring("jar:".length()); 194 | } 195 | if (path.startsWith("file:")) { 196 | path = path.substring("file:".length()); 197 | } 198 | return path; 199 | } 200 | 201 | public String getPid() { 202 | String pid = null; 203 | try { 204 | if (started != null) { 205 | Field field = ReflectionUtils.findField(started.getClass(), "pid"); 206 | ReflectionUtils.makeAccessible(field); 207 | pid = "" + ReflectionUtils.getField(field, started); 208 | } 209 | } 210 | catch (Exception e) { 211 | if (started != null) { 212 | Method method = ReflectionUtils.findMethod(started.getClass(), "pid"); 213 | if (method!=null) { 214 | // ReflectionUtils.makeAccessible(method); 215 | pid = "" + ReflectionUtils.invokeMethod(method, started); 216 | } else { 217 | System.err.println("Cannot find PID for " + started); 218 | } 219 | } 220 | } 221 | return pid; 222 | } 223 | 224 | public void after() throws Exception { 225 | drain(); 226 | if (started != null && started.isAlive()) { 227 | latch.await(10, TimeUnit.SECONDS); 228 | Map metrics = VirtualMachineMetrics.fetch(getPid()); 229 | this.memory = VirtualMachineMetrics.total(metrics); 230 | this.heap = VirtualMachineMetrics.heap(metrics); 231 | if (metrics.containsKey("Classes")) { 232 | this.classes = metrics.get("Classes").intValue(); 233 | } 234 | System.out.println("Stopped " + mainClass + ": " + started.destroyForcibly().waitFor()); 235 | } 236 | } 237 | 238 | private BufferedReader getBuffer() { 239 | return this.buffer; 240 | } 241 | 242 | public void run() throws Exception { 243 | List jvmArgs = new ArrayList<>(this.args); 244 | customize(jvmArgs); 245 | started = exec(jvmArgs.toArray(new String[0]), this.progs.toArray(new String[0])); 246 | InputStream stream = started.getInputStream(); 247 | this.buffer = new BufferedReader(new InputStreamReader(stream)); 248 | monitor(); 249 | } 250 | 251 | public void before() throws Exception { 252 | int classpath = args.indexOf("-cp"); 253 | if (classpath >= 0 && args.get(classpath + 1).length() == 0) { 254 | args.set(classpath + 1, getClasspath()); 255 | } 256 | } 257 | 258 | protected void customize(List args) { 259 | } 260 | 261 | protected Process exec(String[] jvmArgs, String... progArgs) { 262 | List args = new ArrayList<>(Arrays.asList(jvmArgs)); 263 | args.add(0, System.getProperty("java.home") + "/bin/java"); 264 | if (mainClass.length() > 0) { 265 | args.add(mainClass); 266 | } 267 | int classpath = args.indexOf("-cp"); 268 | if (classpath >= 0 && args.get(classpath + 1).length() == 0) { 269 | args.set(classpath + 1, getClasspath()); 270 | } 271 | args.addAll(Arrays.asList(progArgs)); 272 | ProcessBuilder builder = new ProcessBuilder(args); 273 | builder.redirectErrorStream(true); 274 | builder.directory(getHome()); 275 | if (!"false".equals(System.getProperty("debug", "false"))) { 276 | System.out.println("Executing: " + builder.command()); 277 | } 278 | Process started; 279 | try { 280 | started = builder.start(); 281 | return started; 282 | } 283 | catch (Exception e) { 284 | e.printStackTrace(); 285 | throw new IllegalStateException("Cannot calculate classpath"); 286 | } 287 | } 288 | 289 | protected void monitor() throws Exception { 290 | // use this method to wait for an app to start 291 | output(getBuffer(), StartupApplicationListener.MARKER); 292 | } 293 | 294 | protected void finish() throws Exception { 295 | // use this method to wait for an app to stop 296 | output(getBuffer(), ShutdownApplicationListener.MARKER); 297 | } 298 | 299 | protected void drain() throws Exception { 300 | System.out.println("Draining console buffer"); 301 | output(getBuffer(), null); 302 | latch.countDown(); 303 | } 304 | 305 | protected void output(BufferedReader br, String marker) throws Exception { 306 | StringBuilder sb = new StringBuilder(); 307 | String line = null; 308 | if (!"false".equals(System.getProperty("debug", "false"))) { 309 | System.err.println("Scanning for: " + marker); 310 | } 311 | while ((marker != null || br.ready()) && (line = br.readLine()) != null 312 | && (marker == null || !line.contains(marker))) { 313 | sb.append(line + System.getProperty("line.separator")); 314 | if (!"false".equals(System.getProperty("debug", "false"))) { 315 | System.out.println(line); 316 | } 317 | if (line.contains(CLASS_COUNT_MARKER)) { 318 | classes = Integer.valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); 319 | } 320 | if (line.contains(BEAN_COUNT_MARKER)) { 321 | int count = Integer.valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); 322 | beans = count > beans ? count : beans; 323 | } 324 | line = null; 325 | } 326 | if (line != null) { 327 | sb.append(line + System.getProperty("line.separator")); 328 | } 329 | if ("false".equals(System.getProperty("debug", "false"))) { 330 | System.out.println(sb.toString()); 331 | } 332 | } 333 | 334 | public File getHome() { 335 | return home; 336 | } 337 | 338 | } 339 | 340 | class StartupApplicationListener implements ApplicationListener, ApplicationContextAware { 341 | 342 | public static final String MARKER = "Benchmark app started"; 343 | 344 | private static Log logger = LogFactory.getLog(StartupApplicationListener.class); 345 | 346 | private ApplicationContext context; 347 | 348 | @Override 349 | public void setApplicationContext(ApplicationContext context) throws BeansException { 350 | this.context = context; 351 | } 352 | 353 | @Override 354 | public void onApplicationEvent(ApplicationReadyEvent event) { 355 | if (!event.getApplicationContext().equals(this.context)) { 356 | return; 357 | } 358 | if (isSpringBootApplication(sources(event))) { 359 | try { 360 | logger.info(MARKER); 361 | } 362 | catch (Exception e) { 363 | } 364 | } 365 | } 366 | 367 | private boolean isSpringBootApplication(Set> sources) { 368 | for (Class source : sources) { 369 | if (AnnotatedElementUtils.hasAnnotation(source, SpringBootConfiguration.class)) { 370 | return true; 371 | } 372 | } 373 | return false; 374 | } 375 | 376 | private Set> sources(ApplicationReadyEvent event) { 377 | Method method = ReflectionUtils.findMethod(SpringApplication.class, "getAllSources"); 378 | if (method == null) { 379 | method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); 380 | } 381 | ReflectionUtils.makeAccessible(method); 382 | @SuppressWarnings("unchecked") 383 | Set objects = (Set) ReflectionUtils.invokeMethod(method, event.getSpringApplication()); 384 | Set> result = new LinkedHashSet<>(); 385 | for (Object object : objects) { 386 | if (object instanceof String) { 387 | object = ClassUtils.resolveClassName((String) object, null); 388 | } 389 | result.add((Class) object); 390 | } 391 | return result; 392 | } 393 | 394 | } 395 | 396 | @Order(Ordered.HIGHEST_PRECEDENCE) 397 | class ShutdownApplicationListener 398 | implements ApplicationListener, DisposableBean, ApplicationContextAware { 399 | 400 | private static final String SHUTDOWN_LISTENER = "SHUTDOWN_LISTENER"; 401 | 402 | public static final String MARKER = "Benchmark app stopped"; 403 | 404 | private ApplicationContext context; 405 | 406 | @Override 407 | public void setApplicationContext(ApplicationContext context) throws BeansException { 408 | this.context = context; 409 | } 410 | 411 | @Override 412 | public void onApplicationEvent(ApplicationReadyEvent event) { 413 | if (!event.getApplicationContext().equals(this.context)) { 414 | return; 415 | } 416 | if (isSpringBootApplication(sources(event))) { 417 | ((DefaultListableBeanFactory) event.getApplicationContext().getBeanFactory()) 418 | .registerDisposableBean(SHUTDOWN_LISTENER, this); 419 | } 420 | } 421 | 422 | @Override 423 | public void destroy() throws Exception { 424 | try { 425 | System.out.println(MARKER); 426 | } 427 | catch (Exception e) { 428 | } 429 | } 430 | 431 | private boolean isSpringBootApplication(Set> sources) { 432 | for (Class source : sources) { 433 | if (AnnotatedElementUtils.hasAnnotation(source, SpringBootConfiguration.class)) { 434 | return true; 435 | } 436 | } 437 | return false; 438 | } 439 | 440 | private Set> sources(ApplicationReadyEvent event) { 441 | Method method = ReflectionUtils.findMethod(SpringApplication.class, "getAllSources"); 442 | if (method == null) { 443 | method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); 444 | } 445 | ReflectionUtils.makeAccessible(method); 446 | @SuppressWarnings("unchecked") 447 | Set objects = (Set) ReflectionUtils.invokeMethod(method, event.getSpringApplication()); 448 | Set> result = new LinkedHashSet<>(); 449 | for (Object object : objects) { 450 | if (object instanceof String) { 451 | object = ClassUtils.resolveClassName((String) object, null); 452 | } 453 | result.add((Class) object); 454 | } 455 | return result; 456 | } 457 | 458 | } 459 | 460 | class BeanCountingApplicationListener implements ApplicationListener, ApplicationContextAware { 461 | 462 | private static Log logger = LogFactory.getLog(BeanCountingApplicationListener.class); 463 | 464 | private ApplicationContext context; 465 | 466 | @Override 467 | public void setApplicationContext(ApplicationContext context) throws BeansException { 468 | this.context = context; 469 | } 470 | 471 | @Override 472 | public void onApplicationEvent(ApplicationReadyEvent event) { 473 | if (!event.getApplicationContext().equals(this.context)) { 474 | return; 475 | } 476 | ConfigurableApplicationContext context = event.getApplicationContext(); 477 | log(context); 478 | } 479 | 480 | public void log(ConfigurableApplicationContext context) { 481 | int count = 0; 482 | String id = context.getId(); 483 | List names = new ArrayList<>(); 484 | while (context != null) { 485 | count += context.getBeanDefinitionCount(); 486 | names.addAll(Arrays.asList(context.getBeanDefinitionNames())); 487 | context = (ConfigurableApplicationContext) context.getParent(); 488 | } 489 | logger.info("Bean count: " + id + "=" + count); 490 | logger.debug("Bean names: " + id + "=" + names); 491 | try { 492 | logger.info( 493 | "Class count: " + id + "=" + ManagementFactory.getClassLoadingMXBean().getTotalLoadedClassCount()); 494 | } 495 | catch (Exception e) { 496 | } 497 | } 498 | 499 | } 500 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/ProcessLauncherStateTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.bench; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import java.io.File; 22 | 23 | import com.example.bench.CdsBenchmark.CdsState; 24 | import com.example.bench.CdsBenchmark.CdsState.Sample; 25 | import com.example.reactor.ReactorApplication; 26 | 27 | import org.junit.jupiter.api.Test; 28 | import org.junit.jupiter.api.condition.DisabledIf; 29 | import org.junit.jupiter.api.condition.EnabledOnJre; 30 | import org.junit.jupiter.api.condition.JRE; 31 | import org.junit.jupiter.api.extension.ExtendWith; 32 | import org.springframework.boot.test.system.CapturedOutput; 33 | import org.springframework.boot.test.system.OutputCaptureExtension; 34 | 35 | /** 36 | * @author Dave Syer 37 | * 38 | */ 39 | @ExtendWith(OutputCaptureExtension.class) 40 | @DisabledIf("noJarInTargetDir") 41 | public class ProcessLauncherStateTests { 42 | 43 | public static boolean noJarInTargetDir() { 44 | return !new File("target/micro-0.0.1-SNAPSHOT.jar").exists(); 45 | } 46 | 47 | @Test 48 | public void vanilla(CapturedOutput output) throws Exception { 49 | // System.setProperty("bench.args", 50 | // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000"); 51 | System.setProperty("debug", "true"); 52 | ProcessLauncherState state = new ProcessLauncherState("target"); 53 | state.setMainClass(ReactorApplication.class.getName()); 54 | state.before(); 55 | state.run(); 56 | state.after(); 57 | assertThat(output.toString()).contains("Benchmark app started"); 58 | assertThat(state.getHeap()).isGreaterThan(0); 59 | } 60 | 61 | @Test 62 | @EnabledOnJre({ JRE.JAVA_11, JRE.JAVA_14, JRE.JAVA_17 }) 63 | public void cds(CapturedOutput output) throws Exception { 64 | CdsState state = new CdsState(); 65 | state.sample = Sample.demo; 66 | // state.addArgs("-Ddebug=true"); 67 | state.start(); 68 | state.run(); 69 | state.after(); 70 | assertThat(output.toString()).contains("Netty started"); 71 | assertThat(output.toString()).contains("Benchmark app started"); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/ProfileBenchmark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.bench; 17 | 18 | import com.example.demo.DemoApplication; 19 | import com.example.empt.EmptyApplication; 20 | import jmh.mbr.junit5.Microbenchmark; 21 | import org.junit.platform.commons.annotation.Testable; 22 | import org.openjdk.jmh.annotations.AuxCounters; 23 | import org.openjdk.jmh.annotations.AuxCounters.Type; 24 | import org.openjdk.jmh.annotations.Benchmark; 25 | import org.openjdk.jmh.annotations.BenchmarkMode; 26 | import org.openjdk.jmh.annotations.Fork; 27 | import org.openjdk.jmh.annotations.Level; 28 | import org.openjdk.jmh.annotations.Measurement; 29 | import org.openjdk.jmh.annotations.Mode; 30 | import org.openjdk.jmh.annotations.Param; 31 | import org.openjdk.jmh.annotations.Scope; 32 | import org.openjdk.jmh.annotations.Setup; 33 | import org.openjdk.jmh.annotations.State; 34 | import org.openjdk.jmh.annotations.TearDown; 35 | import org.openjdk.jmh.annotations.Warmup; 36 | 37 | @Measurement(iterations = 5, time = 1) 38 | @Warmup(iterations = 1, time = 1) 39 | @Fork(value = 2, warmups = 0) 40 | @BenchmarkMode(Mode.AverageTime) 41 | @Microbenchmark 42 | public class ProfileBenchmark { 43 | 44 | @Benchmark 45 | @Testable 46 | public void main(MainState state) throws Exception { 47 | state.run(); 48 | } 49 | 50 | @State(Scope.Thread) 51 | @AuxCounters(Type.EVENTS) 52 | public static class MainState extends ProcessLauncherState { 53 | 54 | public static enum Sample { 55 | 56 | empt(EmptyApplication.class), demo, jack, actr, jdbc, actj; 57 | 58 | private Class config; 59 | 60 | private Sample(Class config) { 61 | this.config = config; 62 | } 63 | 64 | private Sample() { 65 | this.config = DemoApplication.class; 66 | } 67 | 68 | public Class getConfig() { 69 | return config; 70 | } 71 | 72 | } 73 | 74 | @Param 75 | private Sample sample; 76 | 77 | public MainState() { 78 | super("target"); 79 | } 80 | 81 | @Override 82 | public int getClasses() { 83 | return super.getClasses(); 84 | } 85 | 86 | @Override 87 | public int getBeans() { 88 | return super.getBeans(); 89 | } 90 | 91 | @Override 92 | public double getMemory() { 93 | return super.getMemory(); 94 | } 95 | 96 | @Override 97 | public double getHeap() { 98 | return super.getHeap(); 99 | } 100 | 101 | @TearDown(Level.Invocation) 102 | public void stop() throws Exception { 103 | super.after(); 104 | } 105 | 106 | @Setup(Level.Trial) 107 | public void start() throws Exception { 108 | if (sample != Sample.demo) { 109 | setProfiles(sample.toString()); 110 | } 111 | super.before(); 112 | setMainClass(sample.getConfig().getName()); 113 | } 114 | 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/VirtualMachineMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.example.bench; 17 | 18 | import java.util.Collections; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import javax.management.MBeanServerConnection; 23 | import javax.management.ObjectName; 24 | import javax.management.openmbean.CompositeData; 25 | import javax.management.remote.JMXConnector; 26 | import javax.management.remote.JMXConnectorFactory; 27 | import javax.management.remote.JMXServiceURL; 28 | 29 | import com.sun.tools.attach.VirtualMachine; 30 | 31 | /** 32 | * @author Dave Syer 33 | * 34 | */ 35 | @SuppressWarnings("restriction") 36 | public class VirtualMachineMetrics { 37 | 38 | static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; 39 | 40 | public static Map fetch(String pid) { 41 | if (pid == null) { 42 | return Collections.emptyMap(); 43 | } 44 | try { 45 | VirtualMachine vm = VirtualMachine.attach(pid); 46 | vm.startLocalManagementAgent(); 47 | String connectorAddress = vm.getAgentProperties() 48 | .getProperty(CONNECTOR_ADDRESS); 49 | JMXServiceURL url = new JMXServiceURL(connectorAddress); 50 | JMXConnector connector = JMXConnectorFactory.connect(url); 51 | MBeanServerConnection connection = connector.getMBeanServerConnection(); 52 | gc(connection); 53 | Map metrics = new HashMap<>( 54 | new BufferPools(connection).getMetrics()); 55 | metrics.putAll(new Threads(connection).getMetrics()); 56 | metrics.putAll(new Classes(connection).getMetrics()); 57 | metrics.putAll(new OperatingSystem(connection).getMetrics()); 58 | vm.detach(); 59 | return metrics; 60 | } 61 | catch (Exception e) { 62 | e.printStackTrace(); 63 | return Collections.emptyMap(); 64 | } 65 | } 66 | 67 | private static void gc(MBeanServerConnection mBeanServer) { 68 | try { 69 | final ObjectName on = new ObjectName("java.lang:type=Memory"); 70 | mBeanServer.getMBeanInfo(on); 71 | mBeanServer.invoke(on, "gc", new Object[0], new String[0]); 72 | } 73 | catch (Exception ignored) { 74 | System.err.println("Unable to gc"); 75 | } 76 | } 77 | 78 | public static long total(Map metrics) { 79 | return BufferPools.total(metrics); 80 | } 81 | 82 | public static long heap(Map metrics) { 83 | return BufferPools.heap(metrics); 84 | } 85 | 86 | } 87 | 88 | class Threads { 89 | 90 | private final MBeanServerConnection mBeanServer; 91 | 92 | public Threads(MBeanServerConnection mBeanServer) { 93 | this.mBeanServer = mBeanServer; 94 | } 95 | 96 | public Map getMetrics() { 97 | final Map gauges = new HashMap<>(); 98 | final String name = "Threads"; 99 | try { 100 | final ObjectName on = new ObjectName("java.lang:type=Threading"); 101 | mBeanServer.getMBeanInfo(on); 102 | Integer value = (Integer) mBeanServer.getAttribute(on, "ThreadCount"); 103 | gauges.put(name(name), Long.valueOf(value) * 1024 * 1024); 104 | } 105 | catch (Exception ignored) { 106 | System.err.println("Unable to load thread pool MBeans: " + name); 107 | } 108 | return Collections.unmodifiableMap(gauges); 109 | } 110 | 111 | private static String name(String name) { 112 | return name.replace(" ", "-"); 113 | } 114 | 115 | } 116 | 117 | class Classes { 118 | 119 | private final MBeanServerConnection mBeanServer; 120 | 121 | public Classes(MBeanServerConnection mBeanServer) { 122 | this.mBeanServer = mBeanServer; 123 | } 124 | 125 | public Map getMetrics() { 126 | final Map gauges = new HashMap<>(); 127 | final String name = "Classes"; 128 | try { 129 | final ObjectName on = new ObjectName("java.lang:type=ClassLoading"); 130 | mBeanServer.getMBeanInfo(on); 131 | Integer value = (Integer) mBeanServer.getAttribute(on, "LoadedClassCount"); 132 | gauges.put(name(name), Long.valueOf(value)); 133 | } 134 | catch (Exception ignored) { 135 | System.err.println("Unable to load thread pool MBeans: " + name); 136 | } 137 | return Collections.unmodifiableMap(gauges); 138 | } 139 | 140 | private static String name(String name) { 141 | return name.replace(" ", "-"); 142 | } 143 | 144 | } 145 | 146 | 147 | 148 | class OperatingSystem { 149 | 150 | private final MBeanServerConnection mBeanServer; 151 | 152 | public OperatingSystem(MBeanServerConnection mBeanServer) { 153 | this.mBeanServer = mBeanServer; 154 | } 155 | 156 | public Map getMetrics() { 157 | final Map gauges = new HashMap<>(); 158 | final String name = "CpuLoad"; 159 | try { 160 | final ObjectName on = new ObjectName("java.lang:type=OperatingSystem"); 161 | mBeanServer.getMBeanInfo(on); 162 | Double value = (Double) mBeanServer.getAttribute(on, "CpuLoad"); 163 | gauges.put(name(name), Double.valueOf(value)); 164 | } 165 | catch (Exception ignored) { 166 | System.err.println("Unable to load cpu MBeans: " + name); 167 | } 168 | return Collections.unmodifiableMap(gauges); 169 | } 170 | 171 | private static String name(String name) { 172 | return name.replace(" ", "-"); 173 | } 174 | 175 | } 176 | 177 | class BufferPools { 178 | 179 | private static final String[] ATTRIBUTES = { "Code Cache", "Compressed Class Space", 180 | "Metaspace", "PS Eden Space", "PS Old Gen", "PS Survivor Space", 181 | "G1 Eden Space", "G1 Old Gen", "G1 Survivor Space", "CodeHeap 'non-nmethods'", 182 | "CodeHeap 'non-profiled nmethods'", "CodeHeap 'profiled nmethods'" }; 183 | 184 | private final MBeanServerConnection mBeanServer; 185 | 186 | public BufferPools(MBeanServerConnection mBeanServer) { 187 | this.mBeanServer = mBeanServer; 188 | } 189 | 190 | public static long total(Map metrics) { 191 | long total = 0; 192 | for (int i = 0; i < ATTRIBUTES.length; i++) { 193 | final String name = name(ATTRIBUTES[i]); 194 | total += (Long)(metrics.containsKey(name) ? metrics.get(name) : 0L); 195 | } 196 | total += (Long)metrics.getOrDefault("Threads", 0L); 197 | return total; 198 | } 199 | 200 | public static long heap(Map metrics) { 201 | long total = 0; 202 | for (int i = 0; i < ATTRIBUTES.length; i++) { 203 | final String name = name(ATTRIBUTES[i]); 204 | if (name.startsWith("PS") || name.startsWith("G1")) { 205 | total += (Long)(metrics.containsKey(name) ? metrics.get(name) : 0L); 206 | } 207 | } 208 | return total; 209 | } 210 | 211 | public Map getMetrics() { 212 | final Map gauges = new HashMap<>(); 213 | for (int i = 0; i < ATTRIBUTES.length; i++) { 214 | final String name = ATTRIBUTES[i]; 215 | try { 216 | final ObjectName on = new ObjectName( 217 | "java.lang:type=MemoryPool,name=" + name); 218 | mBeanServer.getMBeanInfo(on); 219 | CompositeData value = (CompositeData) mBeanServer.getAttribute(on, 220 | "Usage"); 221 | gauges.put(name(name), (Long) value.get("used")); 222 | } 223 | catch (Exception ignored) { 224 | System.err.println("Unable to load memory pool MBeans: " + name); 225 | } 226 | } 227 | return Collections.unmodifiableMap(gauges); 228 | } 229 | 230 | private static String name(String name) { 231 | return name.replace(" ", "-"); 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /src/test/java/com/example/bench/VirtualMachineMetricsTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.bench; 18 | 19 | import java.util.Map; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import org.springframework.boot.system.ApplicationPid; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | /** 27 | * @author Dave Syer 28 | * 29 | */ 30 | public class VirtualMachineMetricsTests { 31 | 32 | @Test 33 | public void vanilla() throws Exception { 34 | Map metrics = VirtualMachineMetrics.fetch(new ApplicationPid().toString()); 35 | assertThat(metrics).containsKeys("Classes"); 36 | assertThat(VirtualMachineMetrics.heap(metrics)).isGreaterThan(0); 37 | System.err.println(metrics); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.reactive.server.WebTestClient; 10 | 11 | @SpringBootTest(classes = DemoApplication.class) 12 | @DirtiesContext 13 | @AutoConfigureWebTestClient 14 | public class DemoApplicationTests { 15 | 16 | @Autowired 17 | private WebTestClient client; 18 | 19 | @Test 20 | public void get() throws Exception { 21 | client.get().uri("/").exchange().expectBody(String.class).isEqualTo("Hello"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/RestConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.demo; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 26 | 27 | import org.springframework.boot.SpringApplication; 28 | import org.springframework.boot.SpringBootConfiguration; 29 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 30 | import org.springframework.http.ResponseEntity; 31 | import org.springframework.web.bind.annotation.GetMapping; 32 | import org.springframework.web.bind.annotation.PostMapping; 33 | import org.springframework.web.bind.annotation.RequestBody; 34 | import org.springframework.web.bind.annotation.RestController; 35 | 36 | @SpringBootConfiguration 37 | @EnableAutoConfiguration 38 | @RestController 39 | public class RestConfiguration { 40 | 41 | private static Log logger = LogFactory.getLog(RestConfiguration.class); 42 | 43 | List inputs = new ArrayList<>(); 44 | 45 | private Iterator outputs = Arrays.asList("hello", "world").iterator(); 46 | 47 | @GetMapping("/") 48 | ResponseEntity home() { 49 | logger.info("HOME"); 50 | if (this.outputs.hasNext()) { 51 | return ResponseEntity.ok(this.outputs.next()); 52 | } 53 | return ResponseEntity.notFound().build(); 54 | } 55 | 56 | @PostMapping("/") 57 | ResponseEntity accept(@RequestBody String body) { 58 | logger.info("ACCEPT"); 59 | this.inputs.add(body); 60 | return ResponseEntity.accepted().body(body); 61 | } 62 | 63 | public static void main(String[] args) throws Exception { 64 | SpringApplication.run(RestConfiguration.class, args); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/example/demo/StaticApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.demo; 2 | 3 | import java.util.LinkedHashSet; 4 | import java.util.Map.Entry; 5 | import java.util.Set; 6 | 7 | import org.junit.jupiter.api.Disabled; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; 12 | import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes; 13 | import org.springframework.context.ConfigurableApplicationContext; 14 | import org.springframework.util.StringUtils; 15 | 16 | @Disabled 17 | public class StaticApplicationTests { 18 | 19 | @Test 20 | public void test() throws Exception { 21 | StringBuilder builder = new StringBuilder("package com.sample;\n\n" 22 | + "import org.springframework.boot.SpringApplication;\n" 23 | + "import org.springframework.context.annotation.Configuration;\n" 24 | + "import org.springframework.context.annotation.Import;\n"); 25 | Set imports = new LinkedHashSet<>(); 26 | Set configs = new LinkedHashSet<>(); 27 | ConfigurableApplicationContext context = SpringApplication 28 | .run(DemoApplication.class, "--debug", "--server.port=0"); 29 | ConditionEvaluationReport report = context 30 | .getBean(ConditionEvaluationReport.class); 31 | for (Entry entry : report 32 | .getConditionAndOutcomesBySource().entrySet()) { 33 | ConditionAndOutcomes outcomes = entry.getValue(); 34 | String name = entry.getKey(); 35 | if (outcomes.isFullMatch() && isAutoConfig(name)) { 36 | imports.add(name); 37 | configs.add(StringUtils.getFilenameExtension(name)); 38 | } 39 | } 40 | for (String type : report.getUnconditionalClasses()) { 41 | imports.add(type); 42 | configs.add(StringUtils.getFilenameExtension(type)); 43 | } 44 | for (String string : imports) { 45 | builder.append("import " + string + ";\n"); 46 | } 47 | builder.append("\n@Configuration\n"); 48 | builder.append("@Import({"); 49 | builder.append( 50 | StringUtils.collectionToDelimitedString(configs, ".class, \n ")); 51 | builder.append(".class})\n"); 52 | builder.append("public class SlimApplication {\n" + "\n" 53 | + " public static void main(String[] args) {\n" 54 | + " SpringApplication.run(SlimApplication.class, args);\n" + " }\n" 55 | + "}\n" + ""); 56 | context.close(); 57 | System.err.println(builder); 58 | } 59 | 60 | private boolean isAutoConfig(String name) { 61 | return !name.contains("#") && !name.contains("$") 62 | && name.contains("AutoConfiguration"); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/example/micro/MicroApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.example.micro; 18 | 19 | import org.junit.jupiter.api.AfterEach; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import org.springframework.context.ConfigurableApplicationContext; 24 | import org.springframework.test.web.reactive.server.WebTestClient; 25 | import org.springframework.web.server.WebHandler; 26 | 27 | /** 28 | * @author Dave Syer 29 | * 30 | */ 31 | public class MicroApplicationTests { 32 | 33 | private WebTestClient client; 34 | 35 | private ConfigurableApplicationContext context; 36 | 37 | @BeforeEach 38 | public void init() { 39 | context = new MicroApplication().run(); 40 | WebHandler webHandler = context.getBean(WebHandler.class); 41 | client = WebTestClient.bindToWebHandler(webHandler).build(); 42 | } 43 | 44 | @AfterEach 45 | public void close() { 46 | if (context != null) { 47 | context.close(); 48 | } 49 | } 50 | 51 | @Test 52 | public void test() { 53 | client.get().uri("/").exchange().expectBody(String.class).isEqualTo("Hello"); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/example/mini/MiniApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.mini; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.annotation.DirtiesContext; 7 | 8 | @SpringBootTest(properties = "server.port=0") 9 | @DirtiesContext 10 | public class MiniApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() throws Exception { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------