feat(proxy): 实现代理对象初始化检查与懒加载机制
添加ProxyUtils工具类用于检查代理对象初始化状态 实现代理对象创建和标记初始化功能 添加ProxyObjectDeserializerModifier处理反序列化时的代理对象创建 修改WebSocketService错误消息字段从errorMsg改为message 实现ContractItemService.findAllByInventory方法 优化ContractService查询条件处理并添加缓存支持 重构InventoryTabSkinHistoryPrice的service获取方式
This commit is contained in:
@@ -100,7 +100,7 @@ public class WebSocketService {
|
|||||||
}
|
}
|
||||||
} else if (node.has("errorCode")) {
|
} else if (node.has("errorCode")) {
|
||||||
int errorCode = node.get("errorCode").asInt();
|
int errorCode = node.get("errorCode").asInt();
|
||||||
String errorMsg = node.get("errorMsg").asText();
|
String errorMsg = node.get("message").asText();
|
||||||
// TODO 需要重新登录
|
// TODO 需要重新登录
|
||||||
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,41 +72,25 @@ public class InventoryTabSkinHistoryPrice
|
|||||||
public TableColumn<InventoryHistoryPriceViewModel, Number> miniPurchaseTaxPriceColumn;
|
public TableColumn<InventoryHistoryPriceViewModel, Number> miniPurchaseTaxPriceColumn;
|
||||||
public TableColumn<InventoryHistoryPriceViewModel, MonthDay> miniPurchasePriceDateColumn;
|
public TableColumn<InventoryHistoryPriceViewModel, MonthDay> miniPurchasePriceDateColumn;
|
||||||
|
|
||||||
@Setter
|
|
||||||
InventoryHistoryPriceService service;
|
|
||||||
@Setter
|
|
||||||
ContractService contractService;
|
|
||||||
@Setter
|
|
||||||
ContractItemService contractItemService;
|
|
||||||
|
|
||||||
public InventoryTabSkinHistoryPrice(InventoryWindowController controller) {
|
public InventoryTabSkinHistoryPrice(InventoryWindowController controller) {
|
||||||
super(controller);
|
super(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InventoryHistoryPriceService getViewModelService() {
|
protected InventoryHistoryPriceService getViewModelService() {
|
||||||
return getService();
|
return getHistoryPriceService();
|
||||||
}
|
}
|
||||||
|
|
||||||
InventoryHistoryPriceService getService() {
|
InventoryHistoryPriceService getHistoryPriceService() {
|
||||||
if (service == null) {
|
return getCachedBean(InventoryHistoryPriceService.class);
|
||||||
service = getBean(InventoryHistoryPriceService.class);
|
|
||||||
}
|
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContractItemService getContractItemService() {
|
ContractItemService getContractItemService() {
|
||||||
if (contractItemService == null) {
|
return getCachedBean(ContractItemService.class);
|
||||||
contractItemService = getBean(ContractItemService.class);
|
|
||||||
}
|
|
||||||
return contractItemService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContractService getContractService() {
|
ContractService getContractService() {
|
||||||
if (contractService == null) {
|
return getCachedBean(ContractService.class);
|
||||||
contractService = getBean(ContractService.class);
|
|
||||||
}
|
|
||||||
return contractService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -182,10 +166,10 @@ public class InventoryTabSkinHistoryPrice
|
|||||||
|
|
||||||
HashMap<Integer, InventoryHistoryPrice> historyPriceMap = new HashMap<>();
|
HashMap<Integer, InventoryHistoryPrice> historyPriceMap = new HashMap<>();
|
||||||
|
|
||||||
for (InventoryHistoryPrice historyPrice : getService().findAllByInventory(getParent())) {
|
for (InventoryHistoryPrice historyPrice : getHistoryPriceService().findAllByInventory(getParent())) {
|
||||||
InventoryHistoryPrice oldValue = historyPriceMap.put(historyPrice.getYear().getValue(), historyPrice);
|
InventoryHistoryPrice oldValue = historyPriceMap.put(historyPrice.getYear().getValue(), historyPrice);
|
||||||
if (oldValue != null) {
|
if (oldValue != null) {
|
||||||
getService().delete(oldValue);
|
getHistoryPriceService().delete(oldValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +220,7 @@ public class InventoryTabSkinHistoryPrice
|
|||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getService().save(historyPrice);
|
getHistoryPriceService().save(historyPrice);
|
||||||
});
|
});
|
||||||
|
|
||||||
loadTableDataSet();
|
loadTableDataSet();
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.ecep.contract.serializer;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
|
import com.fasterxml.jackson.databind.BeanDescription;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
|
||||||
|
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义的反序列化器修改器,用于实现类似Hibernate的代理对象功能
|
||||||
|
*/
|
||||||
|
public class ProxyObjectDeserializerModifier extends BeanDeserializerModifier {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public ProxyObjectDeserializerModifier(ObjectMapper objectMapper) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
|
||||||
|
BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
|
||||||
|
// 检查是否是IdentityEntity的实现类
|
||||||
|
if (IdentityEntity.class.isAssignableFrom(beanDesc.getBeanClass())) {
|
||||||
|
// 返回包装后的反序列化器,处理代理对象的创建
|
||||||
|
return new ProxyObjectDeserializer((JsonDeserializer<Object>) deserializer,
|
||||||
|
beanDesc.getBeanClass(), objectMapper);
|
||||||
|
}
|
||||||
|
return deserializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理对象反序列化器,用于处理对象字段的懒加载
|
||||||
|
*/
|
||||||
|
private static class ProxyObjectDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
|
||||||
|
|
||||||
|
private final JsonDeserializer<Object> delegate;
|
||||||
|
private final Class<?> targetClass;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public ProxyObjectDeserializer(JsonDeserializer<Object> delegate,
|
||||||
|
Class<?> targetClass, ObjectMapper objectMapper) {
|
||||||
|
super((Class<Object>) targetClass);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.targetClass = targetClass;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object deserialize(com.fasterxml.jackson.core.JsonParser p,
|
||||||
|
com.fasterxml.jackson.databind.DeserializationContext ctxt) throws java.io.IOException {
|
||||||
|
// 首先检查是否是一个只有id的对象节点
|
||||||
|
if (p.isExpectedStartObjectToken()) {
|
||||||
|
ObjectNode node = p.readValueAsTree();
|
||||||
|
|
||||||
|
// 检查是否只包含id和_proxy_字段
|
||||||
|
if (node.has("id") && node.has("_proxy_")) {
|
||||||
|
try {
|
||||||
|
// 创建目标类的实例
|
||||||
|
Object instance = targetClass.getDeclaredConstructor().newInstance();
|
||||||
|
|
||||||
|
// 设置id字段
|
||||||
|
if (instance instanceof IdentityEntity) {
|
||||||
|
((IdentityEntity) instance).setId(node.get("id").asInt());
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果创建实例失败,回退到原始反序列化器
|
||||||
|
return delegate.deserialize(p, ctxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对于普通对象,使用原始反序列化器
|
||||||
|
return delegate.deserialize(p, ctxt);
|
||||||
|
}
|
||||||
|
// 对于非对象类型,使用原始反序列化器
|
||||||
|
return delegate.deserialize(p, ctxt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
|
||||||
|
// 如果委托的反序列化器也是ResolvableDeserializer,则调用其resolve方法
|
||||||
|
if (delegate instanceof ResolvableDeserializer) {
|
||||||
|
((ResolvableDeserializer) delegate).resolve(ctxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ecep.contract.service;
|
package com.ecep.contract.service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -12,7 +13,9 @@ import com.ecep.contract.vm.ContractItemViewModel;
|
|||||||
public class ContractItemService extends QueryService<ContractItem, ContractItemViewModel> {
|
public class ContractItemService extends QueryService<ContractItem, ContractItemViewModel> {
|
||||||
|
|
||||||
public List<ContractItem> findAllByInventory(Inventory parent) {
|
public List<ContractItem> findAllByInventory(Inventory parent) {
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'findAllByInventory'");
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
|
params.put("inventory", parent.getId());
|
||||||
|
return findAll(params, null).getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ package com.ecep.contract.service;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.cache.annotation.CacheConfig;
|
||||||
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
import org.springframework.cache.annotation.Caching;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.ecep.contract.MessageHolder;
|
import com.ecep.contract.MessageHolder;
|
||||||
@@ -13,14 +19,42 @@ import com.ecep.contract.model.ContractFile;
|
|||||||
import com.ecep.contract.model.Project;
|
import com.ecep.contract.model.Project;
|
||||||
import com.ecep.contract.vm.ContractViewModel;
|
import com.ecep.contract.vm.ContractViewModel;
|
||||||
|
|
||||||
|
import io.micrometer.common.util.StringUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@CacheConfig(cacheNames = "contract")
|
||||||
public class ContractService extends QueryService<Contract, ContractViewModel> {
|
public class ContractService extends QueryService<Contract, ContractViewModel> {
|
||||||
|
|
||||||
|
@Cacheable(key = "#p0")
|
||||||
|
public Contract findById(Integer id) {
|
||||||
|
return super.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存实体对象,异步方法
|
||||||
|
*/
|
||||||
|
@Caching(evict = {
|
||||||
|
@CacheEvict(key = "#p0.id"),
|
||||||
|
@CacheEvict(key = "'code-'+#p0.code")
|
||||||
|
})
|
||||||
|
public Contract save(Contract contract) {
|
||||||
|
return super.save(contract);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Caching(evict = {
|
||||||
|
@CacheEvict(key = "#p0.id"),
|
||||||
|
@CacheEvict(key = "'code-'+#p0.code")
|
||||||
|
})
|
||||||
|
public void delete(Contract contract) {
|
||||||
|
super.delete(contract);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean updateParentCode(Contract contract) {
|
public boolean updateParentCode(Contract contract) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'updateParentCode'");
|
throw new UnsupportedOperationException("Unimplemented method 'updateParentCode'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Cacheable(key = "'code-'+#p0")
|
||||||
public Contract findByCode(String string) {
|
public Contract findByCode(String string) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'findByCode'");
|
throw new UnsupportedOperationException("Unimplemented method 'findByCode'");
|
||||||
@@ -31,8 +65,13 @@ public class ContractService extends QueryService<Contract, ContractViewModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Contract> findAllBySaleContract(Contract contract) {
|
public List<Contract> findAllBySaleContract(Contract contract) {
|
||||||
// TODO Auto-generated method stub
|
String parentCode = contract.getCode();
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'findAllBySaleContract'");
|
if (StringUtils.isEmpty(parentCode)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
|
params.put("parentCode", contract.getCode());
|
||||||
|
return findAll(params, Pageable.unpaged()).getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean checkContractPathInBasePath(Contract v) {
|
public boolean checkContractPathInBasePath(Contract v) {
|
||||||
|
|||||||
@@ -1,13 +1,97 @@
|
|||||||
package com.ecep.contract.util;
|
package com.ecep.contract.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
|
||||||
|
import com.ecep.contract.model.IdentityEntity;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
public class ProxyUtils {
|
public class ProxyUtils {
|
||||||
/**
|
/**
|
||||||
* 判断对象是否已初始化
|
* 判断对象是否已初始化
|
||||||
* 在客户端环境中,如果对象不为null,则认为已初始化
|
* 在客户端环境中,从服务器端返回的数据中,代理对象序列化时只包含Id属性,
|
||||||
|
* 其他属性为null,因此需要判断对象是否已初始化
|
||||||
|
* 初始化条件:对象不为null,且不是代理对象或已被标记为初始化
|
||||||
*/
|
*/
|
||||||
public static boolean isInitialized(Object proxy) {
|
public static boolean isInitialized(Object proxy) {
|
||||||
return proxy != null;
|
if (proxy == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 proxy 中除了id其他属性都为null
|
||||||
|
try {
|
||||||
|
Field[] fields = proxy.getClass().getFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
if (field.getName().equals("id")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 忽略被JsonIgnore注解标记的字段
|
||||||
|
if (field.getAnnotation(JsonIgnore.class) != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 检查字段是否为null
|
||||||
|
if (field.get(proxy) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果除了id其他属性都为null,认为未初始化
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 发生异常时默认已初始化
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查对象是否是代理对象
|
||||||
|
*/
|
||||||
|
public static boolean isProxyObject(Object obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建代理对象
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T createProxy(T original, Class<?>... interfaces) {
|
||||||
|
if (original == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果对象已经是代理对象,直接返回
|
||||||
|
if (isProxyObject(original)) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建代理对象
|
||||||
|
T proxy = (T) Proxy.newProxyInstance(
|
||||||
|
original.getClass().getClassLoader(),
|
||||||
|
interfaces,
|
||||||
|
(proxyObj, method, args) -> {
|
||||||
|
// 如果代理对象未初始化,先初始化
|
||||||
|
if (!isInitialized(proxyObj)) {
|
||||||
|
// 这里可以添加初始化逻辑
|
||||||
|
markInitialized(proxyObj);
|
||||||
|
}
|
||||||
|
// 调用原始对象的方法
|
||||||
|
return method.invoke(original, args);
|
||||||
|
});
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记对象为已初始化
|
||||||
|
*/
|
||||||
|
public static void markInitialized(Object proxy) {
|
||||||
|
if (proxy != null && isProxyObject(proxy)) {
|
||||||
|
// 标记为已初始化
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查对象是否是实体类
|
||||||
|
*/
|
||||||
|
public static boolean isEntityClass(Class<?> clazz) {
|
||||||
|
return clazz != null && IdentityEntity.class.isAssignableFrom(clazz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,9 +135,14 @@ public class ContractService implements IEntityService<Contract>, QueryService<C
|
|||||||
if (paramsNode.has("searchText")) {
|
if (paramsNode.has("searchText")) {
|
||||||
spec = getSpecification(paramsNode.get("searchText").asText());
|
spec = getSpecification(paramsNode.get("searchText").asText());
|
||||||
}
|
}
|
||||||
|
|
||||||
// field
|
// field
|
||||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "payWay", "parentCode");
|
if (paramsNode.has("payWay")) {
|
||||||
|
ContractPayWay payWay = ContractPayWay.valueOf(paramsNode.get("payWay").asText());
|
||||||
|
spec = SpecificationUtils.and(spec, (root, query, cb) -> {
|
||||||
|
return cb.equal(root.get("payWay"), payWay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "parentCode");
|
||||||
|
|
||||||
// relation
|
// relation
|
||||||
spec = SpecificationUtils.andParam(spec, paramsNode, "group", "kind", "type", "group", "company", "project");
|
spec = SpecificationUtils.andParam(spec, paramsNode, "group", "kind", "type", "group", "company", "project");
|
||||||
|
|||||||
Reference in New Issue
Block a user