This is the multi-page printable view of this section. Click here to print.
Documentation
1 - Mapper
<dependency>
<groupId>tw.com.softleader.data.jakarta</groupId>
<artifactId>specification-mapper</artifactId>
<version>${specification-mapper.version}</version>
</dependency>
specification-mapper 是一套 Specifications 的產生器, 它讀取了 Object 中的欄位, 配合欄位上 Annotation 的定義, 來動態的建立查詢條件!
另外 specification-mapper-starter 提供了 Spring Boot 的整合, 讓你可以零配置的在 Spring apps 中使用, 使用 Spring boot 的應用程式可以參考看看!
Getting Started
我們需要的是建立 SpecMapper
實例, 這是最重要的 Class 也是所有 Spec 操作的 API 入口:
var mapepr = SpecMapper.builder().build();
接著我們定義封裝查詢條件的物件, 這是一個 POJO 即可, 如:
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
}
這樣我們就可以做 Specification
的轉換了, 得到 Specification
後就可以依照原本的方式去資料庫查詢, 例如透過 Spring Data JPA 的 repository:
var criteria = new CustomerCriteria();
criteria.setFirstname("Hello")
var mapper = SpecMapper.builder().build();
var specification = mapper.toSpec(criteria);
customerRepository.findAll(specification);
執行 SQL 將會是:
... where x.firstname like '%Hello%'
Skipping Strategy
在 POJO 中的欄位, 只要符合以下任一條件, 在轉換的過程中都將會忽略:
- 沒有掛任何 Spec Annotation
- 值為 null
- 若 Type 為 Iterable 且值為 empty
- 若 Type 為 Optional 且值為 empty
- 若 Type 為 CharSequence 且長度為 0
- 若 Type 為 Array 且長度為 0
- 若 Type 為 Map 且值為 empty
例如, 將以下 POJO 建構後, 不 set 任何值就直接轉換成 Specification
及查詢
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
String lastname = "Hello";
@Spec
String nickname = "";
@Spec(GreaterThat.class)
Optional<Integer> age = Optional.empty();
@Spec(In.class)
Collection<String> addresses = Arrays.asList();
}
var mapper = SpecMapper.builder().build();
customerRepository.findAll(mapper.toSpec(new CustomerCriteria()));
以上執行的 SQL 將不會有任何過濾條件!
如果你有使用 Builder Pattern, (e.g. Lombok’s @Builder), 請特別注意 Builder 的 Default Value!
若你想要客製化跳脫邏輯, 可以實作 SkippingStrategy
, 並在建構 SpecMapper
時傳入:
var mapper = SpecMapper.builder()
.defaultResolvers()
.skippingStrategy(fieldValue -> {
// 判斷是否要跳 field value, 回傳 boolean
})
.build();
Simple Specifications
你可以在 Field 使用 @Spec
來定義 Specification
的實作, 預設是 Equals
:
@Spec // 同等於 @Spec(Equals.class)
String firstname;
對應的 entity path 預設會使用 field name, 你也可以設定 @Spec#path
來改變
@Spec(path = "...") // 有定義則優先使用
String firstname; // 預設使用欄位名稱
Built-in Simple @Spec
以下是內建的 @Spec
的類型清單:
Spec | Supported field type | Sample | JPQL snippet |
---|---|---|---|
Equals | Any | @Spec(Equals.class) String firstname; | ... where x.firstname = ? |
NotEquals | Any | @Spec(NotEquals.class) String firstname; | ... where x.firstname <> ? |
Between | Iterable of Comparable (Expected exact 2 elements in Iterable) | @Spec(Between.class) List<Integer> age; | ... where x.age between ? and ? |
LessThan | Comparable | @Spec(LessThan.class) Integer age; | ... where x.age < ? |
LessThanEqual | Comparable | @Spec(LessThanEqual.class) Integer age; | ... where x.age <= ? |
GreaterThan | Comparable | @Spec(GreaterThan.class) Integer age; | ... where x.age > ? |
GreaterThanEqual | Comparable | @Spec(GreaterThanEqual.class) Integer age; | ... where x.age >= ? |
After | Comparable | @Spec(After.class) LocalDate startDate; | ... where x.startDate > ? |
Before | Comparable | @Spec(Before.class) LocalDate startDate; | ... where x.startDate < ? |
IsNull | Boolean | @Spec(IsNull.class) Boolean age; | ... where x.age is null (if true)... where x.age not null (if false) |
NotNull | Boolean | @Spec(NotNull .class) Boolean age; | ... where x.age not null (if true)... where x.age is null (if false) |
Like | String | @Spec(Like.class) String firstname; | ... where x.firstname like %?% |
NotLike | String | @Spec(NotLike.class) String firstname; | ... where x.firstname not like %?% |
StartingWith | String | @Spec(StartingWith.class) String firstname; | ... where x.firstname like ?% |
EndingWith | String | @Spec(EndingWith.class) String firstname; | ... where x.firstname like %? |
In | Iterable of Any | @Spec(In.class) Set<String> firstname; | ... where x.firstname in (?, ?, ...) |
NotIn | Iterable of Any | @Spec(NotIn.class) Set<String> firstname; | ... where x.firstname not in (?, ?, ...) |
True | Boolean | @Spec(True.class) Boolean active; | ... where x.active = true (if true)... where x.active = false (if false) |
False | Boolean | @Spec(False.class) Boolean active; | ... where x.active = false (if true)... where x.active = true (if false) |
HasLength | Boolean | @Spec(HasLength.class) Boolean firstname; | ... where x.firstname is not null and character_length(x.firstname)>0 (if true)... where not(x.firstname is not null and character_length(x.firstname)>0) (if false) |
HasText | Boolean | @Spec(HasText.class) Boolean firstname; | ... where x.firstname is not null and character_length(trim(BOTH from x.firstname))>0 (if true)... where not(where x.firstname is not null and character_length(trim(BOTH from x.firstname))>0) (if false) |
為了方便已經熟悉 Spring Data JPA 的人使用, 以上名稱都是儘量跟著 Query Methods 一樣
Negates the @Spec
你可以使用 @Spec#not
來判斷反向條件, 預設是 false
, 設定成 true
就會將結果做反向轉換.
例如, 我想要用 Between
找不在區間內的資料, 則範例如下:
@Spec(value = Between.class, not = true)
Collection<Integer> age;
執行的 SQL 將會是:
... where x.age not between ? and ?
Extending Simple @Spec
@Spec 是可以很容易擴充的, 只要實作了 SimpleSpecification<T>
並提供規定的 Constructor, 這些 class 就可以被定義在 @Spec 中
例如, 我有個 Customer Entity, 有以下欄位:
- firstname (String) - 人名, 可重複
- createdTime (LocalDateTime) - 建立時間, 不可重複
我希望可以找出每個人名中, 建立時間為最新的那筆資料! 且我打算撰寫一個 subquery 來完成這需求, 完整的範例如下:
首先我們實作 SimpleSpecification<T>
, 並提供規定的 Constructor:
public class MaxCustomerCreatedTime extends SimpleSpecification<Customer> {
// 這是規定必須提供的建構值, 修飾詞不限定: public, protected, default, private 都支援
protected MaxCustomerCreatedTime(Context context, String path, Object value) {
super(context, path, value);
}
@Override
public Predicate toPredicate(Root<Customer> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
// 以下提供 subquery 的實作, 僅供參考
var subquery = query.subquery(LocalDateTime.class);
var subroot = subquery.from(Customer.class);
subquery.select(
builder.greatest(subroot.get("createdTime").as(LocalDateTime.class))
).where(builder.equal(root.get((String) value), subroot.get((String) value)));
return builder.equal(root.get("createdTime"), subquery);
}
}
上面完成的 MaxCustomerCreatedTime
就可以被應用在 @Spec 中了, 接著我們定義 POJO 及進行 Specification
的轉換:
@Data
public class CustomerCriteria {
@Spec(MaxCustomerCreatedTime.class)
String maxBy;
}
var criteria = new CustomerCriteria();
criteria.setMaxBy("firstname");
var spec = mapper.toSpec(criteria, Customer.class);
repository.findAll(spec);
執行的 SQL 將會是:
... where customer0_.created_time=(
select max(customer1_.created_time) from customer customer1_
where customer0_.firstname=customer1_.firstname
)
Combining Specs
你可以在 class 層級上使用 @And
或 @Or
來組合一個物件中的多個 Specification, 組合的預設是 @And
.
例如我想要改成 @Or
, 程式碼範例如下:
@Or // 若沒定義預設就是 @And
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
@Spec(Like.class)
String lastname;
}
執行的 SQL 將會是:
... where x.firstname like %?% or x.lastname like %?%
Specify Combining Type on Field
你也可以將 @And
或 @Or
註記在欄位上來控制單獨一個欄位要怎麼跟其他欄位組合. 舉個例子如下:
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
@Spec(Like.class)
String lastname;
@Or
@Spec(value = After.class, not = true)
LocalDate birthday;
}
執行的 SQL 將會是:
... where (x.firstname like ?) and (x.lastname like ?) or x.birthday<=?
特別注意, 欄位是依照宣告的順序組合的, 而 SQL 也是有運算子優先順序的, 需注意兩者的配合的結果是否符合你的期望
例如, 將上面的例子調整欄位順序:
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
@Or
@Spec(value = After.class, not = true)
LocalDate birthday;
@Spec(Like.class)
String lastname;
}
執行的 SQL 將會是:
... where (x.firstname like ? or x.birthday<=?) and (x.lastname like ?)
Nested Specs
你可以在 Field 上使用 @NestedSpec
來告知 SpecMapper
要往下一層物件 (Nested Object) 去組合 Specification, 這是沒有層級限制的, 可以一直往下找!
例如我有一個共用的 AddressCriteria
POJO, 我就可以將它掛載到其他的 POJO 中, 程式碼範例如下:
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
@NestedSpec
AddressCriteria address;
}
@Or
@Data
public class AddressCriteria {
@Spec
String county;
@Spec
String city;
}
執行的 SQL 將會是:
... where x.firstname like %?% and ( x.county=? or x.city=? )
Specify Combining Type on Nested Object
你也可以在 Nested Object 的欄位上宣告 @And
或 @Or
來控制結果要怎麼跟其他的欄位組合, 詳細的說明請參考 Specify Combining Type on Field.
舉個例子如下:
@Data
public class CustomerCriteria {
@Spec(Like.class)
String firstname;
@Or
@NestedSpec
AddressCriteria address;
}
@Data
public class AddressCriteria {
@Spec
String county;
@Spec
String city;
}
執行的 SQL 將會是:
... where (x.firstname like ?) or x.county=? and x.city=?
Join
你可以在 Field 上使用 @Join
來過濾關聯 entity, 這些 entity 之間需要都先定義好關係, 例如:
@Entity
class Customer {
@OneToMany(cascade = ALL, fetch = LAZY)
@JoinColumn(name = "order_id")
private Collection<Order> orders;
}
@Entity
class Order {
private String itemName;
}
如果你想要查詢買了指定東西的客戶, 則可以定義 POJO 如下:
@Data
public class CustomerOrderCriteria {
@Join(path = "orders", alias = "o")
@Spec(path = "o.itemName", value = In.class)
Collection<String> items;
}
執行的 SQL 將會是:
select distinc ... from customer customer0_
inner join orders orders1_ on customer0_.id=orders1_.order_id
where orders1_.item_name in (? , ?)
為了比較符合大部分的使用情境, 預設的 Join type 是 INNER
, 也會將結果排除重複 (distinct), 你可以設定 @Join#joinType
或 @Join#distinct
來改變, 如:
@Join(joinType = JoinType.RIGHT, distinct = false)
Multi Level Joins
你可以使用 @Joins
來定義多層級的 Join, 例如:
@Entity
class Customer {
@OneToMany(cascade = ALL, fetch = LAZY)
@JoinColumn(name = "order_id")
private Set<Order> orders;
}
@Entity
class Order {
@ManyToMany(cascade = ALL, fetch = LAZY)
private Set<Tag> tags;
}
@Entity
class Tag {
private String name;
}
如果你想要查詢買了指定所屬類別的東西的客戶, 則可以定義 POJO 如下:
@Data
class CustomerOrderTagCriteria {
@Joins({
@Join(path = "orders", alias = "o"),
@Join(path = "o.tags", alias = "t")
})
@Spec(path = "t.name", value = In.class)
Collection<String> tags;
}
執行的 SQL 將會是:
select distinct ... from customer customer0_
inner join orders orders1_ on customer0_.id=orders1_.order_id
inner join orders_tags tags2_ on orders1_.id=tags2_.order_id
inner join tag tag3_ on tags2_.tags_id=tag3_.id
where tag3_.name in (?)
特別注意, Annotation 的處理是有順序性的, 因此必須依照 Join 的順序去定義 @Joins
例如依照上面的情境, 下列的定義順序是錯誤的:
@Data
class CustomerOrderTagCriteria {
@Joins({
@Join(path = "o.tags", alias = "t"), // "o" alias will be not exist during processing this @Join
@Join(path = "orders", alias = "o")
})
@Spec(path = "t.name", value = In.class)
Collection<String> tagNames;
}
Join Fetch
你可以在 class 層級上使用 @JoinFetch
可以一次撈出所有 Lazy 的關聯資料, 例如:
@Entity
class Customer {
@OneToMany(fetch = LAZY, cascade = ALL)
@JoinColumn(name = "order_id")
private Collection<Order> orders;
}
@Entity
class Order {
private String itemName;
}
如果你想在取得 Customer 時就順便 Join 出 Order, 則:
@JoinFetch(paths = "orders")
@Data
class CustomerOrderCriteria {
@Spec
String name;
}
執行的 SQL 將會是:
select distinct
customer0_.* ...,
orders1_.* ...
from customer customer0_
inner outer join orders orders1_ on customer0_.id=orders1_.order_id
where customer0_.name=?
為了比較符合大部分的使用情境, 預設的 Join type 是 INNER
, 也會將結果排除重複 (distinct), 你可以設定 @FetchJoin#joinType
或 @FetchJoin#distinct
來改變, 如:
@FetchJoin(joinType = JoinType.RIGHT, distinct = false)
Multi Level Fetch Joins
你可以使用 @FetchJoins
來定義多層級的 Fetch Join, 例如:
@Entity
class Customer {
@OneToMany(cascade = ALL, fetch = LAZY)
@JoinColumn(name = "order_id")
private Set<Order> orders;
}
@Entity
class Order {
@ManyToMany(cascade = ALL, fetch = LAZY)
private Set<Tag> tags;
}
@Entity
class Tag {
private String name;
}
如果你想在取得 Customer 時就順便 Join 出 Order 及 Tag, 則:
@JoinFetches({
@JoinFetch(paths = "orders"),
@JoinFetch(paths = "orders.tags")
})
@Data
class CustomerOrderTagCriteria {
@Spec
String name;
}
執行的 SQL 將會是:
select distinct
customer0_.* ...,
orders1_.* ...,
tags3_.* ...
from customer customer0_
left outer join orders orders1_ on customer0_.id=orders1_.order_id
inner join orders orders2_ on customer0_.id=orders2_.order_id
left outer join orders_tags tags3_ on orders2_.id=tags3_.order_id
left outer join tag tag4_ on tags3_.tags_id=tag4_.id
where customer0_.name=?
Customize Spec Annotation
延續 Extending Simple @Spec 章節範例, 進階一點現在我們希望可以將 Entity Class 設計成可以配置, 這樣才能在 Customer 以外的 Entity 都可以使用!
要完成這需求我們需要在 Annotation 中定義更多參數, 因此 Simple @Spec 不適用了, 我們需要的是定義新的 Annotation, 完整的程式碼如下:
首先我們定義 Annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface MaxCreatedTime {
Class<?> from();
}
接著我們要撰寫負責處理 @MaxCreatedTime
的邏輯, 透過實作 SpecificationResolver
來擴充:
public class MaxCreatedTimeSpecificationResolver implements SpecificationResolver {
@Override
public boolean supports(Databind databind) {
// 這邊告訴 SpecMapper 什麼時候要使用此 resolver
return databind.getField().isAnnotationPresent(MaxCreatedTime.class);
}
@Override
public Specification<Object> buildSpecification(Context context, Databind databind) {
var def = databind.getField().getAnnotation(MaxCreatedTime.class);
return databind.getFieldValue()
.map(value -> subquery(def.from(), value.toString()))
.orElse(null);
}
Specification<Object> subquery(Class<?> entityClass, String by) {
// 以下提供 subquery 的實作, 僅供參考
return (root, query, builder) -> {
var subquery = query.subquery(LocalDateTime.class);
var subroot = subquery.from(entityClass);
subquery.select(
builder.greatest(subroot.get("createdTime").as(LocalDateTime.class))
).where(builder.equal(root.get(by), subroot.get(by)));
return builder.equal(root.get("createdTime"), subquery);
};
}
}
接著我們在 SpecMapper
建構時加入此 resolver:
var mapper = SpecMapper.builder()
.defaultResolvers()
.resolver(new MaxCreatedTimeSpecificationResolver())
.build();
最後我們定義 POJO 及進行 Specification
的轉換:
@Data
public class CustomerCriteria {
@MaxCreatedTime(from = Customer.class)
String maxBy;
}
var criteria = new CustomerCriteria();
criteria.setMaxBy("firstname");
var spec = mapper.toSpec(criteria, Customer.class);
repository.findAll(spec);
執行的 SQL 將會是:
... where customer0_.created_time=(
select max(customer1_.created_time) from customer customer1_
where customer0_.firstname=customer1_.firstname
)
Logging
將 tw.com.softleader.data.jpa.spec.SpecMapper
設定為 debug, 會在物件轉換成 Spec 的過程中印出更多資訊, 可以有效的幫助查找問題, 如:
DEBUG 20297 --- [ main] t.c.softleader.data.jpa.spec.SpecMapper : --- Spec AST ---
+-[CustomerCriteria]: my.package.CustomerCriteria
| +-[CustomerCriteria.firstname]: @Spec(value=Equals, path=, not=false) -> Equals[path=name, value=matt]
| +-[CustomerCriteria.address]: my.package.AddressCriteria (NestedSpecificationResolver)
| | +-[AddressCriteria.county]: @Spec(value=Equals, path=, not=false) -> null
| | +-[AddressCriteria.city]: @Spec(value=Equals, path=, not=false) -> Equals[path=name, value=Taipei]
| \-[CustomerCriteria.address]: Conjunction[specs=[Equals[path=city, value=Taipei]]]
\-[CustomerCriteria]: Conjunction[specs=[Equals[path=name, value=matt], Conjunction[specs=[Equals[path=city, value=Taipei]]]]]
如果你喜歡根據轉換的物件來控制和設定 Logging, 我們提供了另一種策略, 通過設定 ASTWriterFactory
, 可以改成使用目標物件的 Logger 來輸出:
var mapper = SpecMapper.builder()
.defaultResolvers()
.astWriterFactory(ASTWriterFactory.impersonation())
.build();
Limitation
SpecMapper
在找 POJO 欄位時, 只會找當前 Class 的 Local Field, 而不去往上找 Hierarchy Classes 的 Field, 如果你共用的欄位想要用在多個 POJO, 請考慮使用 Nested Specs 方式
mapper/README.zh-tw.md
2 - Starter
<dependency>
<groupId>tw.com.softleader.data.jakarta</groupId>
<artifactId>specification-mapper-starter</artifactId>
<version>${specification-mapper.version}</version>
</dependency>
specification-mapper-starter 整合了 specification-mapper 及 Spring Data JPA, 並提供了 Query by Spec 的查詢方式等
Query by Spec (QBS) 是一個 user-friendly 的查詢方式, 可以動態的建立查詢條件 (Specifications), 透過 QBS interface 就可以執行查詢語句!
Getting Started
只要在 pom.xml
中加入 dependency, 此 Starter 在 Spring Boot 啟動過程就會自動的配置一切, 讓你可以零配置的就開始使用, 包含了:
- Query By Spec 的設定
- 註冊預設的
SpecMapper
自動配置預設是啟用的, 你可以透過 properties 中的 spec.mapper.enabled
控制, 如要關閉則:
spec:
mapper:
enabled: false
Query by Spec
Query by Spec (QBS) 提供了 QueryBySpecExecutor<T>
包含了許多查詢方法:
public interface QueryBySpecExecutor<T> {
List<T> findBySpec(Object spec);
List<T> findBySpec(Object spec, Sort sort);
Page<T> findBySpec(Object spec, Pageable pageable);
// … more functionality omitted.
}
只要在原本的 repository interface 中去繼承 QueryBySpecExecutor<T>
就可以直接使用了:
public interface PersonRepository
extends JpaRepository<Person, Long>, QueryBySpecExecutor<Person> {
...
}
@Service
public class PersonService {
@Autowired PersonRepository personRepository;
public List<Person> findPeople(PersonCriteria criteria) {
return personRepository.findBySpec(criteria);
}
}
Customize the QBS Base Repository
在配置的過程中, QBS 會自動配置 Spring Data JPA 的 Base Repository, 預設的實作為 QueryBySpecExecutorImpl
由於 Java 只能單一繼承, 為了應用程式可以保留原有的 Parent Base Repository, QBS 還多提供了 QueryBySpecExecutorAdapter
擴展點
你的應用程式可以視情況選擇繼承 QueryBySpecExecutorImpl
或實作 QueryBySpecExecutorAdapter
去客製化 Base Repository, 如:
class MyRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID>
implements QueryBySpecExecutorAdapter<T> {
@Setter
@Getter
private SpecMapper specMapper;
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
@Override
public Class<T> getDomainClass() {
return super.getDomainClass();
}
@Transactional
public <S extends T> S mySave(S entity) {
// implementation goes here
}
}
並且透過 properties 中的 spec.mapper.repository-base-class
設定成自定義的 base repository 的 fulll package name, 如:
spec:
mapper:
repository-base-class: com.acme.example.MyRepositoryImpl
Default SpecMapper
此 Starter 會在 App 啟動的過程中自動的配置一個 Default SpecMapper 並註冊到 Spring @Bean 中, 你可以透過 Autowired 的方式跟 Spring 取得.
例如, 我想要在轉換成 Specification 後, 先做一些加強再去查詢, 則範例如下:
class PersonService {
@Autowired SpecMapper specMapper;
@Autowired PersonRepository personRepository;
List<Person> getPersonByCriteria(PersonCriteria criteria) {
var spec = specMapper.toSpec(criteria);
// Perform additional operations on the spec, ex:
// spec = spec.and((root, query, criteriaBuilder) -> {
// ...
// });
return personRepository.findAll(spec);
}
}
Customize SpecificationResolver
只要將你自定義的 SpecificationResolver
註冊成 Spring @Bean, 在 App 啟動的過程中就會自動的偵測並加入到 Default SpecMapper 中!
例如, 我想要增加自定義的 Spec Annotation, 配置範例如下:
@Configuration
class MyConfig {
@Bean
SpecificationResolver myResolver() {
return ...
}
}
如果你的 SpecificationResolver
需要用到 SpecMapper
本身, 則你可以包裝成 SpecificationResolverCodecBuilder
, 在建構 resolver 時就會把 SpecCodec
, 即 SpecMapper
的 interface, 傳進去, 例如:
@Configuration
class MyConfig {
@Bean
SpecificationResolverCodecBuilder myResolver() {
return MySpecificationResolver::new;
}
}
class MySpecificationResolver implements SpecificationResolver {
private final SpecCodec codec;
MySpecificationResolver(SpecCodec codec) {
// Keep the SpecCodec around to used.
this.codec = codec;
}
// implementation goes here
}
Customize SkippingStrategy
只要將你自定義的 SkippingStrategy
註冊成 Spring @Bean, 在 App 啟動的過程中就會自動的偵測並加入到 Default SpecMapper 中!
配置範例如下:
@Configuration
class MyConfig {
@Bean
SkippingStrategy mySkippingStrategy() {
return ...
}
}
Customize ASTWriterFactory
透過 properties 中的 spec.mapper.impersonate-logger
, 可以設定 Logging 過程中, 是否要偽裝成實際處理的 object logger, 預設是關閉的, 若要開啟範例如下:
spec:
mapper:
# 是否要偽裝成實際處理的 object logger, 預設關閉
impersonate-logger: true
若你需要完整的客製化, 只要將你自定義的 ASTWriterFactory
註冊成 Spring @Bean, 在 App 啟動的過程中就會自動的偵測並加入到 Default SpecMapper 中!
配置範例如下:
@Configuration
class MyConfig {
@Bean
ASTWriterFactory myASTWriterFactory() {
return ...
}
}
Customize Default SpecMapper
當然, 你也可以完全的客製化 SpecMapper
, 只要將你的 SpecMapper
註冊成 Spring @Bean, App 啟動的過程中就會略過 Default SpecMapper 的配置而優先採用的你所註冊的那個!
配置範例如下:
@Configuration
class MyConfig {
@Bean
SpecMapper mySpecMapper() {
return SpecMapper.builder()
. ...
.build();
}
}