Skip to content

Commit 16a9543

Browse files
authored
native image with spring boot 3 (#5)
* attempting to generate native image image generates fine.. but fails because of EntityGraph Revisit after these two issues are fixed - spring-attic/spring-native#1729 - spring-attic/spring-native#1728 Found a temp workaround for EntityManager injection with lombok spring-attic/spring-native#1597 * will get this corrected in master branch * Merge branch 'master' into boot3-native # Conflicts: # src/main/java/gt/app/domain/AppUser.java * graalvm 22.3+ is required * h2 required at compile time for graalvm native compile * native image WIP * now it works ! * now it works ! * spring-attic/spring-native#1597 is resolved in spring boot 3 * spring-projects/spring-data-jpa#2681 is fixed * spring-projects/spring-data-jpa#2681 is fixed * spring-projects/spring-data-jpa#2681 is fixed
1 parent 532a955 commit 16a9543

File tree

13 files changed

+131
-65
lines changed

13 files changed

+131
-65
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,26 @@ Future: do more stuff
125125
- Liquibase/Flyway change log
126126
- Integrate Markdown editor for writing notes
127127

128+
### Dependency/plugin version checker
129+
130+
`./mvnw versions:display-dependency-updates`
131+
`./mvnw versions:display-plugin-updates`
132+
133+
## Generate native executable:
134+
- Required: GraalVM 22.3+ (for Spring Boot 3)
135+
- Install using sdkman
136+
`sdk install java 22.3.r17.ea-nik`
137+
`sdk use java 22.3.r17.ea-nik`
138+
139+
- Create native executable `./mvnw native:compile -Pnative,dev`
140+
- Run it `./target/note-app`
141+
142+
OR
143+
144+
- Generate docker image with native executable `./mvnw spring-boot:build-image -Pnative,dev`
145+
- Run it `docker run --rm -p 8080:8080 docker.io/library/note-app:3.0.0-RC1`
146+
147+
148+
## Native Test:
149+
- Run with `./mvnw test -PnativeTest`
150+
- Spring Boot 3.0.0: native-test is not working due to spock ( and possibly other dependencies too)

pom.xml

+32-41
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>3.0.0-RC1</version>
9+
<version>3.0.0</version>
1010
<relativePath/> <!-- lookup parent from repository -->
1111
</parent>
1212

@@ -16,9 +16,7 @@
1616
<properties>
1717
<java.version>17</java.version>
1818

19-
<!-- Spring6 not supported yet https://github.com/springdoc/springdoc-openapi/issues/1284
20-
Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest-->
21-
<springdoc-openapi-ui.version>2.0.0-M7</springdoc-openapi-ui.version>
19+
<springdoc-openapi-ui.version>2.0.0-RC2</springdoc-openapi-ui.version>
2220
<mapstruct.version>1.5.3.Final</mapstruct.version>
2321

2422
<selenide.version>6.9.0</selenide.version>
@@ -269,6 +267,28 @@
269267
</excludes>
270268
</configuration>
271269
</plugin>
270+
<plugin>
271+
<groupId>org.graalvm.buildtools</groupId>
272+
<artifactId>native-maven-plugin</artifactId>
273+
</plugin>
274+
<plugin>
275+
<groupId>org.hibernate.orm.tooling</groupId>
276+
<artifactId>hibernate-enhance-maven-plugin</artifactId>
277+
<version>${hibernate.version}</version>
278+
<executions>
279+
<execution>
280+
<id>enhance</id>
281+
<goals>
282+
<goal>enhance</goal>
283+
</goals>
284+
<configuration>
285+
<enableLazyInitialization>true</enableLazyInitialization>
286+
<enableDirtyTracking>true</enableDirtyTracking>
287+
<enableAssociationManagement>true</enableAssociationManagement>
288+
</configuration>
289+
</execution>
290+
</executions>
291+
</plugin>
272292

273293
<plugin>
274294
<groupId>org.apache.maven.plugins</groupId>
@@ -438,6 +458,10 @@
438458
</executions>
439459
<configuration>
440460
<javaVersion>${java.version}</javaVersion>
461+
<ignoreGeneratedClasses>true</ignoreGeneratedClasses>
462+
<ignoreClassNamePatterns>
463+
<classNamePattern>.*SpringCGLIB.*</classNamePattern>
464+
</ignoreClassNamePatterns>
441465
</configuration>
442466
</plugin>
443467

@@ -557,7 +581,10 @@
557581
<dependency>
558582
<groupId>com.h2database</groupId>
559583
<artifactId>h2</artifactId>
560-
<scope>runtime</scope>
584+
<!-- GraalVMNativeImage: required at compile time for graalvm native compile -->
585+
<!--target/spring-aot/main/sources/org/springframework/boot/autoconfigure/h2/H2ConsoleAutoConfiguration__BeanDefinitions.java:4:25
586+
java: package org.h2.server.web does not exist-->
587+
<!--<scope>runtime</scope>-->
561588
</dependency>
562589
</dependencies>
563590
<properties>
@@ -596,41 +623,5 @@
596623

597624
</profiles>
598625

599-
<repositories>
600-
<repository>
601-
<id>spring-milestones</id>
602-
<name>Spring Milestones</name>
603-
<url>https://repo.spring.io/milestone</url>
604-
<snapshots>
605-
<enabled>false</enabled>
606-
</snapshots>
607-
</repository>
608-
<repository>
609-
<id>spring-snapshots</id>
610-
<name>Spring Snapshots</name>
611-
<url>https://repo.spring.io/snapshot</url>
612-
<releases>
613-
<enabled>false</enabled>
614-
</releases>
615-
</repository>
616-
</repositories>
617-
<pluginRepositories>
618-
<pluginRepository>
619-
<id>spring-milestones</id>
620-
<name>Spring Milestones</name>
621-
<url>https://repo.spring.io/milestone</url>
622-
<snapshots>
623-
<enabled>false</enabled>
624-
</snapshots>
625-
</pluginRepository>
626-
<pluginRepository>
627-
<id>spring-snapshots</id>
628-
<name>Spring Snapshots</name>
629-
<url>https://repo.spring.io/snapshot</url>
630-
<releases>
631-
<enabled>false</enabled>
632-
</releases>
633-
</pluginRepository>
634-
</pluginRepositories>
635626

636627
</project>

spot-bugs.filter-exclude.xml

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
<Bug pattern="SPRING_CSRF_PROTECTION_DISABLED"/>
77
<Bug pattern="EI_EXPOSE_REP2"/>
88
<Bug pattern="EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS"/>
9+
<Class name="~.*.*SpringCGLIB.*.*" />
910
</FindBugsFilter>

src/main/java/gt/app/Application.java

+41
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,25 @@
22

33
import gt.app.config.AppProperties;
44
import gt.app.config.Constants;
5+
import gt.app.config.security.AppUserDetails;
6+
import gt.app.modules.email.dto.EmailDto;
7+
import gt.app.modules.note.dto.NoteCreateDto;
8+
import gt.app.modules.note.dto.NoteEditDto;
9+
import gt.app.modules.note.dto.NoteReadDto;
10+
import gt.app.modules.user.AppPermissionEvaluatorService;
11+
import gt.app.modules.user.dto.PasswordUpdateDTO;
12+
import gt.app.modules.user.dto.UserDTO;
513
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.aot.hint.MemberCategory;
15+
import org.springframework.aot.hint.RuntimeHints;
16+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
617
import org.springframework.boot.SpringApplication;
718
import org.springframework.boot.autoconfigure.SpringBootApplication;
819
import org.springframework.boot.context.properties.EnableConfigurationProperties;
20+
import org.springframework.context.annotation.ImportRuntimeHints;
921
import org.springframework.core.env.Environment;
22+
import org.springframework.data.domain.PageImpl;
23+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1024
import org.springframework.transaction.annotation.EnableTransactionManagement;
1125

1226
import java.net.InetAddress;
@@ -18,6 +32,7 @@
1832
@Slf4j
1933
@EnableConfigurationProperties(AppProperties.class)
2034
@EnableTransactionManagement(proxyTargetClass = true)
35+
@ImportRuntimeHints(MyRuntimeHints.class) //required for GraalVMNativeImage::
2136
public class Application {
2237

2338
public static void main(String[] args) throws UnknownHostException {
@@ -41,3 +56,29 @@ public static void main(String[] args) throws UnknownHostException {
4156
}
4257

4358
}
59+
60+
//required for GraalVMNativeImage::
61+
class MyRuntimeHints implements RuntimeHintsRegistrar {
62+
@Override
63+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
64+
//record and dto classes -> get/set not found
65+
hints
66+
.reflection()
67+
.registerType(AppProperties.class, MemberCategory.values())
68+
.registerType(AppProperties.FileStorage.class, MemberCategory.values())
69+
.registerType(EmailDto.class, MemberCategory.values())
70+
.registerType(EmailDto.FileBArray.class, MemberCategory.values())
71+
.registerType(PasswordUpdateDTO.class, MemberCategory.values())
72+
.registerType(UserDTO.class, MemberCategory.values())
73+
.registerType(NoteCreateDto.class, MemberCategory.values())
74+
.registerType(NoteEditDto.class, MemberCategory.values())
75+
.registerType(NoteReadDto.class, MemberCategory.values())
76+
.registerType(NoteReadDto.FileInfo.class, MemberCategory.values())
77+
.registerType(AppUserDetails.class, MemberCategory.values())
78+
.registerType(UsernamePasswordAuthenticationToken.class, MemberCategory.values())
79+
.registerType(AppPermissionEvaluatorService.class, MemberCategory.values())
80+
.registerType(PageImpl.class, MemberCategory.values()); //EL1004E: Method call: Method getTotalElements() cannot be found on type org.springframework.data.domain.PageImpl
81+
82+
83+
}
84+
}

src/main/java/gt/app/config/AuditorResolver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import gt.app.config.security.SecurityUtils;
44
import gt.app.domain.LiteUser;
5+
import jakarta.persistence.EntityManager;
56
import lombok.RequiredArgsConstructor;
67
import org.springframework.data.domain.AuditorAware;
78
import org.springframework.stereotype.Component;
89

9-
import jakarta.persistence.EntityManager;
1010
import java.util.Optional;
1111

1212
@Component

src/main/java/gt/app/config/security/SecurityConfig.java

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class SecurityConfig{
2121
"/h2-console/**",
2222
"/webjars/**",
2323
"/static/**",
24+
"/error/**",
2425
"/swagger-ui/**",
2526
"/swagger-ui.html/**",
2627
"/signup/**",

src/main/java/gt/app/domain/BaseAuditingEntity.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import lombok.Getter;
55
import lombok.Setter;
6-
import org.hibernate.annotations.CreationTimestamp;
7-
import org.hibernate.annotations.UpdateTimestamp;
86
import org.springframework.data.annotation.CreatedBy;
7+
import org.springframework.data.annotation.CreatedDate;
98
import org.springframework.data.annotation.LastModifiedBy;
9+
import org.springframework.data.annotation.LastModifiedDate;
1010
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
1111

1212
import jakarta.persistence.*;
@@ -26,7 +26,7 @@ abstract class BaseAuditingEntity extends BaseEntity {
2626
@JsonIgnore//ignore completely to avoid StackOverflow exception by User.createdByUser logic, use DTO
2727
private LiteUser createdByUser;
2828

29-
@CreationTimestamp
29+
@CreatedDate
3030
@Column(name = "created_date", nullable = false)
3131
private Instant createdDate;
3232

@@ -36,7 +36,7 @@ abstract class BaseAuditingEntity extends BaseEntity {
3636
@JsonIgnore//ignore completely to avoid StackOverflow exception by User.lastModifiedByUser logic, use DTO
3737
private LiteUser lastModifiedByUser;
3838

39-
@UpdateTimestamp
39+
@LastModifiedDate
4040
@Column(name = "last_modified_date")
4141
private Instant lastModifiedDate;
4242
}

src/main/java/gt/app/modules/note/NoteService.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class NoteService {
2525
private final NoteRepository noteRepository;
2626
private final FileService fileService;
2727

28+
private final NoteMapper noteMapper;
29+
2830
public Note createNote(NoteCreateDto dto) {
2931

3032
List<ReceivedFile> files = new ArrayList<>();
@@ -38,7 +40,7 @@ public Note createNote(NoteCreateDto dto) {
3840
files.add(new ReceivedFile(FILE_GROUP, mpf.getOriginalFilename(), fileId));
3941
}
4042

41-
Note note = NoteMapper.INSTANCE.createToEntity(dto);
43+
Note note = noteMapper.createToEntity(dto);
4244
note.getAttachedFiles().addAll(files);
4345

4446
return save(note);
@@ -48,16 +50,14 @@ public Note update(NoteEditDto dto) {
4850

4951
Optional<Note> noteOpt = noteRepository.findById(dto.id());
5052
return noteOpt.map(note -> {
51-
NoteMapper.INSTANCE.createToEntity(dto, note);
52-
return save(note);
53-
}
54-
).orElseThrow();
53+
noteMapper.createToEntity(dto, note);
54+
return save(note);
55+
}).orElseThrow();
5556
}
5657

5758
public NoteReadDto read(Long id) {
5859
return noteRepository.findById(id)
59-
.map(NoteMapper.INSTANCE::mapForRead)
60-
.orElseThrow();
60+
.map(noteMapper::mapForRead).orElseThrow();
6161
}
6262

6363
public Note save(Note note) {
@@ -66,12 +66,12 @@ public Note save(Note note) {
6666

6767
public Page<NoteReadDto> readAll(Pageable pageable) {
6868
return noteRepository.findAll(pageable)
69-
.map(NoteMapper.INSTANCE::mapForRead);
69+
.map(noteMapper::mapForRead);
7070
}
7171

7272
public Page<NoteReadDto> readAllByUser(Pageable pageable, Long userId) {
7373
return noteRepository.findByCreatedByUserIdOrderByCreatedDateDesc(pageable, userId)
74-
.map(NoteMapper.INSTANCE::mapForRead);
74+
.map(noteMapper::mapForRead);
7575
}
7676

7777
public void delete(Long id) {

src/main/java/gt/app/modules/note/dto/NoteMapper.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
import org.mapstruct.Mapper;
66
import org.mapstruct.Mapping;
77
import org.mapstruct.MappingTarget;
8-
import org.mapstruct.factory.Mappers;
98

10-
@Mapper
9+
@Mapper(componentModel = "spring")
1110
public interface NoteMapper {
1211

13-
NoteMapper INSTANCE = Mappers.getMapper(NoteMapper.class);
14-
1512
@Mapping(source = "createdByUser.id", target = "userId")
1613
@Mapping(source = "createdByUser.uniqueId", target = "username")
1714
@Mapping(source = "attachedFiles", target = "files")

src/main/java/gt/app/modules/note/dto/NoteReadDto.java

+11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77
public record NoteReadDto(Long id, String title, String content, Long userId, String username, Instant createdDate,
88
List<FileInfo> files) {
9+
10+
////required for GraalVMNativeImage::
11+
//SpelEvaluationException: EL1004E: Method call: Method size() cannot be found on type java.util.ArrayList
12+
//Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "note.files.size()>0" (template: "note/_notes" - line 43, col 18)
13+
public int getFileSize() {
14+
if (files == null) {
15+
return 0;
16+
}
17+
return files.size();
18+
}
19+
920
public record FileInfo(UUID id, String name) {
1021
}
1122
}

src/main/java/gt/app/modules/user/dto/UserMapper.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33
import gt.app.domain.AppUser;
44
import org.mapstruct.Mapper;
55
import org.mapstruct.Mapping;
6-
import org.mapstruct.factory.Mappers;
76
import org.springframework.security.core.GrantedAuthority;
87

98
import java.util.Collection;
109
import java.util.List;
1110
import java.util.stream.Collectors;
1211

13-
@Mapper
12+
@Mapper(componentModel = "spring")
1413
public interface UserMapper {
1514

16-
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
17-
1815
@Mapping(source = "uniqueId", target = "login")
1916
UserDTO userToUserDto(AppUser user);
2017

src/main/resources/application.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ spring:
1717
username:
1818
password:
1919
main:
20-
lazy-initialization: true
20+
lazy-initialization: false
2121

2222
server:
2323
port: 8080
@@ -42,3 +42,7 @@ wro4j:
4242
app-properties:
4343
file-storage:
4444
upload-folder: ${java.io.tmpdir}
45+
46+
47+
springdoc:
48+
enable-native-support: true

src/main/resources/templates/note/_notes.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ <h5 class="card-title"><span th:text="${note.title}"></span></h5>
4040

4141
<div class="card-body">
4242
<p class="card-text"><span th:text="${note.content}"></span></p>
43-
<div th:if="${note.files.size()>0}">
43+
<div th:if="${note.getFileSize()>0}">
4444
<hr/>
4545
Attachments:
4646
<span th:each="file : ${note.files}">

0 commit comments

Comments
 (0)