The book "Learning Java EE. Modern programming for large enterprises »

    imageHi Habr!

    This book describes the new generation of Java EE. You will go on a journey through Java EE in the context of the modern world of microservices and containers. Rather, it is not an API syntax guide — the concepts and methodologies presented here reflect the real experience of the person who himself recently walked this path, paying close attention to the obstacles encountered and is ready to share his knowledge. In various situations, starting with creating a package for testing and cloud use, this book will be an ideal companion for both beginners and experienced developers looking to understand more than just the API, and help them rebuild their thinking to create an architecture of modern applications in Java EE .

    Sequence of execution


    Business processes implemented in enterprise applications describe specific process flows. For the involved business scenarios, this is either a process of synchronous requests and responses, or asynchronous processing of the initiated process.

    Business scripts are invoked in separate threads, one thread per request or call. Flows are created by the container and placed in the drive for reuse after the call has been successfully processed. By default, business processes defined in application classes, as well as end-to-end tasks, such as transactions, are performed sequentially.

    Synchronous execution


    A typical scenario when an HTTP request for a response from a database is implemented as follows. One thread processes a request coming into the loop, for example, JAX-RS UsersResource, by inverting the control principle; the JAX-RS resource method is called by the container. A resource embeds and uses an UserManagement EJB object, which is also implicitly called by the container. All operations are performed by intermediaries synchronously. The EJB will use the entity manager to store the User entity, and as soon as the business method that initiated the current active transaction finishes, the container will attempt to commit the transaction to the database. Depending on the result of the transaction, the resource method of the circuit resumes operation and generates a response to the client. Everything happens synchronously, at this time the client is blocked and waiting for a response.

    Synchronous execution involves processing synchronous CDI events. They separate the triggering of domain events from their processing, however, events are processed synchronously. There are several transaction monitoring methods. If a transaction stage is specified, the event can be processed at this stage — while the transaction is committed, before it is completed, after completion, in the event of a unsuccessful or successful transaction. By default or if a transaction is inactive, CDI events are processed as soon as they occur. This allows engineers to implement complex solutions — for example, using events that occur only after successfully adding entities to the database. Be that as it may, in all cases the processing is performed synchronously.

    Asynchronous execution


    Synchronous execution of tasks meets the requirements of many business scenarios, but there are times when asynchronous behavior is needed. In the Java EE environment, there are a number of restrictions on the use of threads by an application. The container manages resources and streams and puts them into the drive. External concurrency control utilities are outside the container and they know nothing about these threads. Therefore, the application code should not run and manage its threads. To do this, it uses Java EE functions. There are several APIs with built-in asynchronous support.

    Asynchronous EJB Methods

    The easiest way to implement asynchronous behavior is to use the @Asynchronous annotation for an EJB business method or an EJB class. Calls to these methods are immediately returned, sometimes with a response like Future. They are executed in a separate thread, managed by the container. This works well for simple scripts, but is limited to EJB objects:

    @Asynchronous@StatelesspublicclassCalculator{
          publicvoidcalculatePi(long decimalPlaces){
                // этот метод может долго выполняться
          }
    }

    Execution Management Service

    For asynchronous execution of tasks in managed CDI objects or using Java SE concurrency control utilities, Java EE includes container-managed versions of ExecutorService and ScheduledExecutorService. They are used to implement asynchronous tasks in container-managed threads. Instances of the ManagedExecutorService and the ManagedScheduledExecutorService are embedded in the application code. They can be used to execute custom logic, but are most effective when combined with Java SE concurrency control utilities, such as complemented future values. The following example shows the creation of incremented future values ​​using container-managed streams:

    import javax.annotation.Resource;
    import javax.enterprise.concurrent.ManagedExecutorService;
    import java.util.Random;
    import java.util.concurrent.CompletableFuture;
    @StatelesspublicclassCalculator{
          @Resource
          ManagedExecutorService mes;
          public CompletableFuture<Double> calculateRandomPi(int
                maxDecimalPlaces){
                return CompletableFuture.supplyAsync(() -> new
                      Random().nextInt(maxDecimalPlaces) + 1, mes)
                             .thenApply(this::calculatePi);
          }
          privatedoublecalculatePi(long decimalPlaces){
               …
          }
    }

    The Calculator object returns a complemented future double value that can still be calculated when the calling context resumes. It can be queried when the calculations are completed, as well as combined with subsequent calculations. Regardless of where new threads are required in a corporate application, you should use Java EE functionality to manage them.

    Asynchronous CDI events

    CDI events can also be processed asynchronously. In this case, the container also provides a stream for handling events. To describe an asynchronous event handler, the method is annotated with @ObservesAsync, and the event is activated using the fireAsync () method. The following code snippets demonstrate asynchronous CDI events:

    @StatelesspublicclassCarManufacturer{
          @Inject
          CarFactory carFactory;
          @Inject
          Event<CarCreated> carCreated;
          public Car manufactureCar(Specification spec){
                Car car = carFactory.createCar(spec);
                carCreated.fireAsync(new CarCreated(spec));
                return car;
          }
    }

    The event handler is called in its own container-managed thread:

    import javax.enterprise.event.ObservesAsync;
    publicclassCreatedCarListener {
          publicvoidonCarCreated(@ObservesAsync CarCreated event) {
                // асинхронная обработка события
          }
    }

    For backward compatibility reasons, synchronous CDI events can also be handled in an asynchronous EJB method. Thus, events and handlers are defined as synchronous, and the handler method is an EJB business method with the @Asynchronous annotation. Before asynchronous events were added to the CDI standard for Java EE 8, this was the only way to implement this function. To avoid confusion in Java EE 8 and later versions of this implementation, it is better to avoid it.

    Visibility areas for asynchronous processing

    Since the container does not have information on how long asynchronous tasks can run, the use of scopes in this case is restricted. Objects with a scope within a request or session that were available at the start of an asynchronous task will not necessarily be active throughout its implementation - the request and session may end well before its completion. Thus, threads that perform asynchronous tasks, such as those provided by the service of scheduled executors or asynchronous events, may not have access to instances of managed objects with a scope within a request or session that were active during a call. The same applies to accessing links to embedded instances, for example in lambda methods that are part of synchronous execution.

    This must be considered when modeling asynchronous tasks. All information about a specific call must be provided at the time of launching the task. However, an asynchronous task can have its own instances of managed objects with limited scope.

    Execution at a specified time

    Business scripts can be invoked not only from the outside, for example, via an HTTP request, but also from within an application — a task that is started at a specific time.

    In the Unix world, the functionality for running periodic tasks is popular - these are the tasks of the scheduler. EJB objects provide similar capabilities using EJB timers. Timers invoke business methods at specified intervals or after a specified time. The following example describes the cyclic timer, which runs every ten minutes:

    import javax.ejb.Schedule;
    import javax.ejb.Startup;
    @Singleton@StartuppublicclassPeriodicJob{
          @Schedule(minute = "*/10", hour = "*", persistent = false)
          publicvoidexecuteJob(){
                // выполняется каждые 10 минут
          }
    }

    Any EJB objects — singletones, managed objects with or without state preservation — can create timers. However, in most scenarios it makes sense to create timers only for singletons. The delay is set for all active objects. Usually it is needed to launch scheduled tasks on time, which is why it is used in singleton. For the same reason, in this example, the EJB object must be active when the application starts. This ensures that the timer starts working immediately.

    If the timer is described as constant, then its lifetime extends over the entire life cycle of the JVM. The container is responsible for maintaining persistent timers, usually in a database. Permanent timers, which should work while the application is unavailable, are enabled at startup. It also allows you to use the same timers with multiple instances of an object. Constant timers with the appropriate server configuration is the right solution if you need to run a business process exactly once on multiple servers.

    Timers that are automatically created using Schedule annotationare described using Unix-like cron expressions. For greater flexibility, EJB timers are described programmatically using the container-provided timer service, which creates the callback methods Timers and Timeout .

    Periodic and deferred tasks can also be described outside the EJB components using the container-managed service of scheduled implementers. An instance of the ManagedScheduledExecutorService, which performs tasks after a specified delay or at a specified interval, is embedded in the managed components. These tasks will be implemented in streams managed by containers:

    @ApplicationScopedpublicclassPeriodic{
          @Resource
          ManagedScheduledExecutorService mses;
          publicvoidstartAsyncJobs(){
                mses.schedule(this::execute, 10, TimeUnit.SECONDS);
                mses.scheduleAtFixedRate(this::execute, 60, 10, TimeUnit.SECONDS);
          }
          privatevoidexecute(){
               …
          }
    }

    Calling the startAsyncJobs () method will launch the execute () function in a controlled stream ten seconds after the call and then every ten seconds after the first minute.

    Asynchrony and reactivity in JAX-RS

    JAX-RS supports asynchronous behavior in order not to unnecessarily block request flows on the server side. Even if the HTTP connection is waiting for a response, the request flow can continue to process other requests while the server is running a long process. Query streams are combined in a container, and this query store has a certain size. In order not to waste the request flow, JAX-RS asynchronous resource methods create tasks that run when the request flow returns and can be reused. The HTTP connection is resumed and responds after the asynchronous task is completed or after a timeout. The following example shows the asynchronous JAX-RS resource method:

    @Path("users")@Consumes(MediaType.APPLICATION_JSON)publicclassUsersResource{
          @Resource
          ManagedExecutorService mes;
          …
          @POSTpublic CompletionStage<Response> createUserAsync(User user) {
                return CompletableFuture.supplyAsync(() -> createUser(user), mes);
          }
          private Response createUser(User user) {
             userStore.create(user);
             return Response.accepted().build();
          }
    }

    In order for the request flow not to be busy for too long, the JAX-RS method must end quickly. This is due to the fact that the resource method is called from the container by means of the inversion control. The result obtained at the completion stage will be used to resume the client connection at the end of processing.

    Returning completion stages is a relatively new technology in the JAX-RS API. If you need to describe the delay and at the same time provide greater flexibility in the asynchronous response, then you can include the AsyncResponse type in the method. This approach is demonstrated in the following example:

    importjavax.ws.rs.container.AsyncResponse;
    importjavax.ws.rs.container.Suspended;
    @Path("users")
    @Consumes(MediaType.APPLICATION_JSON)
    public class UsersResource {
          @Resource
          ManagedExecutorService mes;
          …
          @POST
          public void createUserAsync(User user, @Suspended AsyncResponse
                response) {
                response.setTimeout(5, TimeUnit.SECONDS);
                response.setTimeoutHandler(r->
                   r.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).build()));
                mes.execute(() -> response.resume(createUser(user)));
           }
    }

    Thanks to the created timeouts, the client request will not wait indefinitely, but only until the result is received or the call has timed out. However, the calculations will continue as they are performed asynchronously. For JAX-RS resources that are implemented as EJB objects, you can use the @Asynchronous annotation in order not to invoke asynchronous business methods explicitly through the service provider.

    The JAX-RS client also supports asynchronous behavior. Depending on the requirements, it makes sense not to block it with HTTP calls. The previous example shows how to set delays for client requests. For long-running and especially parallel external system calls, it is better to use asynchronous and reactive behavior.

    Consider a few server applications that provide weather information. The client component refers to all of these applications and calculates the average weather forecast. Ideally, access to the systems could be parallel:

    import java.util.stream.Collectors;
    @ApplicationScopedpublicclassWeatherForecast{
          privateClient client;
          privateList<WebTarget> targets;
          @ResourceManagedExecutorService mes;
          @PostConstructprivate void initClient() {
                client = ClientBuilder.newClient();
                targets = …
           }
           publicForecast getAverageForecast() {
                 return invokeTargetsAsync()
                            .stream()
                            .map(CompletableFuture::join)
                            .reduce(this::calculateAverage)
                            .orElseThrow(() -> new IllegalStateException("Нет доступных
                                  прогнозов погоды"));
             }
             privateList<CompletableFuture<Forecast>> invokeTargetsAsync() {
                   return targets.stream()
                             .map(t -> CompletableFuture.supplyAsync(() -> t
                                         .request(MediaType.APPLICATION_JSON_TYPE)
                                         .get(Forecast.class), mes))
                             .collect(Collectors.toList());
              }
              privateForecast calculateAverage(Forecast first, Forecast second) {
                   …
              }
              @PreDestroypublic void closeClient() {
                    client.close();
              }
    }

    The invokeTargetsAsync () method invokes available objects asynchronously, using the service of scheduled artists. CompletableFuture descriptors are returned and used to calculate averaged results. The start of the join () method will be blocked until the call is completed and the results are received.

    Objects invoked asynchronously start and expect a response from several resources at once, possibly slower. In this case, waiting for responses from the weather service resources takes as much time as you have to expect the slowest response, rather than all responses together.

    The latest version of JAX-RS has built-in support for the completion steps, which reduces the stereotypical code in applications. As in the case of padding values, the call immediately returns the code of the completion stage for further use. The following example shows JAX-RS reactive client functions using the rx () call:

    publicForecast getAverageForecast() {
          return invokeTargetsAsync()
                     .stream()
                     .reduce((l, r) -> l.thenCombine(r, this::calculateAverage))
                     .map(s -> s.toCompletableFuture().join())
                     .orElseThrow(() -> new IllegalStateException("Нет доступных
                           прогнозов погоды"));
    }
    privateList<CompletionStage<Forecast>> invokeTargetsAsync() {
          return targets.stream()
                .map(t -> t
                       .request(MediaType.APPLICATION_JSON_TYPE)
                             .rx()
                             .get(Forecast.class))
                       .collect(Collectors.toList());
    }

    In the example above, it is not necessary to look for the service of scheduled artists — the JAX-RS client will manage this itself. Before the rx () method appeared, an explicit call to async () was used in clients. This method behaved similarly, but returned only Future objects. The use of a reactive approach in clients is optimal for most projects.
    As you can see, in the Java EE environment, a container-managed executor service is involved.

    Concepts and design principles in modern Java EE


    The Java EE API is based on conventions and design principles that are spelled out in the form of standards. Software engineers will find familiar API templates and approaches to application development. The goal of Java EE is to encourage consistent use of the API.

    The main principle of applications focused primarily on the implementation of business scenarios, sounds like this: technology should not interfere. As already mentioned, engineers should be able to focus on the implementation of business logic, without spending most of their time on technological and infrastructure issues. Ideally, the domain logic is implemented in simple Java and is supplemented with annotations and other properties supported by the corporate environment, without affecting the code of the domain and without complicating it. This means that the technology does not require much attention of engineers and does not impose too large restrictions. Previously, the J2EE environment required many very complex solutions. To implement interfaces and extend base classes, we had to use managed objects and persistent storage objects.

    In Java EE, the domain logic is implemented as simple Java classes with annotations, according to which the container solves certain corporate tasks during the execution of the application. The practice of creating clean code often involves writing code that is more beautiful than convenient for reuse. Java EE supports this approach. If for some reason you need to remove the technology and leave the pure domain logic, this is done by simply deleting the corresponding annotations.

    As we will see in Chapter 7, such an approach to programming implies the need for testing, since for programmers most of the Java EE specifications are nothing more than annotations.

    The entire API adopts the design principle, called inversion of control (IoC), in other words, “do not call us, we will call ourselves”. This is especially noticeable in application loops, such as JAX-RS resources. Resource methods are described using Java method annotations, which are later called by the container in the appropriate context. The same is true for dependency injection, for which you have to choose generators or take into account such end-to-end tasks as interceptors. Application developers can focus on bringing logic and relationship description to life, leaving the implementation of technical details in a container. Another example, not so obvious, is the description of converting Java objects to JSON and vice versa using JSON-B annotations. Objects are converted not only in an explicit, programmed form,

    Another principle that allows engineers to effectively use this technology is programming by convention. By default, Java EE has a specific behavior defined for most use cases. If it is insufficient or does not meet the requirements, the behavior can be redefined, often at several levels.
    There are many examples of programming by agreement. One of them is the use of JAX-RS resource methods that transform Java functionality into HTTP responses. If the standard JAX-RS behavior with respect to the responses does not meet the requirements, the response response type can be applied. Another example is the specification of managed objects, which is usually implemented using annotations. In order to change this behavior, you can use the XML descriptor beans.xml. For programmers, it’s very convenient that in the modern world of Java EE, corporate applications are developed in a pragmatic and high-performance way that usually does not require the intensive use of XML as before.

    As for the productivity of programmers, another important principle for developing in Java EE is that this platform requires integration into the container of various standards. Because containers support a specific set of APIs — and if they support all of the Java EE APIs — that’s the case, it also requires that the API implementations allow for easy integration of other APIs. The advantage of this approach is the ability to use JAX-RS resources to transform JSON-B and Bean Validation technology without additional explicit configuration, with the exception of annotations. In the previous examples, we saw how the functions defined in the individual standards can be used together without additional effort. This is one of the biggest advantages of the Java EE platform. The generic specification guarantees a combination of separate standards among themselves.

    Convenient, high-quality code


    Programmers generally agree that you should strive to write high-quality code. However, not all technologies are equally well suited for this.

    As mentioned at the beginning of the book, business logic should be in the spotlight when developing applications. In case of changes in business logic or the emergence of new knowledge, it is necessary to update the domain model, as well as the source code. In order to create and maintain a high-quality domain model and the source code as a whole, iterative refactoring is required. Efforts to deepen the understanding of the subject area are described in the concept of problem-oriented design.

    There is a lot of literature on refactoring at the code level. After the business logic is presented in the form of a code and verified by tests, programmers should spend some time and make efforts to rethink and improve the first option. This applies to identifiers of names, methods and classes. Especially important are the choice of names, levels of abstractions and single points of responsibility.

    According to the definition of a problem-oriented design, the subject area should correspond to its own representation in the form of code as much as possible. This includes, in particular, the language of the subject area — in other words, how programmers and business experts talk about specific functions. The goal of the whole team is to find a universal common language that will be effectively used not only in discussions and on presentation slides, but also in code. Refinement of knowledge in the field of business will occur cyclically. Like code-level refactoring, this approach implies that the original model will not fully meet all requirements.

    Thus, the applied technology must support changes to the model and code. If there are too many restrictions, then it will be difficult to make changes later.

    For application development in general, and especially for refactoring, it is imperative that the software is sufficiently covered by automated tests. Since the code is constantly changing, regression tests ensure that none of the business functions are accidentally damaged. Thus, a sufficient number of control tests support refactoring, allowing engineers to clearly understand that after making changes, all the functionality still works as expected. Ideally, the technology should support the ability to test without imposing restrictions on the code structure. We will discuss this in more detail in Chapter 7.

    To allow refactoring, weak binding is preferable to tight. Changing one component affects all the functions that explicitly call it, and all the components it needs. Java EE supports several weak bindings: dependency injection, events, and end-to-end tasks, such as interceptors. All this makes it easy to change the code.

    There are a number of tools and methods for measuring quality. In particular, static code analysis allows you to collect information about complexity, connectivity, dependencies between classes and packages, and implementation as a whole. These tools help engineers identify potential problems and form the overall picture of a software project. Chapter 6 will show you how to automatically check the quality of the code.

    In general, it is recommended to constantly reorganize the code and improve its quality. Software projects are often created to introduce new revenue-generating functions, and not to improve existing functionality. The problem is that refactoring and improving the quality of the code at first glance do not bring benefits to the business. This is certainly not the case. In order to achieve stable speeds and integrate new features with satisfactory quality, it is necessary to revise existing features. Ideally, the refactoring cycles should be embedded in the project outline. Experience shows that project managers are often unaware of this problem. However, a team of software engineers is responsible for maintaining quality.

    about the author


    Sebastian Daschner is a Java freelance consultant and teacher, programming enthusiast and Java (EE). He participates in JCP, helping to create new Java EE standards, serving expert groups 370 and 374 in the JSR and working in various open source projects. For his contributions to the Java community and ecosystem, he was awarded the titles of a Java champion and Oracle developer champion.

    Sebastian regularly speaks at international IT conferences such as JavaLand, JavaOne, and Jfokus. He received the JavaOne Rockstar award at the JavaOne 2016 conference. He and the Java community manager Steve Chin attended dozens of Java conferences and user groups while riding a motorcycle. Steve and Sebastian created JOnsen, a Java non-conference held at a thermal spring in a rural area in Japan.

    About Reviewer


    Melissa McKay (Melissa McKay) is a software developer with 15 years of experience creating applications of various types for both private clients and enterprises. Now she is mainly engaged in server-side Java-applications that are used in the field of communications and television. Her areas of interest include cluster systems, she has a particular passion for solving problems associated with parallel and multi-threaded applications.

    Melissa regularly attends non-conferences JCrete in Crete (Greece) and was pleased to attend the opening of the JOnsen non-conference in Japan. She loves to participate in volunteer IT conferences for children, such as JavaOne4Kids and JCrete4Kids. He was a member of the content committee for JavaOne 2017 and is an active member of the Denver Java User Group.

    »More information about the book is available on the publisher's website
    » Table of contents
    » Excerpt

    For Habrozhiteley 20% discount on the coupon - Java EE

    Also popular now: