Reflecting on Java : Unveiling the magic of Reflection



This content originally appeared on Level Up Coding – Medium and was authored by Shashank Garg

Reflecting on Java : Unveiling the magic of Reflection

From Learning About Reflection to Mastering Its Use!

A tree with it’s reflection in water.

Welcome, fellow coders, to another adventure in the realm of Java programming! Today, we’re diving into the mystical world of Java Reflection and its pivotal role in crafting the JetORM project — a small but mighty ORM framework inspired by Hibernate.

What is Java Reflection?

You might be wondering, what exactly is reflection? Is it some sort of way to reflect something or onto something?

ummm….well, you’re not entirely wrong.

Imagine if your Java code could peek into its own soul — introspect its classes, fields, and methods at runtime. That’s exactly what Java Reflection allows us to do. It’s like having a backstage pass to your program’s inner workings, enabling you to manipulate and interrogate classes, fields, and methods dynamically.

Wonderful, right?

Why is Reflection So Powerful?

Imagine you’re building an application where the structure of data entities or classes is not fully known at compile time. Java Reflection becomes your superhero cape — it empowers you to:

  • Inspect Class Information: Retrieve information about fields, methods, and constructors.
  • Invoke Methods Dynamically: Call methods dynamically, even private ones.
  • Access and Modify Fields: Peek into and modify private fields of objects.
  • Create Objects Dynamically: Instantiate classes or invoke constructors dynamically.
  • Handle Annotations: Process and utilize annotations for custom behaviors.

It’s like having superpowers for your code!

SpongeBob’s character Squidward shocked while bathing in a tub.
When you realize Java Reflection can read private fields!

Practical Applications of Java Reflection

  • Building ORM Frameworks: Reflection powers ORM frameworks like Hibernate by dynamically mapping Java objects to database tables using annotations like @Entity and @Column, facilitating seamless SQL generation and database interaction.
  • Dependency Injection: Frameworks like Spring utilize Reflection for Dependency Injection (DI). At runtime, Spring inspects annotated classes to automatically inject dependencies, reducing boilerplate code and improving modularity.
  • Testing and Debugging: Reflection is essential in testing frameworks like JUnit, enabling dynamic discovery and execution of test methods without explicit calls. This ensures thorough test coverage and effective debugging capabilities.
  • Serialization/Deserialization: Libraries like Jackson or Gson use reflection to convert objects to and from JSON or XML formats.
  • IDEs and Tools: IDEs like Eclipse and IntelliJ use reflection to provide features like code completion, refactoring, and generating boilerplate code.

Let’s Delve into the Code

To get a taste of reflection, let’s see how it creates a table from a class.

We’ll start by making some custom annotations. Here, we’ve created @Entity and @Id annotations for our class and field, respectively.

@Retention(RUNTIME)
@Target(TYPE)
public @interface Entity {
String name() default "";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Id {
String name() default "";
}

@Retention: Determines when the annotation is available to the JVM. Here, it's set to RUNTIME, meaning the annotation is accessible at runtime. Other values are SOURCE and CLASS.

@Target: Specifies the element where the annotation is applied. In our example, TYPE indicates the annotation applies to classes. Other values include FIELD, METHOD, PARAMETER, and many more.

Let’s create a class Userr, annotated with our custom annotations. The @Entity annotation will mark the class for conversion to a table, and the @Id annotation will mark the fields to be used as primary keys.

@Entity
public class Userr {

@Id
private Long userId;
private String userName;
private String userEmail;
public Userr(){}
public Userr(Long userId, String userName, String userEmail) {
this.userId = userId;
this.userName = userName;
this.userEmail = userEmail;
}
}

Specify the package where you want to search for classes using reflection.


Reflections reflections = new Reflections("your.directory.name");

Fetch the classes with @Entity annotation using reflection.

Set<Class<?>> entities = reflections.getTypesAnnotatedWith(Entity.class);

For each class, we aim to generate a CREATE TABLE query based on its member variables. But how do we access the private members? 🤔

Well that’s the essence of Reflection’s power. It enables access to private variables by setting their accessibility to true, offering flexibility in working with class members dynamically.

field.setAccessible(true);

Retrieve the class name and its fields, which are essential for constructing the query.

String className = entity.getSimpleName();
Field[] fields = entity.getDeclaredFields();

Iterate through the fields to retrieve each field’s name and data type. Ensure accessibility is set to true for private fields encountered. Additionally, store the primary key by checking for the presence of the @Id annotation on the field.

StringBuilder tableQuery = new StringBuilder(String.format("CREATE TABLE `%s`(", className));
Field primaryKey = null;

for(Field field : classFields) {
field.setAccessible(true);
if(field.isAnnotationPresent(Id.class)){
primaryKey = field;
continue;
}
//this statement maps the datatype to SQL type, like int/Integer to Integer.
String sqlDataType = TableUtility.mapJavaDataTypeToSql(field.getType().getSimpleName());
String fieldQuery = String.format("%s %s,", field.getName(), sqlDataType);

If the primary key is not present, it’s preferable to throw an exception rather than proceeding with table creation.

if(primaryKey == null){
throw new PrimaryKeyNotPresentException("Primary Key not present for class : " + entity.getName());
}

Include the primary key in the generated query statement.

tableQuery.append(String.format("PRIMARY KEY (%s));", primaryKey.getName()));

Now, execute the query and voilà!

Table has been successfully created!

Congratulations on dynamically creating an SQL table from a Java class!

For detailed insights on building your own ORM, check out JetORM.

Embracing Java Reflection Responsibly

Now you might wonder, if reflection is so powerful, why aren’t we using it in our everyday coding?

Two students raising their hands and saying “Because we didn’t know about it”.

Maybe, but there’s more to consider…

While Java Reflection opens doors to dynamic and flexible coding, it’s crucial to use this power responsibly:

  • Performance Considerations: Reflection can impact performance due to its dynamic nature. Use it judiciously in performance-critical code paths.
  • Security Concerns: Reflection bypasses access control mechanisms, potentially exposing your application to security risks. Validate inputs and handle exceptions diligently.

Wrapping Up

Java Reflection isn’t just a tool — it’s a gateway to dynamic and powerful Java programming. Whether you’re building ORM frameworks or experimenting with runtime code manipulation, Reflection opens doors to new possibilities.

So, next time you’re stuck in SQL query hell, remember: Java Reflection might just be the magic wand you need to make your coding life a tad bit easier.

I hope you found this content helpful! If you have any burning questions or thoughts, drop them in the comments below. And if you enjoyed this blog, go ahead and give it a clap — let’s spread the love for Java Reflection!

Thanks a ton for reading!

References:

Using Java Reflection — Oracle

Java Reflection — Baeldung

Reflection in Java — GFG


Reflecting on Java : Unveiling the magic of Reflection was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding – Medium and was authored by Shashank Garg