Skip to content

Commit ee20e8f

Browse files
committed
使用Lambda 序列化工具获得属性名 来替换动态代理,提升性能
1 parent 042661d commit ee20e8f

File tree

8 files changed

+350
-8
lines changed

8 files changed

+350
-8
lines changed
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
spring:
22
datasource:
33
name: jk-fast-start
4-
url: jdbc:mysql://192.168.1.156:3306/test?useUnicode=true
4+
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true
55
username: root
66
driver-class-name: com.mysql.jdbc.Driver
7-
password: 123456
7+
password: root
8+
89
jpa:
9-
show-sql: true
10+
show-sql: true
11+
hibernate:
12+
ddl-auto: update

jpa-lambda-core/src/main/java/com/github/xuejike/query/jpa/lambda/JpaLambdaQuery.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.github.xuejike.query.jpa.lambda.core.*;
44
import com.github.xuejike.query.jpa.lambda.proxy.GeterSeterMethodInterceptor;
55
import com.github.xuejike.query.jpa.lambda.proxy.Proxy;
6+
import com.github.xuejike.query.jpa.lambda.tool.LambdaTool;
67
import org.hibernate.Session;
78
import org.hibernate.criterion.Criterion;
89
import org.hibernate.criterion.MatchMode;
@@ -37,17 +38,18 @@ public JpaLambdaQuery(Class<T> entityCls,Session session) {
3738
}
3839

