feat(proxy): 实现代理对象初始化检查与懒加载机制

添加ProxyUtils工具类用于检查代理对象初始化状态
实现代理对象创建和标记初始化功能
添加ProxyObjectDeserializerModifier处理反序列化时的代理对象创建
修改WebSocketService错误消息字段从errorMsg改为message
实现ContractItemService.findAllByInventory方法
优化ContractService查询条件处理并添加缓存支持
重构InventoryTabSkinHistoryPrice的service获取方式
This commit is contained in:
2025-09-12 16:00:45 +08:00
parent 422994efcd
commit 98e48c520f
7 changed files with 243 additions and 33 deletions

View File

@@ -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);
} }

View File

@@ -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();

View File

@@ -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);
}
}
}
}

View File

@@ -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();
} }
} }

View File

@@ -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) {

View File

@@ -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);
} }
} }

View File

@@ -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");