JDK를 최소 17부터 19까지 지원함.
String textBlock = """
spring boot 3
is
awesome
!!
""";
""" some paragraph """
삼중 따옴표로 멀티라인 문자열을 사용할 수 있음.DayOfWeek day = DayOfWeek.FRIDAY;
// before jdk17
int numOfLettersOld;
if (day == MONDAY || day == FRIDAY || day == SUNDAY) {
numOfLettersOld = 6;
} else if (day == TUESDAY) {
numOfLettersOld = 7;
} else if (day == THURSDAY || day == SATURDAY) {
numOfLettersOld = 8;
} else if (day == WEDNESDAY) {
numOfLettersOld = 9;
}
// jdk 17 switch-case-construct
int numOfLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
nested if-else-operator를 사용하는 대신 switch-case-construct를 통해 깔끔한 변수 할당이 가능해짐.
// before jdk 17
if (vehicle instanceof Car) {
return ((Car) vehicle).getNumberOfSeats();
} else if (vehicle instanceof Truck) {
return ((Truck) vehicle).getLoadCapacity();
}
// jdk 17 pattern matching
if (vehicle instanceof Car car) {
return car.getNumberOfSeats();
} else if (vehicle instanceof Truck truck) {
return truck.getLoadCapacity();
}
// switch-case에서의 pattern matching
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0d;
};
}
instaceof 나 switch-case문에서 데이터형과 함께 변수를 선언할 수 있음.
public record Person (String name, String address) {
private static int a; // static field
public Person { // 생성자 null 체크
Objects.requirenonull(name);
}
public static Person empty() { // static 메소드
return new Person("", "");
}
}
// 주의
public record Person (@NotBlank String name, @NonNull String address) {}
record
을 사용할 수 있다.https://www.baeldung.com/java-sealed-classes-interfaces
// sealed interfaces
public sealed interface Service permits Car, Truck {}
// sealed classes
public abstract sealed class Vehicle permits Car, Truck {}
// 확장에 닫혀있도록 final으로 선언
public final class Truck extends Vehicle implements Service {}
// 확장할 수 있도록 non-sealed로 선언
public non-sealed class Car extends Vehicle implements Service {}
sealed를 통해 class와 interface의 subtype를 정의(제한)할 수 있다.
public sealed interface Vehicle permits Car, Truck {
String getRegistrationNumber();
}
public record Car(int numberOfSeats, String registrationNumber) implements Vehicle {
@Override
public String getRegistrationNumber() {
return registrationNumber;
}
}
public record Truck(int loadCapacity, String registrationNumber) implements Vehicle {
@Override
public String getRegistrationNumber() {
return registrationNumber;
}
}
sealed class와 record와 함께 사용하는 예시
Java EE에서 Jarkarta EE9으로 대체됨에 따라 package namespace도 javax.에서 jakarta.으로 변경되었다. 따라서 기존 import 코드 뿐만아니라 java/jakarta EE을 사용하는 third party library의 java to jakarta 마이그레이션 여부를 체크해야 한다.
Java EE(Enterprise Edition)은 과거 sun microsystems가 분산 어플리케이션 개발을 위해 내세운 산업 표준이였다.
sun-oracle 합병 이후 Java EE는 비영리 단체 eclipse에 이관되었고 2019년도에 Java EE과 호환되는 Jakarta EE를 출시하게 되었다. java는 oracle이 상표권을 가지고 있었기 때문에 상표권 이슈로 api 네임스페이스까지 javax에서 jakarta로 변경하게 되었다.
삼성 sds의 jakarta 마이그레이션 과정
https://spring.io/blog/2022/10/12/observability-with-spring-boot-3
Micrometer Observation API가 auto configuration을 통해 자동으로 구성되며, Observability의 공식 지원이 시작된다.
micrometer 및 micrometer tracing 기반의 spring observability가 도입되어, metric 정보를 기록하고 zipkin이나 telemetry를 통해 tracing할 수 있게 해준다.
spring boot 2.x에서 spring cloud sleuth을 통해 tracing이 가능했으나 3.x부터 sleuth 지원이 끊겨 micrometer로 마이그레이션이 필요하다.
서비스 인터페이스 선언만으로 Http Access가 가능한 Http Interface Client 가 추가되었다.
RestTemplate 또는 WebClient 와 같은 클래스로 직접 구현하지 않더라도, 인터페이스만 선언하면 API 호출이 가능해진다.
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
@Bean
ApplicationRunner init(ErApi erApi) {
return args -> {
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
Map<String, Map<String, Double>> res = restTemplate.getForObject("https://open.er-api.com/v6/latest", Map.class);
System.out.println(res.get("rates").get("KRW"));
// WebClient
WebClient client = WebClient.create("https://open.er-api.com");
Map<String, Map<String, Double>> res2 = client.get().uri("/v6/latest").retrieve().bodyToMono(Map.class).block();
System.out.println(res2.get("rates").get("KRW"));
};
}
}
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
@Bean
ApplicationRunner init(ErApi erApi) {
return args -> {
// HTTP interface
Map<String, Map<String, Double>> res3 = erApi.getLatest();
System.out.println(res3.get("rates").get("KRW"));
};
}
// url 마다 http interface bean 등록하는게 번거로울 수 있음. (추후에 jpa 같이 자동으로 설정해주는 라이브러리가 나올 수 있음)
@Bean
ErApi erApi() {
WebClient client = WebClient.create("https://open.er-api.com");
HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(client))
.build();
return httpServiceProxyFactory.createClient(ErApi.class);
}
interface ErApi {
@GetExchange("/v6/latest")
Map getLatest();
}
}
spring framework 버전 업그레이드
이에 따라 여러 spring project의 버전도 업데이트 됨
spring framework | spring-batch-core |
---|---|
2.7.6 | 4.3.7 (https://docs.spring.io/spring-batch/docs/4.3.7/reference/html/) |
3.0.9 | 5.0.2 (https://docs.spring.io/spring-batch/docs/5.0.2/reference/html/) |
4.3.7 vs 5.0.2
// 4.3.7 (https://docs.spring.io/spring-batch/docs/4.3.7/reference/html/job.html#configuringAJob)
@Bean
public Job footballJob() {
return this.jobBuilderFactory.get("footballJob")
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
// 5.0.2 (https://docs.spring.io/spring-batch/docs/5.0.2/reference/html/job.html#configuringAJob)
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
버전별 job 구성 차이점
this.jobBuilderFactory.get("footballJob")
new JobBuilder("footballJob", jobRepository)
JobRepository이란
버전별 JobRepository의 configuration 구성방식
자세한 JobRepository 구성 방식 차이를 이해하기 위해서 버전별 spring batch configuration 차이를 알아야 함
두 버전 모두 @EnableBatchProcessing 어노테이션을 달아주면 jobRepository을 포함한 spring batch 관련 bean을 등록해줌.
@EnableBatchProcessing 을 통해 등록되는 bean들
JobRepository
: bean name "jobRepository"JobLauncher
: bean name "jobLauncher"JobRegistry
: bean name "jobRegistry"PlatformTransactionManager
: bean name "transactionManager"JobBuilderFactory
: bean name "jobBuilders"StepBuilderFactory
: bean name "stepBuilders"@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
@Override
public PlatformTransactionManager getTransactionManager() {
return new MyTransactionManager();
}
};
}
위처럼 DefaultBatchConfigurer을 상속받아서 등록할 bean에 대한 customize이 가능했음.
(spring-batch-core 5부터 BatchConfigurer 클래스가 제거됨)
@EnableBatchProcessing 을 통해 등록되는 bean들
JobRepository
: a bean named jobRepository
JobLauncher
: a bean named jobLauncher
JobRegistry
: a bean named jobRegistry
JobExplorer
: a bean named jobExplorer
JobOperator
: a bean named jobOperator
4버전과 비교했을 때 등록되는 bean 중 PlatformTransactionManager, JobBuilderFactory, StepBuilderFactory 이 사라짐.
JobBuilderFactory
, StepBuilderFactory
클래스가 deprecated)@Configuration
@EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager") // <- just like this!
public class MyJobConfiguration {
@Bean
public DataSource batchDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
.generateUniqueName(true).build();
}
@Bean
public JdbcTransactionManager batchTransactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
public Job job(JobRepository jobRepository) {
return new JobBuilder("myJob", jobRepository)
//define job flow as needed
.build();
}
}
dataSource와 transactionManager의 bean을 직접 등록하여 bean name을 @EnableBatchProcessing에 지정해주도록 변경됨.
JobBuilderFactory
, StepBuilderFactory
클래스가 deprecated되고
JobRepository
와JobBuilder
, StepBuilder
로 job, step의 bean을 등록하도록 권장함.
// 4.3.7 (https://docs.spring.io/spring-batch/docs/4.3.7/reference/html/job.html#configuringAJob)
@Bean
public Job footballJob() {
return this.jobBuilderFactory.get("footballJob")
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
// 5.0.2 (https://docs.spring.io/spring-batch/docs/5.0.2/reference/html/job.html#configuringAJob)
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
4버전에는 spring.batch.job.names
프로퍼티에 job의 bean name을 ,
으로 구분지어 전달하면 multiple job을 실행할 수 있었다.
5버전부터 spring.batch.job.names
대신 spring.batch.job.name
프로퍼티에 job bean name 하나만 지정하도록 변경되었다. 즉, multiple job 실행 기능이 삭제된것이다…!
# 4.3.7
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \
+schedule.date(date)=2007/05/05 -vendor.id=123
# 5.0.2
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \
schedule.date=2007-05-05,java.time.LocalDate,true \
vendor.id=123,java.lang.Long,false
parameter(type)=value
→ parameter=value,type,identifying
형식으로 job parameter를 전달 (identifying은 해당 매개변수에 대한 식별여부를 나타냄)spring batch 에서 전달 받은 파라미터(parameter=value,type,identifying
)를 JobParameter 객체로 매핑하기 위해DefaultJobParametersConverter
을 기본으로 사용한다.
/**
* Decode a job parameter from a string.
* @param encodedJobParameter the encoded job parameter
* @return the decoded job parameter
*/
protected JobParameter<?> decode(String encodedJobParameter) {
String parameterStringValue = parseValue(encodedJobParameter);
Class<?> parameterType = parseType(encodedJobParameter);
boolean parameterIdentifying = parseIdentifying(encodedJobParameter);
try {
Object typedValue = this.conversionService.convert(parameterStringValue, parameterType);
return new JobParameter(typedValue, parameterType, parameterIdentifying);
}
catch (Exception e) {
throw new JobParametersConversionException(
"Unable to convert job parameter " + parameterStringValue + " to type " + parameterType, e);
}
}
private String parseValue(String encodedJobParameter) {
return StringUtils.commaDelimitedListToStringArray(encodedJobParameter)[0];
}
private Class<?> parseType(String encodedJobParameter) {
String[] tokens = StringUtils.commaDelimitedListToStringArray(encodedJobParameter);
if (tokens.length <= 1) {
return String.class;
}
try {
Class<?> type = Class.forName(tokens[1]);
return type;
}
catch (ClassNotFoundException e) {
throw new JobParametersConversionException("Unable to parse job parameter " + encodedJobParameter, e);
}
}
private boolean parseIdentifying(String encodedJobParameter) {
String[] tokens = StringUtils.commaDelimitedListToStringArray(encodedJobParameter);
if (tokens.length <= 2) {
return true;
}
return Boolean.valueOf(tokens[2]);
}
/**
* Convert a comma delimited list (e.g., a row from a CSV file) into an
* array of strings.
* @param str the input {@code String} (potentially {@code null} or empty)
* @return an array of strings, or the empty array in case of empty input
*/
public static String[] commaDelimitedListToStringArray(@Nullable String str) {
return delimitedListToStringArray(str, ",");
}
DefaultJobParametersConverter#decode
는 commandline에서 전달받은 파라미터를 JobParameter 객체로 디코딩한다. 이 때 파라미터의 메타데이터를 파싱하기 위해 StringUtils.commaDelimitedListToStringArray
메소드를 사용하는데, 구분자를 ,
로 잡고 있다.
만약 parameter value에
,
가 포함되어 있다면?
parameter=value,type,identifying
형태로 파라미터를 전달할 경우 value
에 ,
가 포함되어있다면 JobParameter로 디코딩하는 과정에서 에러가 발생한다.
이를 해결하기 위해서 JobParameterConverter를 DefaultJobParametersConverter
대신 JsonJobParametersConverter
으로 설정해야 한다
JsonJobParameterConverter을 사용할 경우,
parameterName='{"value": "parameterValue", "type":"parameterType", "identifying": "booleanValue"}'
형태로 파라미터를 전달해야 한다.
@Override
protected JobParameter decode(String encodedJobParameter) {
try {
JobParameterDefinition jobParameterDefinition = this.objectMapper.readValue(encodedJobParameter,
JobParameterDefinition.class);
Class<?> parameterType = String.class;
if (jobParameterDefinition.type() != null) {
parameterType = Class.forName(jobParameterDefinition.type());
}
boolean parameterIdentifying = true;
if (jobParameterDefinition.identifying() != null && !jobParameterDefinition.identifying().isEmpty()) {
parameterIdentifying = Boolean.valueOf(jobParameterDefinition.identifying());
}
Object parameterTypedValue = this.conversionService.convert(jobParameterDefinition.value(), parameterType);
return new JobParameter(parameterTypedValue, parameterType, parameterIdentifying);
}
catch (JsonProcessingException | ClassNotFoundException e) {
throw new JobParametersConversionException("Unable to decode job parameter " + encodedJobParameter, e);
}
}
public record JobParameterDefinition(String value, String type, String identifying) {
}
파라미터를 objectMapper을 통해 JobParameter로 디코딩한다.
commandline에서 job parameter의 전달 방식
parameterName='{"value": "parameterValue", "type":"parameterType", "identifying": "booleanValue"}'
- https://docs.spring.io/spring-batch/docs/current/reference/html/whatsnew.html#extended-notation
공식문서에 나온 방식처럼 JsonJobParametersConverter에 파라미터를 전달하면 제대로 동작하지 않는다. (https://github.com/spring-projects/spring-batch/issues/4299)
"
에 escape(\
) 처리가 필요하며 파라미터의 value에서 json key와 value 사이에 공백문자가 있어선 안된다.param1="{\"key\":\"value\"}"
'
없이 json object를 전달하고 json key와 value 사이에 공백문자가 있어선 안된다param1={"key":"value"}
spring framework | spring-security-core |
---|---|
2.7.6 | 5.7.5 (https://docs.spring.io/spring-security/reference/5.7/index.html) |
3.0.9 | 6.0.5 (https://docs.spring.io/spring-security/reference/6.0/index.html) |
5.7 → 5.8 (https://docs.spring.io/spring-security/reference/5.8/whats-new.html#_core)
5.8 → 6.0 (https://docs.spring.io/spring-security/reference/6.0/whats-new.html#_core)
WebSecurityConfigurerAdapter
. Instead, create a SecurityFilterChain bean.antMatchers
, mvcMatchers
, regexMatchers
helper methods from Java Configuration. Instead, use requestMatchers
or HttpSecurity#securityMatchers
.**WebSecurityConfigurerAdapter가 삭제되므로서** spring-security configuration을 구성방식에 변화가 생겼다.
해당 내용은 https://www.baeldung.com/spring-deprecated-websecurityconfigureradapter 에 자세히 나와있다. 단순한 구성 방식 변경 내용뿐이라 다루지 않겠다.
좋은 정보 얻어갑니다, 감사합니다.