DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Introduction to Spring Boot and JDBCTemplate: JDBC Template
  • Multi-Tenancy Implementation Using Spring Boot, MongoDB, and Redis
  • DDD and Spring Boot Multi-Module Maven Project
  • Dependency Injection in Spring

Trending

  • Scaling Microservices With Docker and Kubernetes on Production
  • Integrating Model Context Protocol (MCP) With Microsoft Copilot Studio AI Agents
  • The End of “Good Enough Agile”
  • Implementing API Design First in .NET for Efficient Development, Testing, and CI/CD
  1. DZone
  2. Coding
  3. Frameworks
  4. Spring Beans With Auto-Generated Implementations: How-To

Spring Beans With Auto-Generated Implementations: How-To

This tutorial will guide you through the process of building a sample framework that implements the automatic creation of Spring beans from Java interfaces.

By 
Pavel Grigorev user avatar
Pavel Grigorev
·
Jul. 08, 21 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
17.1K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

Quite a few frameworks implement the automatic creation of components from Java interfaces. The most popular one is probably Spring Data, which is a whole family of data access frameworks. Spring Data JPA, for instance, helps us run JPA queries. Here's a quick example:

Java
 
public interface ClientRepository extends JpaRepository<Client, Long> {
  List<Client> findByNameLikeAndLastActiveBefore(String name, Date date);
}


Without going into detail, Spring Data JPA will create an implementation class from that interface at runtime and register a bean of type ClientRepository in the application context. The query string will be derived from the method name. The method will run the following JPA query:

SQL
 
select x from Client x where x.name like ?1 and x.lastActive < ?2


That is a nifty declarative API in which method declarations themselves define what parameters to run the framework's underlying functionality with. And we don't have to write any implementation code.

It's a convenient way to integrate libraries and hide boilerplate that they require us to write. As a developer, I'd like to have the ability to build custom APIs that follow such patterns. This would let me build tools that are convenient in use, easily extensible, and specific to my project's business domain.

What We Will Build

In this tutorial, we will build a sample framework that implements the automatic creation of Spring beans from interfaces. The framework's domain will be e-mail and usage will be as follows:

Java
 
@EmailService
public interface RegistrationEmailService {
  void sendConfirmation(@Param("name") String name, @Param("link") String link, @To String email);
  void sendWelcome(@Param("client") @To Client client);
}


The conventions are:

  • @EmailService marks the interface for the framework to be able to distinguish it from regular interfaces.
  • What goes after "send" in a method name defines the email subject and body template. In the case of "sendWelcome", for instance, the template will be "welcome.html" and the subject will be "email.subject.welcome" in the message bundle.
  • @Param specifies a template parameter to bind a method parameter to.
  • @To indicates a recipient.

NOTICE: This article as well as the source code on GitHub does not contain any examples of sending emails with Java. The purpose of this tutorial is to provide a sample framework implementation to illustrate the approach.

This tutorial is accompanied by a working code example on GitHub.

How-To

At a higher level, the framework we're about to build is to do three things:

  1. Find all interfaces marked with @EmailService.
  2. Create an implementation class for each of these interfaces.
  3. Register a Spring-managed bean for each of the interfaces.

We will use a library spring-icomponent as a base (disclosure: I am the author of this library). It covers steps 1 and 3, and partially step 2. In step 2, implementations created by the library are simply proxies that route method invocations to the user-defined handlers. All we have to do as developers is build a handler and bind it to the methods.

Let's start by adding spring-icomponent to a project. The library supports Spring 5.2.0 or newer.

Gradle:

Groovy
 
dependencies {
  implementation 'org.thepavel:spring-icomponent:1.0.8'
}


Maven:

XML
 
<dependency>
  <groupId>org.thepavel</groupId>
  <artifactId>spring-icomponent</artifactId>
  <version>1.0.8</version>
</dependency>


To set it up, add the @InterfaceComponentScan annotation to a java-config class:

Java
 
@Configuration
@ComponentScan
@InterfaceComponentScan
public class AppConfiguration {
}


This will trigger package scanning. Usage is similar to @ComponentScan. This configuration will scan from the package of AppConfiguration. You can also specify basePackages or basePackageClasses to define specific packages to scan.

By default, the scanner will search for interfaces annotated with @Component. The @EmailService annotation will be meta-annotated with @Component, so it will be picked up by the scanner with the default settings.

Building a Method Handler

A method handler is to implement the desired functionality, in our case, sending emails. It must implement the MethodHandler interface:

