Utility classes that simplify common use cases of Java Reflection.
We ship the utility classes PropertyUtils
, ClassUtils
, and ImmutableProxy
that are described in the following sections.
Replacement for org.apache.commons.beanutils.PropertyUtils
with deterministic behaviour
and support to retrieve PropertyDescriptors via Java 8 method references.
Example:
class MyPojo
{
private Long number;
// getters and setters
}
PropertyDescriptor numberProperty = PropertyUtils.getPropertyDescriptor(MyPojo.class, MyPojo::getNumber);
MyPojo pojo = new MyPojo();
PropertyUtils.write(pojo, numberProperty, 12345L);
Long number = PropertyUtils.read(pojo, numberProperty);
assertEquals(12345L, number);
Records of Java 14 and newer are also supported by PropertyUtils
.
Example:
record Point(int x, int y) {}
String propertyName = PropertyUtils.getPropertyName(Point.class, Point::x);
assertEquals("x", propertyName);
ClassUtils
can be used to obtain the method name in a type-safe way. We currently support getter-like methods as well as methods that return void
.
interface MyInterface
{
void doSomething();
int getSomething();
}
String methodName = ClassUtils.getMethodName(MyInterface.class, MyInterface::doSomething);
assertEquals("doSomething", methodName);
String methodName = ClassUtils.getMethodName(MyInterface.class, MyInterface::getSomething);
assertEquals("getSomething", methodName);
Note: ClassUtils.getMethodName
does not support static methods.
ClassUtils.getRealClass(…)
can be used to obtain the underlying "real" class of a proxy.
Its typical use-case is as drop-in replacement for object.getClass()
when object
is potentially a proxy, for example
when working with JPA/Hibernate.
We currently support Java, Byte Buddy, Hibernate and cglib/javassist proxies.
Class<?>[] interfaces = { MyInterface.class };
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), interfaces, (p, method, args) -> null);
assertEquals(MyInterface.class, ClassUtils.getRealClass(proxy));
It’s sometimes desirable to make objects immutable to prevent programming mistakes, such as the accidental modification of an object that is passed to another method.
In some cases the class itself cannot be made immutable. For example, because mutability is required by the JPA provider, the JSON serialization library, or the class is not owned by you.
Creating deep clones might come to the rescue, however, the negative performance impact might not be acceptable and you cannot detect that the clone is accidentally modified.
In such cases, ImmutableProxy
can be used to create a deep but lightweight
read-only view of a POJO that follows the JavaBean
conventions.
Invocation of getters and read-only methods is allowed but other methods such
as setters are rejected by default.
Example:
class MyPojo
{
private String name;
private List<MyPojo> children = new ArrayList<>();
// getters and setters
}
MyPojo original = new MyPojo();
original.setName("original");
MyPojo immutableProxy = ImmutableProxy.create(original);
immutableProxy.getName() // ✔ returns "original"
immutableProxy.setName("…") // ✖ throws UnsupportedOperationException
By default, ImmutableProxy
wraps the return value of a getter in a immutable proxy:
original.getChildren().add(new MyPojo("child"));
immutableProxy.getChildren().size() // ✔ returns 1
MyPojo firstChild = immutableProxy.getChildren().get(0);
firstChild.getName() // ✔ returns "child"
firstChild.setName("…") // ✖ throws UnsupportedOperationException
immutableProxy.getChildren().clear() // ✖ throws UnsupportedOperationException
Some methods need to be annotated with @ReadOnly
if ImmutableProxy
incorrectly considers the method as mutating:
class MyPojo
{
List<String> elements;
@ReadOnly
int size() {
return elements.size();
}
}
As a final word of warning, please note that ImmutableProxy
follows a best-effort approach but cannot guarantee to detect all possible modifications.
For example, it cannot detect that a getter actually modifies the state as a side-effect.
Records of Java 14 and newer are also supported by ImmutableProxy
.
Example:
record Point(int x, int y) {}
class MyPojo
{
private String name;
private Point point;
// getters and setters
}
MyPojo original = new MyPojo();
original.setPoint(new Point(1, 2));
MyPojo immutableProxy = ImmutableProxy.create(original);
// The Point record is detected to be (deeply) immutable, so ImmutableProxy can directly return the value
assertSame(immutableProxy.getPoint(), original.getPoint());
If the record class is (potentially) not deeply immutable, ImmutableProxy can be told to clone the records on-the-fly.
Example:
record WrappedList(List<String> values) {}
class MyPojo
{
private WrappedList list;
// getters and setters
}
MyPojo original = new MyPojo();
original.setList(new WrappedList(List.of("a", "b", "c")));
MyPojo immutableProxy = ImmutableProxy.create(original, ImmutableProxyOption.ALLOW_CLONING_RECORDS);
immutableProxy.getList().values().get(0) // ✔ returns "a"
immutableProxy.getList().values().clear() // ✖ throws UnsupportedOperationException
To get a rough idea about the performance impact of the ImmutableProxy
method interception,
we include a JMH benchmark: ImmutableProxyBenchmark.java.
The benchmark compares direct method invocation of simple fields vs. method invocations through the immutable proxy.
Below you can find one of the benchmark results as conducted on a Thinkpad T480s with an Intel i7-8650U CPU running on Linux 5.16.2
.
# JMH version: 1.34
# VM version: JDK 17.0.1, OpenJDK 64-Bit Server VM, 17.0.1+12
[…]
# Run complete. Total time: 00:41:57
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. […]
Benchmark Mode Cnt Score Error Units
ImmutableProxyBenchmark.unproxiedEquals thrpt 3 47106,090 ± 22356,782 ops/s
ImmutableProxyBenchmark.proxiedEquals thrpt 3 11537,742 ± 4097,233 ops/s
It shows that the invocation of the equals()
method is about 4-5 times slower when routed through the ImmutableProxy
.
However, please note that the benchmark itself runs an inner loop with 10000 cycles.
This actually gives us 10000 * 11537,742 ≈ 115 million invocations per second!
Add the following Maven dependency to your project:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>reflection-util</artifactId>
<version>2.17.0</version>
</dependency>
- Java 17+
- Apache Commons BeanUtils
- Jodd Methref
ImmutableProxy
: Immutator