3940
protected String getFieldName(FieldFunction<T, ?> fieldFun){
40-
fieldFun.apply(proxy);
41-
return proxyInterceptor.getLastPropertyName();
41+
42+
String name = LambdaTool.getName(fieldFun);
43+
return name;
4244
}
45+
4346
protected String[] getFieldNames(FieldFunction<T, ?> ...fieldFun){
4447
return Optional.ofNullable(fieldFun)
4548
.filter(f->f.length>0)
4649
.map(f->{
4750
String[] fieldNames = new String[fieldFun.length];
4851
for (int i = 0; i < fieldFun.length; i++) {
49-
fieldFun[i].apply(proxy);
50-
fieldNames[i] = proxyInterceptor.getLastPropertyName();
52+
fieldNames[i] = getFieldName(fieldFun[i]);
5153
}
5254
return fieldNames;
5355
}).orElse(null);
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.xuejike.query.jpa.lambda.core;
22

3-
public interface FieldFunction<T,R> {
3+
import java.io.Serializable;
4+
5+
public interface FieldFunction<T,R> extends Serializable {
46
R apply(T entity);
57
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.xuejike.query.jpa.lambda.exception;
2+
3+
public class JpaLambdaException extends RuntimeException {
4+
public JpaLambdaException(String message) {
5+
super(message);
6+
}
7+
8+
public JpaLambdaException(String message, Throwable cause) {
9+
super(message, cause);
10+
}
11+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.github.xuejike.query.jpa.lambda.tool;
17+
18+
import com.github.xuejike.query.jpa.lambda.exception.JpaLambdaException;
19+
20+
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.util.Arrays;
23+
import java.util.List;
24+
25+
/**
26+
* <p>
27+
* ClassUtils
28+
* </p>
29+
*
30+
*/
31+
public final class ClassUtils {
32+
33+
34+
private ClassUtils() {
35+
}
36+
37+
/**
38+
* <p>
39+
* 请仅在确定类存在的情况下调用该方法
40+
* </p>
41+
*
42+
* @param name 类名称
43+
* @return 返回转换后的 Class
44+
*/
45+
public static Class<?> toClassConfident(String name) {
46+
try {
47+
return Class.forName(name);
48+
} catch (ClassNotFoundException e) {
49+
throw new JpaLambdaException("找不到指定的class!请仅在明确确定会有 class 的时候,调用该方法", e);
50+
}
51+
}
52+
53+
54+
55+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.xuejike.query.jpa.lambda.tool;
2+
3+
import com.github.xuejike.query.jpa.lambda.core.FieldFunction;
4+
import com.github.xuejike.query.jpa.lambda.exception.JpaLambdaException;
5+
6+
import java.lang.ref.WeakReference;
7+
import java.util.HashMap;
8+
import java.util.Locale;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
13+
public class LambdaTool {
14+
public static String methodToProperty(String name) {
15+
if (name.startsWith("is")) {
16+
name = name.substring(2);
17+
} else if (name.startsWith("get") || name.startsWith("set")) {
18+
name = name.substring(3);
19+
} else {
20+
throw new JpaLambdaException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
21+
}
22+
23+
if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
24+
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
25+
}
26+
27+
return name;
28+
}
29+
30+
protected static Map<Class, WeakReference<String>> cache = new ConcurrentHashMap<>();
31+
public static<T> String getName(FieldFunction<T,?> fieldFun){
32+
Class<? extends FieldFunction> cls = fieldFun.getClass();
33+
return Optional.ofNullable(cache.get(cls))
34+
.map(WeakReference::get)
35+
.orElseGet(()->{
36+
SerializedLambda lambda = SerializedLambda.resolve(fieldFun);
37+
String methodName = lambda.getImplMethodName();
38+
String property = methodToProperty(methodName);
39+
WeakReference<String> wr = new WeakReference<>(property);
40+
cache.put(cls,wr);
41+
return property;
42+
});
43+
44+
}
45+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.github.xuejike.query.jpa.lambda.tool;
17+
18+
import java.io.*;
19+
20+
21+
/**
22+
* <p>copy from org.springframework.util.SerializationUtils</p>
23+
*
24+
* @since 1.0
25+
*/
26+
public class SerializationUtils {
27+
28+
/**
29+
* Deep clone an {@code Object} using serialization.
30+
* <p>This is many times slower than writing clone methods by hand
31+
* on all objects in your object graph. However, for complex object
32+
* graphs, or for those that don't support deep cloning this can
33+
* be a simple alternative implementation. Of course all the objects
34+
* must be {@code Serializable}.</p>
35+
*
36+
* @param <T> the type of the object involved
37+
* @param object the {@code Serializable} object to clone
38+
* @return the cloned object
39+
*/
40+
@SuppressWarnings("unchecked")
41+
public static <T extends Serializable> T clone(final T object) {
42+
if (object == null) {
43+
return null;
44+
}
45+
final byte[] objectData = serialize(object);
46+
return (T) deserialize(objectData);
47+
}
48+
49+
/**
50+
* Serialize the given object to a byte array.
51+
* @param object the object to serialize
52+
* @return an array of bytes representing the object in a portable fashion
53+
*/
54+
public static byte[] serialize(Object object) {
55+
if (object == null) {
56+
return null;
57+
}
58+
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
59+
try {
60+
ObjectOutputStream oos = new ObjectOutputStream(baos);
61+
oos.writeObject(object);
62+
oos.flush();
63+
} catch (IOException ex) {
64+
throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
65+
}
66+
return baos.toByteArray();
67+
}
68+
69+
/**
70+
* Deserialize the byte array into an object.
71+
*
72+
* @param bytes a serialized object
73+
* @return the result of deserializing the bytes
74+
*/
75+
public static Object deserialize(byte[] bytes) {
76+
if (bytes == null) {
77+
return null;
78+
}
79+
try {
80+
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
81+
return ois.readObject();
82+
} catch (IOException ex) {
83+
throw new IllegalArgumentException("Failed to deserialize object", ex);
84+
} catch (ClassNotFoundException ex) {
85+
throw new IllegalStateException("Failed to deserialize object type", ex);
86+
}
87+
}
88+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.github.xuejike.query.jpa.lambda.tool;
17+
18+
19+
import com.github.xuejike.query.jpa.lambda.core.FieldFunction;
20+
import com.github.xuejike.query.jpa.lambda.exception.JpaLambdaException;
21+
22+
import java.io.*;
23+
import java.util.regex.Matcher;
24+
import java.util.regex.Pattern;
25+
26+
/**
27+
* 这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的
28+
* <p>负责将一个支持序列的 Function 序列化为 SerializedLambda</p>
29+
*
30+
*/
31+
@SuppressWarnings("unused")
32+
public class SerializedLambda implements Serializable {
33+
34+
private static final long serialVersionUID = 8025925345765570181L;
35+
36+
private Class<?> capturingClass;
37+
private String functionalInterfaceClass;
38+
private String functionalInterfaceMethodName;
39+
private String functionalInterfaceMethodSignature;
40+
private String implClass;
41+
private String implMethodName;
42+
private String implMethodSignature;
43+
private int implMethodKind;
44+
private String instantiatedMethodType;
45+
private Object[] capturedArgs;
46+
47+
/**
48+
* 通过反序列化转换 lambda 表达式,该方法只能序列化 lambda 表达式,不能序列化接口实现或者正常非 lambda 写法的对象
49+
*
50+
* @param lambda lambda对象
51+
* @return 返回解析后的 SerializedLambda
52+
*/
53+
public static SerializedLambda resolve(FieldFunction<?, ?> lambda) {
54+
if (!lambda.getClass().isSynthetic()) {
55+
throw new JpaLambdaException("该方法仅能传入 lambda 表达式产生的合成类");
56+
}
57+
try (ObjectInputStream objIn = new ObjectInputStream(new
58+
ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
59+
@Override
60+
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
61+
Class<?> clazz = super.resolveClass(objectStreamClass);
62+
return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
63+
}
64+
}) {
65+
return (SerializedLambda) objIn.readObject();
66+
} catch (ClassNotFoundException | IOException e) {
67+
throw new JpaLambdaException("This is impossible to happen", e);
68+
}
69+
}
70+
71+
/**
72+
* 获取接口 class
73+
*
74+
* @return 返回 class 名称
75+
*/
76+
public String getFunctionalInterfaceClassName() {
77+
return normalName(functionalInterfaceClass);
78+
}
79+
80+
/**
81+
* 获取实现的 class
82+
*
83+
* @return 实现类
84+
*/
85+
public Class<?> getImplClass() {
86+
return ClassUtils.toClassConfident(getImplClassName());
87+
}
88+
89+
/**
90+
* 获取 class 的名称
91+
*
92+
* @return 类名
93+
*/
94+
public String getImplClassName() {
95+
return normalName(implClass);
96+
}
97+
98+
/**
99+
* 获取实现者的方法名称
100+
*
101+
* @return 方法名称
102+
*/
103+
public String getImplMethodName() {
104+
return implMethodName;
105+
}
106+
107+
/**
108+
* 正常化类名称,将类名称中的 / 替换为 .
109+
*
110+
* @param name 名称
111+
* @return 正常的类名
112+
*/
113+
private String normalName(String name) {
114+
return name.replace('/', '.');
115+
}
116+
117+
private static final Pattern INSTANTIATED_METHOD_TYPE = Pattern.compile("\\(L(?<instantiatedMethodType>[\\S&&[^;)]]+);\\)L[\\S]+;");
118+
119+
public Class getInstantiatedMethodType() {
120+
Matcher matcher = INSTANTIATED_METHOD_TYPE.matcher(instantiatedMethodType);
121+
if (matcher.find()) {
122+
return ClassUtils.toClassConfident(normalName(matcher.group("instantiatedMethodType")));
123+
}
124+
throw new JpaLambdaException(String.format("无法从 %s 解析调用实例。。。", instantiatedMethodType));
125+
}
126+
127+
/**
128+
* @return 字符串形式
129+
*/
130+
@Override
131+
public String toString() {
132+
return String.format("%s -> %s::%s", getFunctionalInterfaceClassName(), getImplClass().getSimpleName(),
133+
implMethodName);
134+
}
135+
136+
}

0 commit comments

Comments
 (0)