Java
 
@Component
public class EmailServiceMethodHandler implements MethodHandler {
  @Override
  public Object handle(Object[] arguments, MethodMetadata methodMetadata) {
    return null;
  }
}


The handle method is supplied with invocation arguments as well as method metadata so that we can derive extra execution parameters from the signature of the method being called.

Recalling the method declaration conventions we've established earlier, a method name is "send" + email type. Email type defines the email subject and body template:

Java
 
// Get the method name and cut off "send"
// Eg: "sendWelcome" -> "Welcome"
String emailType = methodMetadata.getSourceMethod().getName().substring(4);

// "Welcome" -> "welcome"
String templateName = Introspector.decapitalize(emailType);

// "welcome" -> "email.subject.welcome"
String subjectKeyInMessageBundle = "email.subject." + templateName;


In order to collect the template parameters and recipient email addresses from the invocation arguments we have to iterate through the method parameters:

Java
 
// Get metadata of the method parameters
List<ParameterMetadata> parametersMetadata = methodMetadata.getParametersMetadata();

// Collections to populate
Map<String, Object> templateParameters = new HashMap<>();
Set<String> emails = new HashSet<>();

for (int i = 0; i < parametersMetadata.size(); i++) {
  // Get annotations of the i-th method parameter
  MergedAnnotations parameterAnnotations = parametersMetadata.get(i).getAnnotations();
  Object parameterValue = arguments[i];

  // TODO: Populate templateParameters
  // TODO: Populate emails
}


MergedAnnotations is the Spring's class that represents a view of all annotations and meta-annotations declared on an element, in this case, method parameter.

If a method parameter is annotated with @Param then it's a template parameter. Here's the source code of @Param:

Java
 
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Param {
  String value();
}


Its value attribute specifies a name of a template parameter to bind the annotated method parameter to. We should populate the templateParameters map inside the loop:

Java
 
for (int i = 0; i < parametersMetadata.size(); i++) {
  // Get annotations of the i-th method parameter
  MergedAnnotations parameterAnnotations = parametersMetadata.get(i).getAnnotations();
  Object parameterValue = arguments[i];

  if (parameterAnnotations.isPresent(Param.class)) {
    // Get the Param annotation then get the value of its `value` attribute as String
    String templateParameterName = parameterAnnotations.get(Param.class).getString("value");
    templateParameters.put(templateParameterName, parameterValue);
  }

  // TODO: Populate emails
}


If a method parameter is annotated with @To then it represents a recipient. Here's the source code of @To:

Java
 
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface To {
}


A recipient may either be a plain string or a Client object:

Java
 
if (parameterAnnotations.isPresent(To.class)) {
  if (parameterValue instanceof String) {
    emails.add((String) parameterValue);
  } else if (parameterValue instanceof Client) {
    Client client = (Client) parameterValue;
    emails.add(client.getEmail());
  }
}


Now, as we've got all the parameters necessary to send the email, we can send it:

Java
 
String subject = getMessage(subjectKeyInMessageBundle);
String body = buildFromTemplate(templateName, templateParameters);

emailSender.send(subject, body, emails);


I'm leaving the helper methods out just to keep it brief. You can find them in this tutorial's source code on GitHub. To sum it up, here are all the code snippets put together:

Java
 
@Component
public class EmailServiceMethodHandler implements MethodHandler {
  @Autowired
  EmailSender emailSender;

  @Override
  public Object handle(Object[] arguments, MethodMetadata methodMetadata) {
    // Cut off "send"
    String emailType = methodMetadata.getSourceMethod().getName().substring(4);
    String templateName = Introspector.decapitalize(emailType);
    String subjectKeyInMessageBundle = "email.subject." + templateName;

    List<ParameterMetadata> parametersMetadata = methodMetadata.getParametersMetadata();
    Map<String, Object> templateParameters = new HashMap<>();
    Set<String> emails = new HashSet<>();

    for (int i = 0; i < parametersMetadata.size(); i++) {
      MergedAnnotations parameterAnnotations = parametersMetadata.get(i).getAnnotations();
      Object parameterValue = arguments[i];

      if (parameterAnnotations.isPresent(Param.class)) {
        String templateParameterName = parameterAnnotations.get(Param.class).getString("value");
        templateParameters.put(templateParameterName, parameterValue);
      }

      if (parameterAnnotations.isPresent(To.class)) {
        if (parameterValue instanceof String) {
          emails.add((String) parameterValue);
        } else if (parameterValue instanceof Client) {
          Client client = (Client) parameterValue;
          emails.add(client.getEmail());
        }
      }
    }

    String subject = getMessage(subjectKeyInMessageBundle);
    String body = buildFromTemplate(templateName, templateParameters);

    emailSender.send(subject, body, emails);

    return null;
  }

  // helper methods omitted
}


The method handler is now complete. In the next section, we will bind this method handler to the methods in the RegistrationEmailService interface.

Building a Marker Annotation

It's as simple as this:

Java
 
@Retention(RUNTIME)
@Target(TYPE)
@Service
@Handler("emailServiceMethodHandler")
public @interface EmailService {
}


Two important things here:

  • @Service is what makes it a marker. I've previously mentioned that the framework will search for interfaces annotated with @Component. As you may already know, @Service is meta-annotated with @Component, so @EmailService is also transitively annotated with @Component.
  • @Handler specifies a MethodHandler bean. "emailServiceMethodHandler" is the name of the bean. When @Handler is declared on an interface, the specified method handler will handle invocations of all methods in the interface.

By decorating an interface with @EmailService we tell the framework to route invocations of all methods in the interface to the EmailServiceMethodHandler bean:

Java
 
@EmailService
public interface RegistrationEmailService {
  void sendConfirmation(@Param("name") String name, @Param("link") String link, @To String email);
  void sendWelcome(@Param("client") @To Client client);
}


Now, RegistrationEmailService can be injected into other beans:

Java
 
@Service
public class RegistrationServiceBean implements RegistrationService {
  @Autowired
  RegistrationEmailService emailService;
  //...

  @Override
  public void register(String name, String email) {
    Client client = createClient(name, email);
    persist(client);
    emailService.sendWelcome(client);
  }

  //...
}


Adding New Methods

This tutorial's demo is a Spring Boot project with the Thymleaf dependency included. You can check out the demo project and use it as a playground to follow the steps described in this section.

Let's say the system must email clients about security policy updates and we have to build a component responsible for that. The component's API could be as follows:

Java
 
void sendSecurityPolicyUpdate(Client client, String policyLink);


The email text will be a template with two variables: client name and link. Here's a snippet of the email template built with Thymeleaf:

HTML
 
<p th:text="'Hello, ' + ${client.name} + '!'"/>
<p>
    Our security policy has been updated. Please follow the link for more info:
    <a th:href="${link}" th:text="${link}"/>
</p>


If we pick a method name "sendSecurityPolicyUpdate" then, by convention, the template name must be "securityPolicyUpdate". In a Spring Boot project with the default Thymeleaf starter configuration, the corresponding template file must be located at "resources/templates/securityPolicyUpdate.html". So we have to create the file and copy and paste the template text into it.

In order to define the email subject, we have to add a message to the message bundle with a key "email.subject.securityPolicyUpdate":

Properties files
 
email.subject.confirmation = Confirm your e-mail address
email.subject.welcome = Welcome to the app
email.subject.securityPolicyUpdate = Security policy updated


The message bundle is located at "resources/messages.properties" in Spring Boot projects by default.

The setup is complete so we can now create a new component for the policy emails:

Java
 
@EmailService
public interface PolicyEmailService {
  void sendSecurityPolicyUpdate(@Param("client") @To Client client, @Param("link") String policyLink);
}


Here's a usage example for the new component:

Java
 
String link = policyService.getPolicyLink();

clientRepository
    .getAllClientsAsStream()
    .forEach(client -> policyEmailService.sendSecurityPolicyUpdate(client, link));


Conclusion

Pretty quickly, we've built a small and simple email sending tool with business domain-specific, easily extensible API. This is just a sample intended to illustrate the approach that could help in reducing boilerplate code but, on the other hand, has some extra code overhead. So it's "this much code vs that much code" decision. When you, once again, are considering building a wrapper around some client API to simplify its usage in certain patterns, this is when this approach may potentially be beneficial.

This tutorial's source code is available on GitHub.

Spring Framework Spring Boot Implementation Java (programming language) Interface (computing) Template Framework

Opinions expressed by DZone contributors are their own.

Related

  • Introduction to Spring Boot and JDBCTemplate: JDBC Template
  • Multi-Tenancy Implementation Using Spring Boot, MongoDB, and Redis
  • DDD and Spring Boot Multi-Module Maven Project
  • Dependency Injection in Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!