2

I'm using Payara Microservice for a Java web application and want to inject a bean into my deserialised JSON object so that I can add perform some additional logic.

So far I have the resource class, annotated with @Path and @POST, which I call via Postman with some JSON. The method invokes fine, however no matter what I try, I am unable to get the bean to inject.

The JSON object's class that looks like this:

public class IncomingJsonRequest {

    @NotNull
    private String value;

    private AdditionalLogicClass additionalLogicBean;

    public String setValue(String value) {
        this.value = value;
    }

    public void performAdditionalLogic() {
        additionalLogicBean.performLogic(value);
    }
}

What I would like to do is inject the AdditionalLogicClass bean so that when I call the performAdditionalLogic() method from the resource method it doesn't throw a null pointer exception.

I've tried all sorts of annotations and so far the only way I can seem to do this is for the resource class to pass the bean in, but that's not good encapsulation. I don't want the resource to know about how this additional logic is done.

The other way was programatically loading the bean but I've read that it's not good practice.

What is the best way to achieve this?

Thanks

AlBlue
  • 23,254
  • 14
  • 71
  • 91
Alan
  • 23
  • 3
  • 1
    Injecting a service into a model is not good practice either. Why do yo uwant that? – Kukeltje Jun 24 '20 at 08:43
  • To increase encapsulation and not have an anemic object. The object's class has the information required to invoke the additional logic. If I want to push it to a service layer I then have to make getters and the service layer has to orchestrate it all. Isn't this an anti-pattern that Martin Fowler wrote about, or have I misunderstood the point. – Alan Jun 24 '20 at 08:56
  • 1
    I'm not really a pattern/antipattern guy, more of the practical kind ;-) for injection to work, you need the class to be explicitly 'mananged' or via the configuration implicitly be eligeable for injection (empty constructor requirement) and the instance must be created by the container that manages them. Then it might work. – Kukeltje Jun 24 '20 at 09:13
  • 2
    From https://www.martinfowler.com/bliki/AnemicDomainModel.html: _"It's also worth emphasizing that putting behavior into the domain objects should not contradict the solid approach of using layering to separate domain logic from such things as persistence and presentation responsibilities. The logic that should be in a domain object is domain logic - validations, calculations, business rules - whatever you like to call it. (There are cases when you make an argument for putting data source or presentation logic in a domain object, but that's orthogonal to my view of anemia.)_" – Kukeltje Jun 24 '20 at 10:27
  • 2
    _"One source of confusion in all this is that many OO experts do recommend putting a layer of procedural services on top of a domain model, to form a Service Layer. But this isn't an argument to make the domain model void of behavior, indeed service layer advocates use a service layer in conjunction with a behaviorally rich domain model."_ So I'm luckily doing it right ;-) Just never had the need to use complex (business) logic in a domain model that was techically provided by a 'service'. But https://en.wikipedia.org/wiki/Anemic_domain_model contains interesting info too ('Crtitisism' ;-)) – Kukeltje Jun 24 '20 at 10:28

1 Answers1

1

Using a programmatical CDI approach you can achieve it with the following:

@Path("sample")
public class SampleResource {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response message(MyPayload myPayload) {
        return Response.ok(myPayload.getName()).build();
    }

}

A sample business logic CDI bean

public class BusinessLogic {
    public String doFoo(String name) {
        return name.toUpperCase();
    }
}

The payload:

public class MyPayload {

    private String name;
    private BusinessLogic businessLogic;

    public MyPayload() {
        this.businessLogic = CDI.current().select(BusinessLogic.class).get();
    }

    public String getName() {
        return businessLogic.doFoo(this.name);
    }

    public void setName(String name) {
        this.name = name;
    }
}

and finally the beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
       bean-discovery-mode="all">
</beans>

Hitting the endpoint returns the expected input as uppercase:

curl -v -d '{"name": "duke"}' -H 'Content-Type: application/json' http://localhost:9080/resources/sample
< HTTP/1.1 200 OK
< X-Powered-By: Servlet/4.0
< Content-Type: application/octet-stream
< Date: Thu, 02 Jul 2020 06:16:34 GMT
< Content-Language: en-US
< Content-Length: 4
< 
* Connection #0 to host localhost left intact
DUKE

This way you inject the CDI bean in the default constructor which is called by JSON-B when it tries to serialize the incoming payload.

The other way was programatically loading the bean but I've read that it's not good practice.

Not sure if there is any other solution to make this happen and let the CDI container take care of the injection.

rieckpil
  • 10,470
  • 3
  • 32
  • 56
  • Like so: https://stackoverflow.com/questions/24798529/how-to-programmatically-inject-a-java-cdi-managed-bean-into-a-local-variable-in – Kukeltje Jul 02 '20 at 07:48