Annotations in Java Spring
Akriti Keswani
Posted on June 3, 2024
For decades as of today, Java is undoubtedly one of the most popular programming languages used at companies with massive codebases. You would think that now with languages such as Kotlin and Scala, companies probably want to pivot and entirely modernize their codebases, but there must be something so special about Java that makes people want to stay and revert to it. I wonder what that is…
Well, one could argue that it is the mere fact of Java being such a popular choice for object-oriented programming and that the JVM makes it so dynamic to run and deploy across complex operating systems. One must also consider though, the sheer flexibility of Java, especially with frameworks such as Spring that leverage containerizing objects and method calls, which essentially make applications easier to scale, which is quite necessary in enterprise software environments.
Let’s get into why Spring is so awesome as a framework, especially when you are dealing with huge codebases that require you to sift through hundreds of services in several interconnected repositories - we have all been there!
So in most normal applications, you definitely need a lot of boilerplate code to get started, especially if you are running a client on top of a server, and Spring makes this seamless with the abstraction and layering of classes, as well as annotations classifying objects, services, and the lines of code actually executing the other lines of code. All that sort of may seem confusing, but here’s the thing, annotations can really enhance the way your code looks, operates, and performs after more code has been written.
So, in Java Spring, it is a known concept to use annotations, which can get pretty tricky fast, because it abstracts away so much code that you would otherwise have to write. Annotations such as @Slf4j
(used for logging) can be super confusing to someone who is just starting to write classes in a Spring application. I'm here to tell you that it all gets easier, and makes sense eventually, like all things in life!
Typically, in these Java Spring applications, you usually have a main file that is like the runner, the one that calls the entire application, often denoted with a @SpringBootApplication
annotation. In its bare form, this file looks like:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
In reality @SpringBootApplication
is actually a combination of @SpringBootConfiguration
(specialized version of @Configuration
) @EnableAutoConfiguration,
and @ComponentScan.
The reason for this is that we need to reduce the boilerplate code and make the code more readable. We can see this in the way that @EnableAutoConfiguration
minimizes the need to write configuration code manually. Lastly, @ComponentScan
can do the work of registering beans in the application context for the build to work without explicitly declaring each component.
As you can see, each annotation serves a specific purpose, and together they enable the entire application to function. Let's dive deeper into @Repository
, another useful annotation that supports storage, retrieval, update, and delete operations on objects. This annotation is a specialization of @Component
, which allows Spring to detect and manage repository classes.
Here's an example:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Custom query methods can be defined here
}
In the comment area, you can include custom query logic using the @Query
annotation or take advantage of Spring Data JPA's query method conventions. For example, methods like IsStartingWith
or Containing
can be used to filter data.
A practical example would be:
List<User> findByFirstnameContaining(String fragment);
This method filters users whose first names contain the specified fragment. Using these conventions makes the code more readable and easier to maintain.
All this is great, but in order to do some action on the code and perform operations on how we are using all this data, understanding the @Service
annotation is quite necessary. The @Service
annotation is used to define a service class in the service layer of the application. This layer contains the business logic and acts on data passed between the controller and repository layers. Services often operate on models or POJO (Plain Old Java Object) objects, which represent the data in your application.
Here's an example of how a service can be structured:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Logic on finding a user by ID
public User findUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
// Logic on how to create or update a user
public User saveUser(User user) {
return userRepository.save(user);
}
}
Here we see that the @Autowired
annotation is simply ensuring that the service uses the UserRepository
to interface with the database. These methods are simply performing CRUD-like operations on the User
objects which are just POJOs.
Now, we mentioned POJOs but how do they even represent the data we are using? Most of it is simply having a getter, setter, and maybe a few methods here to represent unique use cases of manipulating the data. An example of a User model associated with the above would look something like:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String email;
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Now we see that we also have the @Entity
annotation here, but not to worry, it is simply ensuring that this can be mapped to a table in a database - this lets use the JPA (Java Persistance API) as mentioned earlier! @Id
is also included in this class to mark a primary key of the entity and this is just a unique identifier as the name implies.
The service class ensures that these POJOs transfer data between the repository and the controller, which we have not touched upon much, but essentially allows us to separate the presentation later from the application logic. Services are special in the sense that we can make sure the core logic of the application is applied properly before any data is saved, retrieved, or manipulated.
After going through all of this, it is crucial to understand how a typical Spring application flows together, and some key parts as mentioned are the controller (handling requests), service (business logic), and the repository (for CRUD operations). To leverage Java Spring’s full potential, these annotations are super useful to know and adapt, as they make the code more readable as well as maintainable!
Looking Ahead
In order to take full advantage of Spring and really understand what a brilliant architecture of an application may look like, it is vital to explore the microservices architecture, which I plan to delve into within the next article! Microservices are nice in that they offer more scalability and flexibility, since we can break down the application into smaller, deployable services.
Posted on June 3, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.