0

I have the following Spring Bean structure:

public abstract class XmlBaseChild {
    protected Integer value;
    protected String text;

@Autowired
transient protected MasterCodeService masterCodeService;



    public XmlBaseChild(Integer value) {
        setValue(value);
    }

    /**
     * Set the Numeric value of the ChildView.
     * This code is common for all childViews and handles a null value.
     * @param value Numeric value of the ChildView
     */
    @JsonProperty(value="id")
    public void setValue(Integer value) {
        if (value == null) {
                this.value = null;
                this.text = null;
                return;
        }
        setConcreteValue(value);
    }

    /**
     * Set the Numeric value of the ChildView.
     * This code must be overridden by the concrete childViews.
     * @param value Numeric value of the ChildView
     */
    protected void setConcreteValue(Integer value){
        boolean keyNotFound = true;
        if (value != null && value > -1) {
            this.value = value;
            String messageKey = getValueFromMap(value, GetMasterCodeMapForChildView());
            if (messageKey != null) {
                this.text = LocalizeString(messageKey, null, getLocale);
                keyNotFound = false;
            }
        }
        if (keyNotFound){
            throw new NotFoundException();
        }
    }

    protected abstract Map<String, MasterCodeView> GetMasterCodeMapForChildView();
}

And the subclass:

@Component
@XmlRootElement(name=XmlDeployTool.VIEW_NAME)
public class XmlDeployTool extends XmlBaseChild {

    public static Map<String, MasterCodeView> toolTypeCodes = new HashMap<String, MasterCodeView>();


    /**
     * Constructor for creating this object and preparing for marchalling (from java objects to xml/json).
     * @param value         Numeric value of the ChildView
     * @param request       HttpServletRequest
     * @param includeSelf   Include SELF link
     * @param includeUP     Include UP link
     */
    public XmlDeployTool(Integer value) {
        super(value);
    }

    /**
     * Initialize the Tool Type codes after the component is wired (postconstruct),
     * so that they are available in the constructor when an XmlDeploy object is created.
    */
    @PostConstruct
    protected void initializeDeployToolTypeCodes() {
    toolTypeCodes = convertListToMap(masterCodeService.getToolTypeCodes());
    }
    @Override
    protected Map<String, MasterCodeView> GetMasterCodeMapForChildView() {
        return toolTypeCodes;
    }
}

However, from what I understand from other posts like Order of @PostConstruct and inheritance, the @PostConstruct here normally executes AFTER the constructor is called. Then why is the toolTypeCodes map populated during the constructor? Is this part of the @Component annotation of Spring?

I also tried doing this with the masterCodeView map defined in the XmlBaseChild and only the PostConstruct method defined in the XmlDeployTool class, but that didn't work, the list didn't get initialized in that case. Why is this?

Nzall
  • 3,439
  • 5
  • 29
  • 59
  • can you create XmlDeployTool successfully?the constructor required a bean of type 'java.lang.Integer' that could not be found. – jin May 22 '19 at 01:51
  • @jin I didn't include any of the imports, and I simplified my constructors since they had a bunch of extra code that wasn't relevant AFAIK. – Nzall May 22 '19 at 05:36
  • There is a whole lot going on here, apparently JAXB needs this, but it is also a Spring object. What is this contraption, looks like you are doing things you shouldn't be doing. – M. Deinum May 22 '19 at 18:14
  • @M.Deinum Example: a Project has a DeployTool defined that can be one of 4 Tool Types, defined by the toolTypeCodes MasterCodeView Map, with the tool type in the database being the String. When a Project REST call is made, an XmlDeployTool object is created through reflection, and as part of the Constructor, the localized name for the Tool Type is generated. This way, a lot of the repeated code for retrieving and localizing a referenced resource is deduplicated. – Nzall May 22 '19 at 19:00

1 Answers1

0

After checking the documentation and reading up some more, I figured out what's going on here:

  1. Because my subclass is annotated with @Component, the PostConstruct triggers as part of the Spring startup process, even before any invocations of the normal constructor. Because of this, the static Map with MasterCodeViews gets populated, and since this is static, it stays populated as part of the subclass static properties. Because of this, this map has the proper usable data during construction.

  2. When I tried to move the Map to the base class, In effect I turned this from a static property of the subclass to a static property of the subclass, which meant each constructor in turn populated it with the separate properties, leading to the map having the wrong data most of the time. When after that I tried to do this with a non-static map, the data wasn't retained when I invoked the constructor from code because this was effectively a new object with no initialized components.

Nzall
  • 3,439
  • 5
  • 29
  • 59
  • `@PostConstruct` simply cannot be called on a non-constructed object, so point 1 totally doesn't make sense. Java simply forbids this... – M. Deinum May 22 '19 at 18:13
  • @M.Deinum Spring apparently constructs an object as part of the Component annotation. Though it does seem like this particular way of doing this is not quite intended. – Nzall May 22 '19 at 19:03
  • It is simply not possible what you state, there is No way you can call a method on a non constructed object. That is simply impossible in Java and not even spring can work around that. – M. Deinum May 22 '19 at 19:04
  • @M.Deinum Yeah, but Spring does actually construct an instance of any object marked with the Component annotation. These are meant to be injected into Controllers, either through the Autowired annotation (You can see one in the XmlBaseChild) or through WebApplicationContext.getBean(). This happens as part of the Spring startup process. What I meant by "before any invocations of the normal constructor" is that this happens before any REST calls themselves happen and can invoke the constructor themselves. – Nzall May 22 '19 at 19:21
  • Spring calls after it constructed an instance. You populate Static map so that is shares beteren all instances. What you constructed is still an ugly contraption. There is too much magic making this work. – M. Deinum May 22 '19 at 19:23
  • @M.Deinum Yeah, it's not really that pretty. I'm probably going to ask a question about it on Code Review tomorrow when I get back to work. – Nzall May 22 '19 at 19:27
  • Also if you inject them and construct them yourself? Then why on earth use thiss contraption? If you construct it why all the magic? – M. Deinum May 23 '19 at 05:24
  • @M.Deinum I have created a question on Code Review about this code. It can be found at https://codereview.stackexchange.com/questions/220810/given-a-resource-id-instantiate-retrieve-and-localize-a-linked-resource-for-a and you'll probably be better able to give in-depth feedback and comments on the structure and working there. – Nzall May 23 '19 at 09:44