Missing a wildcard open statement

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Missing a wildcard open statement

Kasper Nielsen
Hi,

Having worked with the module system for some time now. There is one
situation I've started coming across a number of times now, that requires a
lot of boiler plate.
Opening every package of a module to another module. For example, for
dependency injection, code analysis, ect.
Basically you have 2 choices now.
* Opening it to every possible module, via open <Module> which you rarely
want
* For each package in your module add an opens <Package> to <OtherModule>

The last option is fine if you have a couple of packages, but when you have
many packages it starts to be a bit of a work.
And since I (and most people) are lazy, they are just going to go with the
first option, opening the whole module unqualified.

What I am missing is a "open all packages to this module" option ("opens *
to mod1, mod2")
This could be implemented, for example, by adding simply by making the
package statement in opens <Package> to <OtherModule> optionally (maybe
there are some corner cases so it would'nt work syntactically).
Or maybe by adding an openallto <OtherModules> statement.
Implementation wise, it would just enumerate all packages and add a
qualified open statement for each of them. So no changes would be required
at runtime.

Anyone else had the same experience?

/Kasper
Reply | Threaded
Open this post in threaded view
|

Re: Missing a wildcard open statement

Alan Bateman
On 14/01/2019 12:26, Kasper Nielsen wrote:
> Hi,
>
> Having worked with the module system for some time now. There is one
> situation I've started coming across a number of times now, that requires a
> lot of boiler plate.
> Opening every package of a module to another module. For example, for
> dependency injection,
This is the type of use-case where using Lookup objects might be a
better choice. If you can coerce the user module to pass a suitably
privileged Lookup to the framework then the framework will be able to
inject classes without needing to open packages to the framework. I
think this is an area where we need framework maintainers to do more
exploration.

-Alan
Reply | Threaded
Open this post in threaded view
|

Re: Missing a wildcard open statement

Kasper Nielsen
>
> This is the type of use-case where using Lookup objects might be a
> better choice. If you can coerce the user module to pass a suitably
> privileged Lookup to the framework then the framework will be able to
> inject classes without needing to open packages to the framework. I
> think this is an area where we need framework maintainers to do more
> exploration.
>

Yes, but we have found this to be equally tedious when you are trying to
aggregate multiple modules.
Because every module has to supply their own Lookup object.

So you need some kind of bootstrap class in every module that registers
their Lookup object somehow. And, you also have to make sure that the
bootstrap class does not make the Lookup object available to someone who
should not have access to it. This is solvable, for example, by using a
ServiceLoader.

But now, you also have X number of Lookup objects you need to keep track
of. And then someone gets the idea that you need an extension
hierarchy that crosses module boundaries. So you end up with needing one
Lookup object for one method on an object and another Lookup object on
another method on the object. Because the first method is on an abstract
class located in another modul.

In the end it might just be simpler to add an open statement for every
package in your module-info. At least for some use cases.

/Kasper
Reply | Threaded
Open this post in threaded view
|

Re: Missing a wildcard open statement

Jochen Theodorou
On 14.01.19 17:18, Kasper Nielsen wrote:
[...]

> So you need some kind of bootstrap class in every module that registers
> their Lookup object somehow. And, you also have to make sure that the
> bootstrap class does not make the Lookup object available to someone who
> should not have access to it. This is solvable, for example, by using a
> ServiceLoader.
>
> But now, you also have X number of Lookup objects you need to keep track
> of. And then someone gets the idea that you need an extension
> hierarchy that crosses module boundaries. So you end up with needing one
> Lookup object for one method on an object and another Lookup object on
> another method on the object. Because the first method is on an abstract
> class located in another modul.

Maybe I understood something wrong, but assuming

Module A
public abstract class A {
   public abstract void foo();
}

Module B (reads A)
public class B extends A {
   public void foo(){}
   public void bar(){}
}

then in Module C to call bar on an instance of B I need a Lookup object
with the correct rights for B. But I can use the same Lookup object to
call foo on an instance of B.

If B does not read A when loading the class B I would actually expect
the module system to deny loading the class... never tested that though.
Is that not the case?

> In the end it might just be simpler to add an open statement for every
> package in your module-info. At least for some use cases.

For many cases in which you have to load things dynamically. If not, you
can figure out these things statically and patch the modules to have the
right settings, which means to make a tailored module loading in the end.

bye Jochen
Reply | Threaded
Open this post in threaded view
|

Re: Missing a wildcard open statement

Alan Bateman
In reply to this post by Kasper Nielsen
On 14/01/2019 16:18, Kasper Nielsen wrote:

> Yes, but we have found this to be equally tedious when you are trying
> to aggregate multiple modules.
> Because every module has to supply their own Lookup object.
>
> So you need some kind of bootstrap class in every module that
> registers their Lookup object somehow. And, you also have to make sure
> that the bootstrap class does not make the Lookup object available to
> someone who should not have access to it. This is solvable, for
> example, by using a ServiceLoader.
>
> But now, you also have X number of Lookup objects you need to keep
> track of. And then someone gets the idea that you need an extension
> hierarchy that crosses module boundaries. So you end up with needing
> one Lookup object for one method on an object and another Lookup
> object on another method on the object. Because the first method is on
> an abstract class located in another modul.
>
> In the end it might just be simpler to add an open statement for every
> package in your module-info. At least for some use cases.
>
Which library or framework is this and is there a write-up of the issues
encountered when migrating it to use Lookup objects?

-Alan
Reply | Threaded
Open this post in threaded view
|

Re: Missing a wildcard open statement

Kasper Nielsen
On Tue, 15 Jan 2019 at 07:48, Alan Bateman <[hidden email]> wrote:

> Which library or framework is this and is there a write-up of the issues
> encountered when migrating it to use Lookup objects?
>
> -Alan
>

It is a small dependency injection/application I am trying to built on top
of the module system.
It is not in finished form yet, however, I have made some notes about some
observations I've done.

# Naming / User Friendliness
I know this is to late to change, but I think the Lookup mechanism could
have been designed a bit more user friendly.

Here is a simple method that lets a user construct a new object using
dependency injection. The two parameters are: The type of object that
should be constructed, and a Lookup object that can be used by the
underlying framework for invoking a constructor on the specified type.

import java.lang.invoke.MethodHandles;
public <T> inject(Class<T> type, MethodHandles.Lookup lookup)

Having Lookup defined as a static nested class of MethodHandles kind of
requires you to introduce two topics at the same time. It is quite a bit to
explain if you only have 10 lines for a getting started guide. Especially
since the usage of both of them are more or less unknown to 99% of Java
developers.

I could use this signature
import java.lang.invoke.MethodHandles.Lookup;
public <T> inject(Class<T> type, Lookup lookup)

But unless you know how to acquire a lookup object, it is a bit hard to
figure out you need to look in its parent object to find the method.
Buried, among tons of other methods. I think adding a static method such as
Lookup.of() could make it a bit less painful. Its still a bit difficult to
explain why this is needed without getting to technical. But I hope this
will change if it sees widespread use.

# Single Module
In general, Lookup objects are really easy to work with when you are have a
_single_ module that has a dependency on a library that needs a Lookup
object to be initialized. And you are in full control of initializing the
library.

# Inversion of Control/Container deployment
Here I am thinking about something like how people would normally deploy a
modern servlet WAR; A bunch of annotated classes packaged in a single jar.
Since your code is being initialized by the container you cannot directly
handoff a Lookup object to the container. Instead you need to rely on some
kind of delivery mechanism. For example, by requiring all deployable
bundles to provide a service (that secretly exposes the Lookup object) via
a ServiceLoader to the container. However, this quickly gets complicated
because if you work with something like Jakarte EE, where you have to
provide a standardized mechanism. With which any implementor can get a hold
of the provided lookup object. But at the same time make sure no one else
can read it.

This is an issue with qualified open statements via module-info as well. If
you do not know which implementation you are going to deploy your WAR in.
You cannot use a qualified open statement. So you are left with "open
module", which I suspect will be how things are going to work for a long
time.

Some of these issues might be solvable by using something like Module
layers.

# Multiple Modules
If your application consists of multiple modules. For example, you might
have some customer-services in one module and some order-service in a
another module. Then a third module that kind of glues the two modules
together. You might also have a DI framework, a JPA implementation and a
JaxRS container.

It is a bit of similar problem here, you need to handoff a Lookup object
from customer-services->main module and from order-services->main module.
And then the main module then needs to initialize each library with each of
the Lookup objects for every module.

You also sometimes get into some complex situations where an abstract class
is located in one module, and the concrete class in another module. Each of
them having an annotated method/field that you need to access. So you need
one Lookup object to access the method on the abstract class and another
Lookup object to access the method on the concrete class. Depending on how
you track your Loookup objects, you worst case end of with API's like this
public <T> inject(Class<T> type, MethodHandles.Lookup... lookup) and then
some complex logic to match methods with Lookup objects.

I actually really like working with Lookup objects. However, I think it
potentially could end up with a lot of micro management for larger projects.
And then people are going to be, fine, lets just put open module everywhere.

/Kasper