17

Lombok Saved My Ass

 4 years ago
source link: https://devolution.tech/how-lombok-saved-my-ass/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Lombok is a Java library that generates boilerplate code for you during the compilation. You probably use it or at least heard how it can clean the code with annotations like @Data or @Value . So I am not going to write yet another article on how to use the most popular annotations. I am going to show you one of the two most underrated features in Lombok – @Cleanup .

@Cleanup

It is just (or maybe even) an alternative to try-with-resource introduced in Java 7. Wait. What? In Java 7? When was that? On July 28, 2011. Why am I writing about alternative to feature introduced natively in Java almost eight years ago? We will get back to it soon. Let’s see a simple example first. Copy input stream into output stream with Lombok:

@Cleanup InputStream in = new FooInputStream();
@Cleanup OutputStream out = new BarOutputStream();
in.transferTo(out); // since Java 9

With plain Java:

InputStream in = new FooInputStream();      
try {
    OutputStream out = new BarOutputStream();
    try {
        in.transferTo(out);
    } finally {
        if (out != null) {
            out.close();
        }
    }
} finally {
    if (in != null) {
        in.close();
    }
}

You can find low-level file copy example in Lombok’s documentation

As you can see Lombok handles nested resources closing properly. The same as try-with-resource syntax:

try(InputStream in = new FooInputStream(); 
    OutputStream out = new BarOutputStream()) {
    in.transferTo(out);
}

Why Lombok is better

There is little gotcha when using Java’s try-with-resource syntax. The resource has to implement AutoCloseable interface, so the compiler can use void close() method for closing a resource. Lombok’s @Cleanup , it doesn’t have to implement this interface. The resource just needs to have some closing method which by default is void close() (but you can specify your own method name responsible for closing resource).

It may be irrelevant as all standard libraries implement AutoCloseable for all resources. However in the past, I figured out that the AutoCloseable interface is not implemented in some older libraries like Jersey Client 1 (it does in version 2).

Due to that, a production system crashed (of course after office hours) and engineers were not able to find anything suspicious quickly using standard debugging tools. Whole Tomcat based application slowed down and started returning HTTP 500 status code as Jersey client was not closing resources (HTTP connection in this case) properly.

Let see how Lombok can save use in simple example:

FooDto getFooDtoFromRestService() {
    @Cleanup
    ClientResponse response = client
        .resource("https://example.com")
        .get(ClientResponse.class);

    if(response.getStatus() == HttpStatus.SC_OK) {
        return response.getEntity(FooDto.class);
    }

    throw new UexpectedStatusCodeException();
}

FooDto getFooDtoFromRestService() {
    ClientResponse response = client
        .resource("https://example.com")
        .get(ClientResponse.class);
    try { 
        if(response.getStatus() == HttpStatus.SC_OK) {
            return response.getEntity(FooDto.class);
        }

        throw new UexpectedStatusCodeException();
    } finally {
        if (response != null) {
            response .close();
        }
    }
}

Let’s analyze the above code a little bit.

ClientResponse closes resources internally (input stream representing HTTP body) when method getEntity is called. But what if it is not called. For example, we just check the status code like in the example. Then, unfortunately, connection/thread/memory is not released and we have a resource leak.

In that case, @Cleanup saves us – it makes the program work properly (no resource leak) and hide boilerplate code. Let me note that ClientResponse::close() method is idempotent so it is safe to call it after reading the response body (which is obviously a great design decision).

The most important fact is that no IDE, compiler nor Sonar would catch such bug like this one due to the fact that Jersey 1 does not implement AutoCloseable interface.

Lombok also gives an example for SWT in documentation:

@Cleanup("dispose")
org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);

The proper resource clean up will be done using dispose (see SWT: Managing Operating System Resources Rule 1: If you created it, you dispose it .)

Isn’t @Cleanup a RAII pattern?

RAII (Resource acquisition is initialization) pattern says that holding resource is related to object lifetime (so object’s resources should be initialized in its constructor and released in its destructor). This pattern saves us from resource leak if there is no object leak. It is highly used in C++ to implement abstractions like “guards” e.g. lock guard . Let’s see simplified implementation of generic guard and example usage:

template <typename T> class Guard {
    T& res;
public:
    Guard(T& resource) : res(resource) {
    }

    ~Guard() {
        res.close();
    }
};

void exampleFunction() {
    FooResource fooResource;
    Guard g(fooResource);
    fooResource.doSth();
}

Guard class just holds resource and calls close() method in the destructor. In exampleFunction() , you can see that we wrote one additional line to create a guard object. C++ compiler does the rest of the job. When exampleFunction() execution is completed (even if an exception has been thrown) guard’s destructor will be called and resource release.

The same pattern is used by @Cleanup and try-with-resource syntax in Java – resources are initialized and released in the same method. It is just handled a little bit different because Java uses automatic garbage collection where C++ leaves memory management to programmers (fortunately with RAII support).

Conclusion

@Cleanup is a good alternative for try-with-resource syntax especially if you have to deal with older libraries. It can prevent serious resource leaks in production systems and does it really smoothly. It can really save your ass and make you and your customers sleep well. In newer systems, where classes implement AutoCloseable interface, it is up to you if you want to use @Cleanup or try-with-resource.

Oh and yes, @Cleanup uses 1 line and 1 indent less :wink:

What is the second most underrated feature in Lombok? Stay tuned for my next post!

About Post Author

Tomasz Kuczma

https://devolution.tech

Software engineer with passion. Interested in computer networks and large-scale distributed computing.

The views I express are my alone and they do not necessarily express the views of my employer or ex-employers.

See author's posts


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK