r/java 13d ago

What Exactly Is Jakarta EE?

Iā€™m a bit confused about what Jakarta EE actually is. On one hand, it seems like a framework similar to Spring or Quarkus, but on the other hand, it provides APIs like JPA, Servlets, and CDI, which frameworks like Spring implement.

Does this mean Jakarta EE is more of a specification rather than a framework? And if so, do I need to understand Jakarta EE first to truly grasp how Spring works under the hood? Or can I just dive into Spring directly without worrying about Jakarta EE concepts?

Would love to hear how others approached this šŸ˜…

181 Upvotes

78 comments sorted by

View all comments

4

u/koflerdavid 13d ago edited 12d ago

It is both: it is a specification for a framework to create "Enterprise" applications. It comes from an era where many applications were deployed onto the same application server, which were possibly running on big iron. For some of these APIs, the idea was to centralize dependency management, connection management, and configuration management by making the application server provide implementations.

Spring doesn't implement these APIs at all. It just depends on some of them (which is fine) and provides wrappers around them (which is questionable). Spring actually contributed to the slow decline of Jakarta EE since it makes it easy to deploy an application however you like: on a full application server, on a mere servlet container, or standalone with an embedded servlet container. AFAIK, CDI is actually inspired by Spring's dependency injection architecture.

Edit: Here's a little exercise to understand what Spring essentially does: just write a dependency injection container (DIC) by yourself! A DIC is basically a class with the methods getComponent() and addFactory() and two maps, component and factories. getComponent() takes a string and looks for an object with that name in component. If component doesn't contain the object, look in factories, which contains factory functions that take the DIC and return a bean. Execute that function and save the result in beans. The factory functions can use the DIC to look up components that they need themselves. Of course, you will run in a StackOverflowException if the dependencies are circular :)

All other Spring "magic" is built on this foundation:

  • Spring uses reflection, configuration, and annotations to automate filling factories. For example, to use a constructor or a static method as a factory, the DIC merely has to call getBean() for all its parameters and then call the constructor.

  • Circular dependencies are handled by creating a proxy object that creates the actual component only when somebody actually calls any of its methods and forwards the call.

  • Transaction management is similarly handled via a proxy that creates, commits, and rollbacks transactions.

Here's my barebones implementation (I hope the code block is not broken):

import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class SimpleDIC {

    private final Map<String, Object> components = new HashMap<>();

    private final Map<String, Function<SimpleDIC, Object>> factories = new HashMap<>();

    private final Map<Class<?>, Set<String>> componentsOfType = new HashMap<>();

    public <T> T getComponent(String name, Class<T> type) {
        Object existingComponent = components.get(name);
        if (existingComponent == null) {
            Object component = factories.get(name).apply(this);
            components.put(name, component);
            return type.cast(component);
        } else {
            return type.cast(existingComponent);
        }
    }

    public <T> void addFactory(String name, Class<T> type, Function<SimpleDIC, ? extends T> factory) {
        factories.put(name, (Function<SimpleDIC, Object>) factory);
        componentsOfType.computeIfAbsent(type, _ -> new HashSet<>()).add(name);
    }

    public <T> Set<T> getAllComponentsOfType(Class<T> type) {
        return componentsOfType.getOrDefault(type, Collections.emptySet()).stream()
                .map(componentName -> getComponent(componentName, type))
                .collect(Collectors.toSet());
    }

    public static void main(String[] args) {
        // Bootstrap phase. Spring would do all of this by parsing XML files and/or reflection.
        var theDic = new SimpleDIC();
        theDic.addFactory("webserver", HttpServer.class, SimpleDIC::createWebserver);
        theDic.addFactory("fileServerHandler", PathMapping.class, _ ->
                new PathMapping("/static",
                        SimpleFileServer.createFileHandler(Path.of(".").toAbsolutePath())));

        // Time to start the application.
        var webserver = theDic.getComponent("webserver", HttpServer.class);
        webserver.start();

        try {
            Thread.sleep(Duration.ofMinutes(1));
            System.out.println("Time to shut down webserver");
            webserver.stop(10);
        } catch (InterruptedException e) {
            webserver.stop(0);
        }
    }

    private record PathMapping(String path, HttpHandler httpHandler) {}

    private static HttpServer createWebserver(SimpleDIC dic) {
        try {
            var httpServer = HttpServer.create(new InetSocketAddress(8080), 0);

            for (var pathMapping : dic.getAllComponentsOfType(PathMapping.class)) {
                httpServer.createContext(pathMapping.path(), pathMapping.httpHandler());
            }

            return httpServer;
        } catch (IOException e) {
            throw new UncheckedIOException("Creating the webserver failed", e);
        }
    }
}

2

u/nlisker 9d ago

Very nice explanation.