The important parts of the Java source code are:
- StoreApp.java: This is the main entry class for the application. Since this is a Spring Boot application, the main class is executable and you can start the application by just running this class from an IDE. Let's take a look at this class:
- The class is annotated with a bunch of Spring JavaConfig annotations:
@ComponentScan
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class})
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
-
- The first one, @ComponentScan, tells the Spring application to scan the source files and auto detect Spring components (Services, Repository, Resource, Configuration classes that define Spring beans, and so on).
- The second one is @EnableAutoConfiguration, which tells Spring Boot to try to guess and auto-configure beans that the application might need based on the classes found on the classpath and the configurations we have provided. The exclude settings specifically tells Spring Boot not to auto-configure the specified beans.
- The third one, @EnableConfigurationProperties, helps register additional configurations for the application via property files.
-
- The main method of the class bootstraps the Spring Boot application and runs it:
public static void main(String[] args) throws UnknownHostException {
SpringApplication app = new SpringApplication(StoreApp.class);
DefaultProfileUtil.addDefaultProfile(app);
Environment env = app.run(args).getEnvironment();
...
}
- config: This package contains Spring bean configurations for the database, cache, WebSocket, and so on. This is where we will configure various options for the application. Some of the important ones are:
- CacheConfiguration.java: This class configures the Hibernate second level cache for the application. Since we chose Hazelcast as the cache provider, this class configures the same way.
- DatabaseConfiguration.java: This class configures the database for the application and enables transaction management, JPA auditing, and JPA repositories for the application. It also configures Liquibase to manage DB migrations and the H2 database for development.
- SecurityConfiguration.java: This is a very important part of the application as it configures security for the application. Let's take a look at important parts of the class:
- The annotations enable web security and method level security so that we can use @Secured and @Pre/PostAuthorize annotations on individual methods:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
-
-
- The following configuration tells the application to ignore static content and certain APIs from Spring security configuration:
-
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/api/register")
.antMatchers("/api/activate")
.antMatchers("/api/account/reset-
password/init")
.antMatchers("/api/account/reset-
password/finish")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
-
-
- The following configuration tells Spring security which endpoints are permitted for all users, which endpoints should be authenticated, and which endpoints require a specific role (ADMIN, in this case):
-
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
...
.antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker")
.hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**")
.hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html")
.hasAuthority(AuthoritiesConstants.ADMIN)
.and()
.apply(securityConfigurerAdapter());
}
-
- WebConfigurer.java: This is where we set up HTTP cache headers, MIME mappings, static assets location, and CORS (Cross-Origin Resource Sharing).
JHipster provides great CORS support out of the box:
- CORS can be configured using the jhipster.cors property, as defined in the JHipster common application properties (http://www.jhipster.tech/common-application-properties/).
- It is enabled by default in dev mode for monoliths and gateways. It is disabled by default for microservices as you are supposed to access them through a gateway.
- It is disabled by default in prod mode for both monoliths and microservices, for security reasons.
- domain: The domain model classes for the application are in this package. These are simple POJOs which have JPA annotations mapping it to a Hibernate entity. When the Elasticsearch option is selected, these also act as the Document object. Let's take a look at the User.java class:
- An entity class is characterized by the following annotations. The @Entity annotation marks the class as a JPA entity. The @Table annotation maps the entity to a database table. The @Cache annotation enables second level caching of the entity, and it also specifies a caching strategy:
@Entity
@Table(name = "jhi_user")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
- There are various annotations used at field level in these classes. @Id marks the primary key for the entity. @Column maps a field to a database table column by the same name when no override is provided. @NotNull, @Pattern, and @Size are annotations that are used for validation. @JsonIgnore is used by Jackson to ignore fields when converting the objects into JSON which are to be returned in the REST API requests. This is especially useful with Hibernate as it avoids circular references between relationships, which create tons of SQL DB requests and fail:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 1, max = 50)
@Column(length = 50, unique = true, nullable = false)
private String login;
@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash",length = 60)
private String password;
-
- The relationships between the database tables are also mapped to the entities using JPA annotations. Here, for example, it maps a many-to-many relationship between a user and user authorities. It also specifies a join table to be used for the mapping:
@JsonIgnore
@ManyToMany
@JoinTable(
name = "jhi_user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@BatchSize(size = 20)
private Set<Authority> authorities = new HashSet<>();
- repository: This package holds the Spring Data repositories for the entities. These typically interface definitions which are automatically implemented by Spring Data. This removes the need for us to write any boilerplate implementations for the data access layer. Let's look at the UserRepository.java example:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant
dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmailIgnoreCase(String email);
...
}
-
- The @Repository annotation marks this as a Spring data repository component.
- The interface extends JpaRepository, which lets it inherit all the default CRUD operations like findOne, findAll, save, count, and delete.
- Custom methods are written as simple method definitions following the Spring data naming conventions so that the method name specifies the query to be generated. For example, findOneByEmailIgnoreCase generates a query equivalent of SELECT * FROM user WHERE LOWER(email) = LOWER(:email).
- security: This package holds Spring security-related components and utils, and since we chose JWT as our authentication mechanism, it holds JWT-related classes such as TokenProvider, JWTFilter, and JWTConfigurer as well.
- service: This package holds the service layer consisting of Spring service beans, DTOs, Mapstruct DTO mappers, and service utilities.
- web: This package holds the web resource classes, view models classes and utility classes.
- rest: This package holds Spring resource classes for the REST API. It also holds view model objects and utilities. Let's take a look at UserResource.java:
- The resource classes are marked with the @RestController and @RequestMapping("/api") annotations from Spring. The latter specifies the base URL path for the controller so that all <applicationContext>/api/* requests are forwarded to this class.
- Request methods are annotated with annotations according to their purpose, for example, the below marks the createUser method as a PostMapping for "/users", which means all POST requests to <applicationContext>/api/users will be served by this method. The @Timed annotation is used to measure the performance of the method. The @Secured annotation restricts the method access to the specified role:
- rest: This package holds Spring resource classes for the REST API. It also holds view model objects and utilities. Let's take a look at UserResource.java:
@PostMapping("/users")
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity createUser(@Valid @RequestBody ManagedUserVM managedUserVM) throws URISyntaxException {
...
}
-
- WebSocket: This package holds the Websocket controllers and view models.
JHipster uses DTO (Data Transfer Object) and VM (View Model) on the server side. DTOs are for transferring data from the service layer to and from the resource layer. They break the Hibernate transactions and avoids further lazy loading from being triggered by the resource layer. VMs are only used for displaying data on the web frontend and don't interact with the service layer.