Easier Java reflections.
Reflections, access to program elements structure from themselves, although not recommended in applications are essential when creating frameworks. Java has somewhat complicated and verbose ways to do reflections, sometimes to the extremes.
Introspectable comes to rescue. This library has few methods that eases common reflection tasks.
Queries allow simpler access to reflections objects. They are available under the
org.perfectable.introspection.query
package. The examples should explain everything.
To inject created mocks, it needs to find all non-static fields in test object, that have annotation @Mock
present.
Stream<Field> injectedMocks =
introspect(testObject.getClass())
.fields()
.excludingModifier(Modifier.STATIC)
.annotatedWith(Mock.class)
.stream();
To create remote proxy, it needs to know all the interfaces implemented by provided class, that extend
Remote
interface, but excluding remote, and their name ends with Rpc
.
Iterable<? extends Class<?>> interfaces =
introspect(proxiedClass)
.interfaces()
.filter(candidate -> candidate.getSimpleName().endsWith("Rpc"))
.upToExcluding(Remote.class)
.filter(Remote.class::isAssignableFrom);
To clear form, binding library needs to know all single-parameter void non-static method that are named
apply
:
Stream<Method> appliers =
introspect(target.getClass())
.methods()
.named("apply")
.parameterCount(1)
.returningVoid()
.excludingModifier(Modifier.STATIC)
.stream();
To inject new instances with required dependencies, IoC container needs to know which constructor is injected:
Constructor<C> injectedConstructor =
introspect(instanceClass)
.constructors()
.annotatedWith(Inject.class)
.requiringModifier(Modifier.PUBLIC)
.option()
.orElseThrow(MissingInjectableConstructor::new);
To discover and register all servlets, web application needs to scan classpath for Servlet
classes to instantiate:
introspect(Application.class.getClassLoader())
.classes()
.inPackage(Application.class.getPackage())
.subtypeOf(Servlet.class)
.stream()
.map(Introspections::introspect)
.map(ClassIntrospection::instantiate)
.forEach(this::registerServlet);
Native java java.lang.reflect.Type
is annoyingly hard to interrogate and reason with. To check subtyping.
Injection framework needs to check if concrete implementation of Supplier
can be injected into field with
parameterized type:
boolean canInject =
introspect(targetField)
.typeView()
.isSuperTypeOf(injectedValue);
This check will fail if targetField
is declared as Supplier<Node<C> newNodeSupplier
and injectedValue
is instance
of class StringSupplier
which implements Supplier<String>
, as it should. But the check will pass when doing
targetField.getType().isInstance(injectedValue)
. Using targetField.getGenericType()
would help, but all the logic
implemented here would need to be recreated.
To convert an incoming message to an appropriate object, messaging needs to know actual type parameter for consumer:
Class<?> targetClass =
introspect(MessageConsumer.class)
.view()
.resolve(messageConsumer.getClass())
.parameter(0)
.erasure()
Let's say we have following declarations:
interface MessageConsumer<M extends Message, S extends State> {}
class LoginMessageConsumer<S extends LoginState> implements MessageConsumer<LoginMessage, S> {}
Let's say variable messageConsumer
is of class with erasure LoginMessageConsumer
. In this case resolve
allows
creating new type that uses same type variable substitution. Expression
TypeView.of(Consumer.class).resolve(messageConsumer.getClass())
will produce TypeView
with synthetic
parameterized type MessageConsumer<LoginMessage, S>
because parameter M
in MessageConsumer
was substituted
with LoginMessage
in declaration of class LoginMessageConsumer
.
This is a simplified case, because in this situation calling Class#getGenericSuperclass
. But even then, this needs
to be casted onto ParameterizedType
, first parameter needs to be extracted, casted to Class
and so on.
Introspectable adds a simple facade for creating proxies. Natively, it supports standard JDK proxies and javassist + objenesis.
Proxies are built by org.perfectable.introspection.proxy.ProxyBuilder
. It allows creating proxy by chaining
configuration. After the configuration is done, proxies for specific objects can be created by providing
InvocationHandler
:
Remoting library needs to replace method call on proxy with message transmission:
Object proxy =
ProxyBuilder.ofType(stubClass).withInterface(Remote.class)
.instantiate(invocation -> {
Transmission transmission =
invocation.decompose((method, target, parameters) ->
channel.transmit(method.getName(), proxyName, serialize(parameters)));
return transmission.readReplay();
});
UserService
service calls needs to be logged:
UserService proxy =
ProxyBuilder.ofInterface(UserService.class)
.instantiate(invocation -> {
String source = invocation.decompose((method, target, parameters) ->
method.getDeclaringClass() + "#" + method.getName());
LOGGER.info("Before call on {}", source);
try {
return invocation.withReceiver(realService).invoke();
}
finally {
LOGGER.info("After call on {}", source);
}
});
Sometimes you just need to get instance of annotation type. Be it library interface which assumes that you will extract
the annotation from element, or some method call just requires annotation, assuming you extract. In either case,
you can build it using AnnotationBuilder
.
Injection framework requires you to provide javax.inject.Named
annotation instance:
Named instance =
AnnotationBuilder.of(Named.class)
.with(Named::value, "fantastic")
.build()
Sometimes it would be nice to have properties or method compile-time checked to match requested signature. You can use
FunctionalReference
superinterface to make functional interfaces introspectable.
Introspection framework uses annotation methods to build annotations:
@FunctionalInterface
public interface MemberExtractor<A, X> extends FunctionalReference {
X extract(A annotation);
}
class AnnotationBuilder<A> {
public <X> AnnotationBuilder<A> with(MemberExtractor<A, X> member, X value) {
String name = member.introspect().referencedMethodName();
return withMember(name, value); // private, safe
}
<...>
}
Injection framework needs to extract factory class and product class from provided factory method:
@FunctionalInterface
public interface Factory<F, P> extends FunctionalReference {
P build(F factory);
}
class FactoryInjector<F, P> {
public static <F, P> FactoryInjector<F, P> of(Factory<F, P> factory) {
FunctionalReference.Introspection introspection = factory.introspect(); // this kinda slow
Class<F> factoryClass = (Class<F>) introspection.resultType();
Class<P> productClass = (Class<P>) introspection.parameterType(0); // safe
return of(factoryClass, productClass, factory);
}
<...>
}
A lot of code still uses Java Beans as data container. Package org.perfectable.introspection.bean
helps manage
those:
Bean<User> userBean = BeanSchema.of(User.class).instantiate();
form.forEachField((fieldName, value) ->
userBean.property(fieldName).set(value));
User boundUser = userBean.contents();
Add as dependency:
<dependency>
<groupId>org.perfectable</groupId>
<artifactId>introspectable</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>