Skip to content

Commit b641d19

Browse files
committed
refactor: 优化json序列化
Signed-off-by: zhouhao <zh.sqy@qq.com>
1 parent 3715c3a commit b641d19

File tree

4 files changed

+136
-45
lines changed

4 files changed

+136
-45
lines changed

hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationHolder.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818

1919
package org.hswebframework.web.authorization;
2020

21+
import io.netty.util.concurrent.FastThreadLocal;
22+
import lombok.SneakyThrows;
2123
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
24+
import reactor.core.Disposable;
2225
import reactor.core.publisher.Flux;
2326
import reactor.core.publisher.Mono;
2427

2528
import java.util.ArrayList;
2629
import java.util.List;
2730
import java.util.Optional;
31+
import java.util.concurrent.Callable;
2832
import java.util.concurrent.locks.ReadWriteLock;
2933
import java.util.concurrent.locks.ReentrantReadWriteLock;
3034
import java.util.function.Function;
@@ -50,6 +54,9 @@ public final class AuthenticationHolder {
5054

5155
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
5256

57+
private static final FastThreadLocal<Authentication> CURRENT = new FastThreadLocal<>();
58+
59+
5360
private static Optional<Authentication> get(Function<AuthenticationSupplier, Optional<Authentication>> function) {
5461
int size = suppliers.size();
5562
if (size == 0) {
@@ -58,21 +65,23 @@ private static Optional<Authentication> get(Function<AuthenticationSupplier, Opt
5865
if (size == 1) {
5966
return function.apply(suppliers.get(0));
6067
}
61-
SimpleAuthentication merge = new SimpleAuthentication();
68+
ReactiveAuthenticationHolder.AuthenticationMerging merging
69+
= new ReactiveAuthenticationHolder.AuthenticationMerging();
6270
for (AuthenticationSupplier supplier : suppliers) {
63-
function.apply(supplier).ifPresent(merge::merge);
64-
}
65-
if (merge.getUser() == null) {
66-
return Optional.empty();
71+
function.apply(supplier).ifPresent(merging::merge);
6772
}
68-
return Optional.of(merge);
73+
return Optional.ofNullable(merging.get());
6974
}
7075

76+
7177
/**
7278
* @return 当前登录的用户权限信息
7379
*/
7480
public static Optional<Authentication> get() {
75-
81+
Authentication current = CURRENT.get();
82+
if (current != null) {
83+
return Optional.of(current);
84+
}
7685
return get(AuthenticationSupplier::get);
7786
}
7887

@@ -89,7 +98,7 @@ public static Optional<Authentication> get(String userId) {
8998
/**
9099
* 初始化 {@link AuthenticationSupplier}
91100
*
92-
* @param supplier
101+
* @param supplier 认证信息提供者
93102
*/
94103
public static void addSupplier(AuthenticationSupplier supplier) {
95104
lock.writeLock().lock();
@@ -100,4 +109,23 @@ public static void addSupplier(AuthenticationSupplier supplier) {
100109
}
101110
}
102111

112+
/**
113+
* 指定用户权限,执行一个任务。任务执行过程中可通过 {@link Authentication#current()}获取到当前权限.
114+
*
115+
* @param current 当前用户权限信息
116+
* @param callable 任务执行器
117+
* @param <T> 任务执行结果类型
118+
* @return 任务执行结果
119+
*/
120+
@SneakyThrows
121+
public static <T> T executeWith(Authentication current, Callable<T> callable) {
122+
Authentication previous = CURRENT.get();
123+
try {
124+
CURRENT.set(current);
125+
return callable.call();
126+
} finally {
127+
CURRENT.set(previous);
128+
}
129+
}
130+
103131
}

hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationHolder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public synchronized void merge(Authentication auth) {
103103
}
104104
}
105105

106-
private Authentication get() {
106+
Authentication get() {
107107
return auth;
108108
}
109109
}

hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.netty.util.concurrent.FastThreadLocal;
44
import lombok.AllArgsConstructor;
5+
import lombok.SneakyThrows;
56
import org.hswebframework.web.exception.I18nSupportException;
67
import org.reactivestreams.Publisher;
78
import org.reactivestreams.Subscription;
@@ -135,6 +136,23 @@ public static void doWith(Locale locale, Consumer<Locale> consumer) {
135136
}
136137
}
137138

139+
/**
140+
* 使用指定的区域来执行某些操作
141+
*
142+
* @param locale 区域
143+
* @param callable 任务
144+
*/
145+
@SneakyThrows
146+
public static <T> T doWith(Locale locale, Callable<T> callable) {
147+
Locale old = CONTEXT_THREAD_LOCAL.get();
148+
try {
149+
CONTEXT_THREAD_LOCAL.set(locale);
150+
return callable.call();
151+
} finally {
152+
CONTEXT_THREAD_LOCAL.set(old);
153+
}
154+
}
155+
138156
/**
139157
* 在响应式作用,使用指定的区域作为语言环境,在下游则可以使用{@link LocaleUtils#currentReactive()}来获取
140158
* <p>

hsweb-starter/src/main/java/org/hswebframework/web/starter/jackson/CustomJackson2jsonEncoder.java

Lines changed: 81 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package org.hswebframework.web.starter.jackson;
22

3+
import lombok.AllArgsConstructor;
4+
import org.hswebframework.web.authorization.Authentication;
5+
import org.hswebframework.web.authorization.AuthenticationHolder;
6+
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
37
import org.hswebframework.web.i18n.LocaleUtils;
48
import org.springframework.http.codec.json.Jackson2CodecSupport;
59

610
import java.io.IOException;
711
import java.lang.annotation.Annotation;
812
import java.nio.charset.Charset;
913
import java.util.*;
14+
import java.util.concurrent.Callable;
15+
import java.util.function.Function;
1016

1117
import com.fasterxml.jackson.core.JsonEncoding;
1218
import com.fasterxml.jackson.core.JsonGenerator;
@@ -37,6 +43,8 @@
3743
import org.springframework.util.Assert;
3844
import org.springframework.util.MimeType;
3945

46+
import javax.annotation.Nonnull;
47+
4048
/**
4149
* Base class providing support methods for Jackson 2.9 encoding. For non-streaming use
4250
* cases, {@link Flux} elements are collected into a {@link List} before serialization for
@@ -106,71 +114,80 @@ public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType
106114
}
107115
}
108116
return (Object.class == clazz ||
109-
(!String.class.isAssignableFrom(elementType.resolve(clazz)) && getObjectMapper().canSerialize(clazz)));
117+
(!String.class.isAssignableFrom(elementType.resolve(clazz)) && getObjectMapper().canSerialize(clazz)));
110118
}
111119

120+
112121
@Override
113-
public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory bufferFactory,
114-
ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
122+
@Nonnull
123+
public Flux<DataBuffer> encode(@Nonnull Publisher<?> inputStream, @Nonnull DataBufferFactory bufferFactory,
124+
@Nonnull ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
115125

116126
Assert.notNull(inputStream, "'inputStream' must not be null");
117127
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
118128
Assert.notNull(elementType, "'elementType' must not be null");
119129

130+
120131
if (inputStream instanceof Mono) {
121-
return Mono.from(inputStream)
122-
.as(LocaleUtils::transform)
123-
.map(value -> encodeValue(value, bufferFactory, elementType, mimeType, hints))
124-
.flux();
132+
return Mono
133+
.zip(
134+
currentContext(hints),
135+
Mono.from(inputStream),
136+
(ctx, value) -> ctx
137+
.execute(() -> encodeValue(value, bufferFactory, elementType, mimeType, hints))
138+
)
139+
.flux();
125140
} else {
126141
byte[] separator = streamSeparator(mimeType);
127142
if (separator != null) { // streaming
128143
try {
129144
ObjectWriter writer = createObjectWriter(elementType, mimeType, hints);
130145
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer
131-
.getFactory()
132-
._getBufferRecycler());
146+
.getFactory()
147+
._getBufferRecycler());
133148
JsonEncoding encoding = getJsonEncoding(mimeType);
134149
JsonGenerator generator = getObjectMapper()
135-
.getFactory()
136-
.createGenerator(byteBuilder, encoding);
150+
.getFactory()
151+
.createGenerator(byteBuilder, encoding);
137152
SequenceWriter sequenceWriter = writer.writeValues(generator);
138153

139-
return Flux
140-
.from(inputStream)
141-
.as(LocaleUtils::transform)
142-
.map(value -> this.encodeStreamingValue(value,
143-
bufferFactory,
144-
hints,
145-
sequenceWriter,
146-
byteBuilder,
147-
separator))
148-
.doAfterTerminate(() -> {
149-
try {
150-
byteBuilder.release();
151-
generator.close();
152-
} catch (IOException ex) {
153-
logger.error("Could not close Encoder resources", ex);
154-
}
155-
});
154+
return currentContext(hints)
155+
.flatMapMany(ctx -> ctx
156+
.transform(inputStream,
157+
value -> this
158+
.encodeStreamingValue(value,
159+
bufferFactory,
160+
hints,
161+
sequenceWriter,
162+
byteBuilder,
163+
separator)))
164+
165+
.doAfterTerminate(() -> {
166+
try {
167+
byteBuilder.release();
168+
generator.close();
169+
} catch (IOException ex) {
170+
logger.error("Could not close Encoder resources", ex);
171+
}
172+
});
156173
} catch (IOException ex) {
157174
return Flux.error(ex);
158175
}
159176
} else { // non-streaming
160177
ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
161-
return Flux.from(inputStream)
162-
.collectList()
163-
.as(LocaleUtils::transform)
164-
.map(value -> encodeValue(value, bufferFactory, listType, mimeType, hints))
165-
.flux();
178+
return currentContext(hints)
179+
.flatMapMany(ctx -> ctx
180+
.transform(Flux.from(inputStream).collectList(),
181+
value -> encodeValue(value, bufferFactory, listType, mimeType, hints)));
166182
}
167183

168184
}
169185
}
170186

171187
@Override
172-
public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
173-
ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
188+
@Nonnull
189+
public DataBuffer encodeValue(@Nonnull Object value,@Nonnull DataBufferFactory bufferFactory,
190+
@Nonnull ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
174191

175192
ObjectWriter writer = createObjectWriter(valueType, mimeType, hints);
176193
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.getFactory()._getBufferRecycler());
@@ -251,7 +268,7 @@ private ObjectWriter createObjectWriter(ResolvableType valueType, @Nullable Mime
251268
JavaType javaType = getJavaType(valueType.getType(), null);
252269
Class<?> jsonView = (hints != null ? (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null);
253270
ObjectWriter writer = (jsonView != null ?
254-
getObjectMapper().writerWithView(jsonView) : getObjectMapper().writer());
271+
getObjectMapper().writerWithView(jsonView) : getObjectMapper().writer());
255272

256273
if (javaType.isContainerType()) {
257274
writer = writer.forType(javaType);
@@ -321,4 +338,32 @@ public Map<String, Object> getEncodeHints(@Nullable ResolvableType actualType, R
321338
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
322339
return parameter.getMethodAnnotation(annotType);
323340
}
341+
342+
static final SimpleAuthentication ANONYMOUS = new SimpleAuthentication();
343+
344+
static Mono<EncodingContext> currentContext(Map<String, Object> hints) {
345+
return Mono
346+
.zip(Authentication.currentReactive().defaultIfEmpty(ANONYMOUS),
347+
LocaleUtils.currentReactive(), EncodingContext::new);
348+
}
349+
350+
@AllArgsConstructor
351+
static class EncodingContext {
352+
private final Authentication authentication;
353+
private final Locale locale;
354+
355+
private <T, R> Flux<T> transform(Publisher<R> source, Function<R, T> transformer) {
356+
return Flux
357+
.from(source)
358+
.map((val) -> execute(() -> transformer.apply(val)));
359+
}
360+
361+
private <T> T execute(Callable<T> callable) {
362+
if (authentication == null || authentication == ANONYMOUS) {
363+
return LocaleUtils.doWith(locale, callable);
364+
}
365+
return AuthenticationHolder
366+
.executeWith(authentication, () -> LocaleUtils.doWith(locale, callable));
367+
}
368+
}
324369
}

0 commit comments

Comments
 (0)