5

I am using an inner class in a Spring Controller. It is having problems accessing protected fields/methods from it's parent classes super class.

Research suggests that this is caused by differing class-loaders in some way but I don't know enough about Spring to be certain.

class SuperBaseController {
    protected String aField;

    protected void aMethod() {

    }
}

@Controller
class OuterMyController extends SuperBaseController {

    class Inner {

        public void itsMethod() {
            // java.lang.IllegalAccessError: tried to access method
            aMethod();
        }

        public void getField() {
            // java.lang.IllegalAccessError: tried to access field
            String s = aField;
        }
    }

    void doSomething () {
        // Obviously fine.
        aMethod();
        // Fails in the Inner method call.
        new Inner().itsMethod();

        // Obviously fine.
        String s = aField;
        // Fails in the Inner method call.
        new Inner().getField();
    }
}

Are there any simple techniques to avoid/fix this issue? Preferably ones that do not involve making the fields/methods public.

I have confirmed that the ClassLoader attributes of the outer class is not the same as that of the super class.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Does this also happen with `MyController.this.aMethod()`? – daniu Dec 08 '17 at 11:27
  • @daniu - Yes it does. – OldCurmudgeon Dec 08 '17 at 11:28
  • 1
    Which compiler (and version) are you using? – Erwin Bolwidt Dec 08 '17 at 11:38
  • 1
    @ErwinBolwidt - `Java` 1.8.0_151 (target 1.7) - `Spring` 3.0.7 – OldCurmudgeon Dec 08 '17 at 11:50
  • 1
    You're talking about classloaders, so would this result from Spring loading `MyController` with one classloader (obviously Spring will load that controller class), and since `Inner` won't be loaded until needed, it would be loaded with a different classloader? If you fiddle with `Inner` inside a `@PostConstruct` method (which should trigger the classloading), does it work then? – Kayaman Dec 08 '17 at 11:53
  • @Kayaman - Haven't tried. On first glance `@PostConstruct` seems to be dealing with non-availability of injected fields during construction rather than access. How might I use `@PostConstruct` to help here? – OldCurmudgeon Dec 08 '17 at 11:58
  • I've been having this exact issue recently with the super class being a Spring CGLib enhancement of the class which is out of my hands somewhat. – Plog Dec 08 '17 at 12:10
  • Well, considering that `@PostConstruct` is run after the bean is initialized, this could mean that the same classloader would be used to load `Inner` if it's accessed from `@PostConstruct`. This is just a guess I'm throwing out there, might not make a difference. – Kayaman Dec 08 '17 at 12:21
  • 3
    Can you post the exact stacktraces (at least the first few lines of it)? The error should originate from a synthetic accessor method created by the compiler inside the `MyController` class. Exact details will give a hint to the cause. – Erwin Bolwidt Dec 09 '17 at 00:26

3 Answers3

0

I created the following classes:

public class BaseController
{
    protected String field;
    protected void method()
    {
        System.out.println("I'm protected method");
    }
}

@RestController
@RequestMapping("/stack")
public class StackController extends BaseController
{
    class Inner
    {
        public void methodInvocation()
        {
            method();
        }
        public void fieldInvocation()
        {
            field = "Test";
        }
    }
    @RequestMapping(value= {"/invoca"}, method= {RequestMethod.GET})
    public ResponseEntity<String> invocation()
    {
        Inner in = new Inner();
        in.fieldInvocation();
        in.methodInvocation();
        return new ResponseEntity<String>("OK", HttpStatus.OK);
    }
}

I tried to invoke the rest service and I had no issue. Now I'm using this configuration for Spring (annotation based annotation):

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = { "it.spring.controller" })
@PropertySource( value={"classpath:config.properties"}, encoding="UTF-8", ignoreResourceNotFound=false)
public class DbConfig
{
}


@Configuration
@EnableWebMvc
@Import(DbConfig.class)
@PropertySource(value = { "classpath:config.properties" }, encoding = "UTF-8", ignoreResourceNotFound = false)
public class WebMvcConfig extends WebMvcConfigurerAdapter
{
}

As you can see I used the @Import annotation and in my web.xml I use this configuration:

<servlet>
    <servlet-name>SpringDispSvlt</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>it.WebMvcConfig</param-value>
    </init-param>
    <init-param>
        <param-name>dispatchOptionsRequest</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

By using this configuration I had no issue in invoking protected fields and/or method inside an inner class

If you can't adapt your configuration to this one.. may you post the configuration you used?

I hope it can be useful

Angelo

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
0

How about:

class OuterMyController extends SuperBaseController {
    private String getAField() { return super.aField; }
    protected void aMethod() { super.aMethod(); }
    ...
}

Now the inner class uses its outer class's methods so there's no illegal access, and the outer class's methods have access to the protected fields of the base class.

Klitos Kyriacou
  • 10,634
  • 2
  • 38
  • 70
0
class SuperBaseController {
protected String aField;

protected void aMethod() {

    }
}

@Controller
class OuterMyController extends SuperBaseController {


    public void itsMethod() {
        aMethod();
    }

    public void getField() {
        String s = aField;
    }

class Inner {
    public void innerItsMethod() {
        itsMethod();
    }
    public void innerGetField() {
    String s = getField();
    }
}

void doSomething () {
    aMethod();
    new Inner().itsMethod();
    String s = aField;
    new Inner().getField();
    }
}

Does this work?

Also, you may have to define which object is running the method. (e.g. Inner.getField(), or SuperBaseController.aMethod())

GreenHawk1220
  • 133
  • 1
  • 10