Whats New in Spring Boot 2.3

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
- Hey everybody, Phil Webb here from the Spring Boot team. Ordinarily, when we release a bit of software, we get the opportunity to come to conferences and tell you all about it. Unfortunately, with the global COVID-19 pandemic going on, that's not really an option. So I thought it might be useful to record a little screen cast to show you what's new in Spring Boot 2.3. Let's get into it. (technological music) So the first thing that's new in Spring Boot 2.3 is the release itself. And by that I mean you might not be expecting it. If you've been using Spring Boot for a while, you're probably used to upgrading once every 12 months or so, and this release is about six months early. That's because, starting with Spring Boot 2.3, we're going to try something different. We're going to start releasing based on a calendar date, so every six months you can expect a new release. This means that releases might be lighter, there might be less in them, but the cadence is going be more predictable, and it's going be easier for you to upgrade. There are a few other projects that are joining us on this adventure, for example Spring Data is also moving to a six-month release cadence. Other projects are going to stick to feature-based releases. So for example Spring Framework is only going to release when it's ready. Regardless, you can be sure that when we release a new version of Spring Boot, we're going to pull in the latest versions of all the dependencies that are available and make sure that they work well together. Talking of dependency upgrades, there are a few things you need to be aware of with this release, especially if you're a Spring Data user. This version of Spring Boot pulls in Spring Data Neumann, and that brings with it some driver updates. Specifically the Cassandra driver is now at version four, and the Couchbase driver is at version three. These include breaking changes, so it's possible that you may need to change your code when you upgrade. Other notable upgrades in this release include Micrometer 1.5, and this is the first release of Spring Boot where we're offering Java 14 support. The main part of this presentation however, I want to talk about Kubernetes, and for that I want to dive into some demos... So here we are at start.spring.io, and what I'm going to do is generate a new project that we can play with. I'm going to generate a Maven project, I'm going to make Java the language of choice, the latest version in the 2.3 line, and then I'm going to upgrade the Java version to 14. I also need to add a couple of dependencies, so I'm going to add a web dependency. And I'm also going to add Actuator, because I want to demo some features from that later. Click generate, save the file, and we should have a zip file. Let's head over to our terminal. And then we can unzip this demo file and import it into our IDE. So we need to make this wiggle a little bit, so let's add a @Controller, let's just a Hello World @Controller. To do that, I just jump in the code, I'm going to create a new class, I'm going to call it 'HelloWorld...'. 'HelloWorldController`, and then I'm going to make it a @RestController, and I'll add one endpoint. And just make it say, "Hello World!". And we're going to map that to '/hello'. So that's it, that's our basic application up and running, let's see if it works. Here we are. And there we go, jump over to the web browser and just make sure that we can hit that 'hello' endpoint. And there we go, "Hello World!" Now pretty much the first thing you're going to want to do when working with Kubernetes is take that application and put it in a container. Now it's always been possible to do this with Spring Boot, but sometimes the results were less than optimal. For example, you may have found that if you change a lot of files, that your images start to get bloated, and new layers are created each time and a lot of disk space gets consumed. Or you may have found that you simply want it to run with the fat jar and performance wasn't as good as you expected. So to help with this, what we've tried to do is make image creation a first-class concern. And we've done that by leaning on something called Buildpacks. Now Buildpacks are technology that have been around in some form or other for quite a while. And the Buildpacks.io project is an open-source project, developed jointly by VMware and Heroku. What a Buildpack does is, it takes your application, and it creates a sensible Docker image. So let's have a look at what we've got here. I'm going to jump over to the manual and just show you the Maven plugin documentation. So specifically I want to show you how you can package an OCI image. So section six of our reference guide includes information about this new goal called build-image. Now you can add that goal as an execution, as is demonstrated here, or you can just call it. So for instance if I head back over to this command line prompt, I can just type 'mvn spring-boot:build-image'. And what's going to happen here is, my app's going to build, the tests are going to run and then an image is going to get created for me. So what we'll see is some output at the later half of this build that shows Buildpack running. And here you can see, this area here, is the Buildpack doing its stuff, so it's detecting Buildpacks that can work with the software. And then ultimately it's spat out this Docker image. Now by default, when you use the plugins, the name of the image will be the name of your project, and the tag of the image will be the version of your project. So in this case we've got demo as the name, and 0.0.1-SNAPSHOT as the tag. If I want to run that application, I can just, let me stop the existing one, and then just type docker run, and then I'm going to tell it to use an interactive mode and port forward 8080 to 8080. And then run this image and tag. And there we go, you can see our application's running, and if we head back over to the web browser and hit 'localhost 8080/hello', we get "Hello World!" again, but this time from a Docker image. One tool that's very useful if you're working with Docker images is something called Dive, and Dive will let you dig into an image and see the layers that make up that image. So I want to show you, by default, what the Buildpack has done here. So I'm going to type 'dive', and then I'm going to give it the name of the image and the tag that I want it to view. Once that's loaded I'm just going to head over here and uncheck the unmodified option, so that we can only see things that are added, or removed, or changed. The way this screen is organized is, you've got contents on the right and the layers on the left. So if I scroll down, you can see which layer adds which content. So starting from the top, we can see some APT stuff, some more operating system stuff, and then a little bit further down we should see the JRE itself being added. And then some additional options by the looks of things, but the layers at the bottom are the ones that are most important. This 18 MB layer at the bottom is our application. There's one final layer that the Buildpack also adds, which just provides some metadata for it to read later. Now I mentioned that having one layer that gets updated with every code change is less then optimal. So one thing we can do now is tell the Buildpack and Spring Boot to generate a layer jar. In order to do that we need to add some configuration. If I head back over to the documentation again, in section 5.1 you can see the configuration that you need to add to your Maven plugin if you want to create a layered jar. So I'm just going to copy and paste that and stick it into my POM file. Let's head over here and build this application again. And this time, let's open up Dive and see what the differences are. So again, I'm going to just show the updated content. I'm going to scroll down to the interesting stuff, and already we can see that there is some differences. So, unlike previously, we now have one 18 MB layer that just contains our jar files. We then got something that's about 200 kB, which is our Spring Boot Loader classes. We've then got a layer that's 0 bytes, and this would be snapshot dependencies if you had any. And then finally we've got our kind of application layer itself. So here you can see that our application is now just 5.2 kilobytes. Now this layered approach is really useful if you've got an application that changes frequently, because it means the only layer that's updated is that one that's 5.2K near the end. If your dependencies are the same, then they don't change. And that means that you take up a lot less disk space with each edit. Now the Buildpack stuff's quite useful, and we think a lot of people will like it, but we also understand that there's a big ecosystem around Dockerfiles, so we wanted to be able to support Dockerfiles as well. And to do that we've added something called a jar mode. To show you our jar modes I'm going to jump back into this code again. And this time show you what happens if you run the application as a jar. So ordinarily, if you run 'java -jar' and then our fat jar, the application just starts. However, if you want to run this thing in a different mode, what we can do is add a flag to the front. So if I add '-Djarmode=layertools', what this is going to do is, it's going to start that jar, and it's going to offer me a different set of functionality. What this is actually doing is, it's just starting up the normal launcher and then diverting off to a different process, and providing some built-in functionality that we think you might want to use. Layer tools is the first one that we're offering. You can see here we have three commands. We have 'list', 'extract', and 'help', and help is the one that's being displayed by default. If I run this command again and put 'list' on the end, you'll see we get the output that represents the layers that this jar supports. So in this example the jar's got 'dependencies' as the top layer, the 'Spring Boot loader' classes that we saw previously, then 'snapshot dependencies', and then finally your 'application'. So the other command we've got here is 'extract'. So if I run this command again, this time change it to 'extract', and give it a destination. I'm just going to create a temp directory. What this is going to do is, it's going to extract the contents of that jar, so that this jar is now kind of self-extracting. But it's going to extract the contents into layer folders. So let me show you the contents of that unpacked area. So you can see here we have a folder called 'application', a folder called 'dependencies', one called 'snapshot dependencies', and one called 'Spring Boot Loader'. And these match the contents of the layers that 'list' provides. If I dig into a bit more detail and just show you a tree view of that. You can see we have our application layer, it's the same kind of light 5.2K stuff. Our libraries are all in the lib folder under the 'dependencies' layer, and the 'Spring Boot Loader' provides the loader classes that we need. Now the reason we've added this jar mode functionality is allows you to write proper Dockerfiles. So if I jump back into the documentation, and this time I'm going to look at the main reference guide. So this chapter explains how you can write a well-formed Dockerfile that's going to be both performant and efficient in terms of disk space. And the way that we recommend you do this is to use a multi-stage Dockerfile. So I'm going to copy and paste this example here and we're just going to edit it just a little bit. So let me create a Dockerfile. And paste that contents, I need to make a couple of minor tweaks. I want to change the JDK version to 14, both in the builder and the main Docker contents. So if you've not seen these files before, they're quite interesting. They're called multi-stage Dockerfiles. And what they allow you to do is have one section that's effectively thrown away when it's no longer needed. So in this example we have a first stage, which is the 'builder' stage, and a second stage, which is our main kind of Dockerfile as you probably know it. So what the first stage does is, we've named it builder, and it's going to take our JAR file, it's going to copy it into this temporary ... Docker area, and then it's going to run that layertools extract command. So that's going to end up with a bunch of files in this kind of temporary area. The second stage we then make use of those files. So here you can see that we're again coming from a particular JDK version, setting a work directory, and then we're saying we want to copy... And this from command is saying, copy it from this previous operation that we ran. So here we're copying dependencies, and the loader, and the snapshot dependencies, and the application. It's important to note that those orderings need to be correct, they need to match what the list command says in order to have an efficient Docker image. The final part, here we have an entry point, and this is just going to run Java and point at one of our launcher classes. And this launcher is a piece of code that if you run 'java -jar', it's responsible for loading the fat jar and finding the nested jars. But you can also actually run it with an exploded form, as we're doing here, and it will just pick the appropriate classpath and load the nested jars in the same way. So if we want to run this we just jump back over here and we type 'docker build' and give it the current directory. And you can see here the stage is working, so we have our builder stage that starts off, the jar gets copied up, the extract command runs and creates these folders, and then a little bit further down we copy the contents into the image. Now unlike the Maven plugin, the Dockerfile doesn't actually have a name here, because I haven't specified one. I could use that hash and point Dive at it, and we can take a look at the contents. Again, just jump over, show any of the updates, and you can see the various commands that make up this Docker image, and these are the ones that we're interested in here. And again, they match what the Buildpack does, so our 'libraries', our 'dependencies'. We have our 'loader classes', our 'snapshot dependency' is empty, and then our 'application classes', again 5.2k. There's a few things I want to show you inside the jar file that will explain how the jartools command works. So I'm going to jump back over into my window, and I'm just going to look in the target directory and unpack the jar file. And if I look in the BOOT-INF folder, you'll see there are a couple of files here that are new. So the first is called 'classpath.idx', and the second is 'layers.idx'. Now 'classpath.idx' solves one of the longstanding issues with Spring Boot. And that is that when you run an exploded mode, the class path order was non-deterministic. If I just 'cat' the contents of this, you can see it's a fairly simple file. And it just lists the jars that we're going to load and the order that we're going to load them. The other file we've got in here is a layers file. So if I 'cat' the contents of 'layers.idx', what you'll see here is a YAML-like file that describes the layers and the order that they're going to appear in, along with their contents. So you can see we have a 'dependencies' layer that contains the BOOT-INF/lib/ folder, the 'loader' layer that contains the contents of the org/ folder, the 'snapshot dependencies', which is empty, and then our 'application' layer, which contains classes and these idx files, along with the META-INF folder. And this layer.idx file describes the entire jar, so once you've read this file, we've got the complete jar carved up into layers. I'm going to jump back over to the documentation, because I want to show you something in the Maven plugin documentation that explains a little bit how you can customize the layers that you might want. Now because that layer file is quite flexible, it allows you to choose how you want to carve up your application. We've picked sensible defaults that we think will work for most applications, but it might be possible that you want to do something a little bit unusual. Perhaps you have a bunch of static resources and it makes sense for you to have those in their own layer. Or perhaps you have some kind of custom libraries that you use inside your organization that change less frequently than your application, but more frequently than our dependencies. In order to do that, what you can do is, you can define a layers configuration. So here in the Maven documentation we've got a little bit of explanation about what we need to do. I'm going to copy this line, head back over to my configuration and just add it in. And what this is saying is "I want you to configure the layers using this layers.xml file". So we need to go ahead and create that in our source directory. And then what I'm going to do is, I'm going to jump over to the documentation and look at some examples. And down here we have custom layers configuration example that shows you the kind of file that you can write. Now depending on whether you're using Maven or Gradle, the customization logic is slightly different. So in Maven we've chosen an XML file that you need to define yourself, and in Gradle there's a DSL that's part of the build plugin. Both are documented in the reference documentation. I'll briefly explain how this layers file works, because the concepts are the same. So what we have here is three main sections. The first section is 'application', the second is 'dependencies', and the third is 'layer order'. Now 'application' defines the parts of the code that aren't jars, that aren't your dependencies, and explains how you should slice those up. 'Dependencies' of the jars themselves, and then the 'layer order' is the order that these layers should actually appear. Each of these work in a very similar way. You have these 'into' blocks, and this is saying "I want you to take a section of this code and I want you to put it into a specific layer". So in this example we're saying I want you to put into the 'spring-boot-loader' layer anything that's in this folder. This final 'into' layer here is saying anything that's left over, just put in the 'application' layer. If we look at dependencies, it's got the same structure. We're going to say here I want to put anything that ends in snapshot into my 'snapshot dependencies' layer. Anything that starts with com.acme I'm going to put in my 'company-dependencies' layer, and then anything that's left over I'm just going to put in the 'dependencies' layer. So that's anything that hasn't been covered by the previous ones gets put into this default layer at the end. And then finally our ordering, which makes sense here. So here we're saying we expect 'dependencies' will change least frequently, followed by 'spring-boot-loader', then 'snapshot-dependencies', then my 'company-dependencies' which we expect to change more frequently than our regular dependencies, and then finally my 'application', which we expect to change the most frequently. Now obviously we haven't got any com.acme dependencies, so I'm just going to change this and pick a random dependency that we have got, so I'm going to pick Jackson for this. So I'm going to do fasterxml.jackson.core. And what this is going to say is, any of the libraries that are in this group will end up in my 'company-dependencies' layer. Let's head over and rebuild our application. I'm just going to run 'build-image' again, let it do its thing, let the Buildpack kick in. So start Dive again. So we can see, if we jump down here, we can now have a few more layers. So we have our 16 MB dependencies layer. Then we have our loader layer, then we have our snapshot dependencies, which is empty again. And then we have a 1.8 MB for our company dependencies, in this case it's just got Jackson inside it. Then we have our application, and then the Buildpack layer as usual. So we hope that that layers configuration is simple enough to understand, but powerful enough to allow you to do what you want to carve up your jar if our default layers aren't working for you. The next thing I want to show you is graceful shutdown. So to do that, I'm going to jump back into the code. And I'm going to make some changes to my controller. What I want to do here is, I want to make this controller act like it's very, very slow. So I'm just going to add a Thread.sleep(). And I'm going to make this really slow, a five-second delay for each request. If I jump back over to the command line, and this time I'm going to run this application using 'spring-boot:run'. Let's jump into our web browser, and hit our '/hello' endpoint. And you can see there that that request took a significant amount of time. What I'm going to do here is, I'm going to turn on the web tools, and I'm going to show you the network traffic. I'm going to hit refresh again and you can see the request happening and it's going to take about five seconds and then the response coming back. Now in early versions of Spring Boot, if you had a request like this and your application needed to be stopped in order to be moved; so for instance if you're running Kubernetes and the pod needed to be relocated somewhere else; then this kind of long-running request would not shut down properly. I can show you that now if I hit refresh on this again. And then jump back over here and just ctrl-c like mad until this application stops. You can see not only do we get an ugly stacktrace, but if we head back to our web browser we got an incomplete response. So here the user experience is not great. If the user happens to be hitting request when your application is shut down, they don't get to finish that request and they get this horrible experience. Now in order to fix that what we've done is add something called graceful shutdown support. So I'm going to jump back over to my application and I'm going to open my 'application.properties'. And what I need to do now is add a couple of properties. The first is called [...] 'server.shutdown', and there are two values available. The 'immediate' is the mode that we ship out of the box, and 'graceful' is a new mode. So I'm going to choose 'graceful'. The second property that I need to set is a lifecycle property, and that's 'timeout-per-shutdown-phase', and I'm going to give that 20 seconds. And what this property means is when Spring is running a phase, when it's doing a shutdown of a lifecycle phase, it's going to give it this amount of time to complete that phase. So 20 seconds is about right for what we want. Let's head back over to the app again and do 'spring-boot:run'. And then I'm going to head back to my web browser, do a refresh, and jump back over here and hit ctrl-c. And immediately you can see there are some differences. So we no longer got this stacktrace, instead we got this message "Commencing graceful shutdown. Waiting for active request to complete." So what that's going to do is, it's going to drain the web server. All the active threads are going to complete, but no new ones are going to be accepted. If we head back over to our web browser, you can see that this time we actually did get a response. So an inflight request was finished. Now this kind of functionality is very important if you're running in a cloud platform like Kubernetes. Kubernetes wants to be able to take your application, shut it down, and start up a new version somewhere else, and we want that experience to be as seamless as possible for our users. One of the ways that Kubernetes is able to understand about your application, is through something called probes. To show you probes I'm going to jump back over to the code. So I need to add a couple of properties. The first one I need to add is the cloud-platform property. And I'm going to pretend that we're running on Kubernetes. Now Spring Boot's had cloud platform support for a while, you can do @ConditionalOn the cloud platform and pick the platform that you're running under, but previously you weren't able to customize it. We've now added a property, which means it's very easy to pretend you're in a specific cloud platform without actually being there. It's quite useful for testing, and it's quite useful for demos. The second property that I want to add is my exposure for my Actuator endpoints. So I'm going to do expose for the web endpoints I'm just going to include everything. The way that we've offered probe support is to lean on the existing Actuator functionality, so I need to make sure that all of my web endpoints are exposed and available. Let's start this application again. And then I'm going to head over, and instead of looking at my hello endpoint, I'm going to look at the Actuator endpoint that is provided by Spring Boot. And specifically the one I want to look at is health. So here you can see we have an overall application health of up, but you can also see that we have two groups available. The health groups have been available in Spring Boot since version 2.2, and it's a way of collecting health indicators that make sense for a particular purpose, and grouping them together. What we've decided to do for probe support, is lean on this group functionality and provide a couple of auto-configured ones on your behalf if you're running in Kubernetes. So you can see we have one called liveness, and one called readiness. If I look at liveness, you can see we have a status of up. And if I look at readiness, you can see I also have a status of up. To show you how this stuff works I want to jump back into the code and dig into some in the Spring Boot code itself. What I want to do is, I'm going to add a couple of new methods here that are going to simulate this application going up and down. So I'm going to add a down method. And I'm going to add an up method. I'm going to map those as appropriate. And just return a string response. So the way that we've added probes is, we've added a new feature in Spring Boot called "application availability". So there is a new class called AvailabilityChangeEvent, and this is an event that Spring Boot itself will fire when it thinks an important part of the application lifecycle has happened. All this event does is it carries one piece of data, called the AvailabilityState, and that's just an interface. And we have two of these that are provided out of the box. We have a LivenessState, which is an availability state for the liveness probe, and we have a ReadinessState, which is an availability state for the readiness probe. So you can see here, these are just simple enums. We have a liveness state of 'correct' if the application is running, and 'broken' if it's not. And a readiness state for 'accepting traffic' or 'refusing traffic'. And what Spring Boot will do is as your application starts, it will fire these events at the appropriate time. So for instance once the web server is started and Spring MVC has come up, it will fire an event to say I'm ready to accept traffic. But if you want to you can also control these events yourself, and you can fire them from your own application. So what I want to do here is simulate a publish. And I'm going to publish this event to a publisher. I'm going to use 'this' as the source, and I'm going to change my readiness state. So let's pick ReadinessState.REFUSING_TRAFFIC for down, and ReadinessState.ACCEPTING_TRAFFIC for up. If you're not familiar with an application event, it's been in Spring Framework for a long time, and a publisher is just an interface, so I can inject it into my controller. So what's going to happen here is our controller is going to get created, Spring is going to give it this ApplicationEventPublisher, and then later on we're going to use that to publish an AvailabilityChangeEvent and we're going to publish either one with 'refusing traffic' or 'accepting traffic'. Let's restart our application. And then if we head over here, if I hit the readiness state now you can see it's up. If I go to a new tab, hit 'localhost:8080/down', we get our 'down' response and our application is now returning a response of OUT_OF_SERVICE. If I hit '/up' again, our application becomes healthy and Kubernetes will start routing traffic to it. And these response codes will work out of the box with Kubernetes, so once you wire everything up, then things will just work. And there's examples in the reference documentation that show you how to do that. Whilst we're in this Actuator endpoint, there's one more feature that I want to show you that I think is quite useful. If you look at the '/configprops' endpoint, we've added a new field to it, so I'm just going to a grab the raw data here, pretty-print it and copy it into a text editor. And I want to show you the 'graceful' property that we set. So right here, what this endpoint is showing you is that we have a class called ServerProperties, which is a @ConfigurationProperties class, so if I jump into the codebase, we can actually look at that class, this is part of Spring Boot itself. You can see it's a @ConfigurationProperties class that binds anything that starts in 'server' to these properties. And indeed, one of the properties is the 'shutdown' property which points to an enum. So the '/configprops' endpoint's been there for a while, but what we've added is an additional field. So if I jump over back into our properties again, what you can see here is the value that's set on the property is the enum. But just below it we have a list of inputs. And what this is saying is "where does this value actually come from". And if we scroll down we can find the 'shutdown' property. You can see here we have a value of 'graceful', but this case it's lowercase, and then it will tell us the 'origin' of that property. So right here you can see that we come from a class path resource named 'application.properties', right down to the line and column number that this is defined in. So if I jump back to the code, look at our 'application.properties', then line one, column 17 down here does indeed match where this value came from. But we think this kind of end-to-end traceability is really useful. It's really helpful to have rich, type-safe configuration properties objects, but still know which PropertySource contributed the value, right down to the line and column number. And that pretty much wraps up the demo. There are a couple of things that I wanted to show you that I couldn't get to. That includes the R2DBC support that we've added, so if you're a reactive programmer, you might want to check that out. And we've also added RSocket support with Spring Integration. Both of these are documented in the release notes, and you can check out the reference documentation. There are a couple of gotchas that you should also be aware of. One has to do with server threads, so if you manually configure your server threads, you need to migrate that property under a specific technology group. So for example it might live under Tomcat or Undertow, and that's because the server threads are subtly different, depending on which technology you're using, and we didn't feel comfortable having a uniform property that worked differently depending on which servlet container you where using. Finally, if you're using the 'whitelabel error page', you should be aware that we hide the details by default now. There's a single line property that you can change if you want to show those again, and all of this is documented in the Wiki on the release notes section. I'll leave you with one final interesting bit of information. If you've ever contributed to Spring Boot itself, you may be aware that we used to build the project with Maven. Starting with Spring Boot 2.3, we've actually migrated to Gradle. This means if you've got an existing checkout, you may need to reimport it into your IDE. The good news is that most IDEs support Gradle really well out of the box, and the new build is significantly faster than the old one. If you need extra help, jump over to the contributing guide on the GitHub page and you'll find detailed instructions on how to work with the code. We really appreciate all pull-requests and contributions that get made to the project. And that's pretty much it from me. I hope that next year we'll be able to go back to conferences and present this information face-to-face, but for now this will have to do, cheers.
Info
Channel: SpringDeveloper
Views: 55,752
Rating: undefined out of 5
Keywords: Web Development (Interest), spring, pivotal, Web Application (Industry) Web Application Framework (Software Genre), Java (Programming Language), Spring Framework, Software Developer (Project Role), Java (Software), Weblogic, IBM WebSphere Application Server (Software), IBM WebSphere (Software), WildFly (Software), JBoss (Venture Funded Company), cloud foundry, spring boot, spring cloud
Id: WL7U-yGfUXA
Channel Id: undefined
Length: 34min 50sec (2090 seconds)
Published: Tue Jun 16 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.