@Slf4j @Controller @RequestMapping("/order") public class ForeOrderController { @Autowired private ProductService productService; @Autowired private ProductOrderItemService productOrderItemService; @Autowired private AddressService addressService; @Autowired private CategoryService categoryService; @Autowired private ProductImageService productImageService; @Autowired private ProductOrderService productOrderService; @Autowired private ReviewService reviewService; @Autowired private LastIDService lastIDService;
时间: 2024-04-03 10:31:01 AIGC 浏览: 154
这是一个Java类,使用了Spring MVC框架的注解,其中包括@Controller和@RequestMapping("/order")。该类中有多个@Autowired注解,用于自动注入其他服务类,如ProductService、AddressService等。这些服务类应该都是用于处理订单相关操作的。另外,该类还可能涉及到一些产品分类、产品图片、评论等相关服务。最后,该类中还有一个@Autowired注解用于注入LastIDService,可能是用于生成订单号等唯一标识的服务。
相关问题
aop实现订单操作日志,商品福建采购等等的操作日志,1.订单日志 可查看订单新增,删除,编辑记录,点击详情可查看明细怎么实现这一需求,下面是我的aop切面类:@Slf4j @Aspect @AllArgsConstructor public class SysLogAspect { private final AdminService adminService; private final SysLogService sysLogService; private ObjectMapper objectMapper; @Around("@annotation(sysLog)") public Object around(ProceedingJoinPoint joinPoint, com.jxyunge.sign.annotation.SysLog sysLog) throws Throwable { long beginTime = SystemClock.now(); //执行方法 Object result = joinPoint.proceed(); //执行时长(毫秒) long time = SystemClock.now() - beginTime; SysLog sysLogEntity = new SysLog(); if(sysLog != null){ //注解上的描述 sysLogEntity.setOperation(sysLog.value()); } //请求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); sysLogEntity.setMethod(className + "." + methodName + "()"); sysLogEntity.setParams(null); //请求的参数 Object[] args = joinPoint.getArgs(); if(args != null && args.length > 0) { String params = null; try { if(args.length > 0 && args[0] != null && Objects.equals(args[0].getClass().getName(), PageParam.class.getName())){ PageParam<Object> pageParam = (PageParam<Object>) args[0]; List<Object> records = pageParam.getRecords(); // 过滤掉不可序列化的对象 List<Object> filteredRecords = filterSerializableObjects(records); params = objectMapper.writeValueAsString(filteredRecords); } else { // 过滤参数中的不可序列化对象 Object[] filteredArgs = filterSerializableObjects(args); params = objectMapper.writeValueAsString(filteredArgs); } } catch (Exception e) { // 如果序列化失败,记录参数类型信息或简单处理 params = getSafeParamRepresentation(args); log.warn("参数序列化失败,使用备用方案: {}", e.getMessage()); } sysLogEntity.setParams(params); } //设置IP地址 sysLogEntity.setIp(IpHelper.getIpAddr()); // 设置业务数据ID(String类型) String businessId = extractIdFromResult(result); if (businessId != null) { sysLogEntity.setBizId(businessId); } //用户名 Admin admin = adminService.getAdminFromReq(); if (!Objects.isNull(admin)) { sysLogEntity.setUsername(admin.getUsername()); } sysLogEntity.setTime(time); sysLogEntity.setCreateDate(new Date()); //保存系统日志 sysLogService.save(sysLogEntity); return result; } private String extractIdFromResult(Object result) { try { // 如果result为null,直接返回null if (result == null) { return null; } Class<?> clazz = result.getClass(); // 第一层:尝试从result的data字段/getData方法中获取实际数据 Object dataObject = null; try { // 尝试调用getData()方法 java.lang.reflect.Method getDataMethod = clazz.getMethod("getData"); dataObject = getDataMethod.invoke(result); } catch (Exception ignored) { // 如果没有getData方法,尝试直接访问data字段 try { java.lang.reflect.Field dataField = clazz.getDeclaredField("data"); dataField.setAccessible(true); dataObject = dataField.get(result); } catch (Exception ignored2) { // 如果既没有getData方法也没有data字段,直接使用result本身 dataObject = result; } } // 如果data为null,返回null if (dataObject == null) { return null; } // 第二层:从data对象中提取ID Class<?> dataClass = dataObject.getClass(); // 优先使用getter方法 try { java.lang.reflect.Method getIdMethod = dataClass.getMethod("getId"); Object idValue = getIdMethod.invoke(dataObject); if (idValue != null) { return idValue.toString(); } } catch (Exception ignored) { // 如果没有getId方法,尝试直接访问字段 } // 备用方案:直接访问字段 java.lang.reflect.Field[] fields = dataClass.getDeclaredFields(); for (java.lang.reflect.Field field : fields) { if (field.getName().equals("id") || field.getName().endsWith("Id")) { field.setAccessible(true); Object value = field.get(dataObject); if (value != null) { return value.toString(); } } } } catch (Exception e) { log.warn("从返回结果中提取ID失败: {}", e.getMessage()); } return null; } // 过滤可序列化对象的方法 private List<Object> filterSerializableObjects(List<Object> objects) { if (objects == null) return new ArrayList<>(); return objects.stream() .filter(this::isSerializable) .collect(Collectors.toList()); } private Object[] filterSerializableObjects(Object[] objects) { if (objects == null) return new Object[0]; return Arrays.stream(objects) .filter(this::isSerializable) .toArray(); } // 检查对象是否可序列化 private boolean isSerializable(Object obj) { if (obj == null) return true; try { // 尝试快速判断是否为常见不可序列化类型 Class<?> clazz = obj.getClass(); String className = clazz.getName(); // 过滤常见的不可序列化类 if (className.contains("java.util.Collections$") || className.contains("HttpServletRequest") || className.contains("HttpServletResponse") || className.contains("Session") || className.contains("ServletContext") || className.startsWith("org.apache.catalina") || className.startsWith("org.springframework.security")) { return false; } // 简单测试序列化 objectMapper.writeValueAsString(obj); return true; } catch (Exception e) { return false; } } // 获取安全的参数表示 private String getSafeParamRepresentation(Object[] args) { if (args == null || args.length == 0) { return "[]"; } List<String> paramInfos = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if (args[i] != null) { String className = args[i].getClass().getSimpleName(); paramInfos.add("arg" + i + ": " + className); } else { paramInfos.add("arg" + i + ": null"); } } try { return objectMapper.writeValueAsString(paramInfos); } catch (Exception e) { return paramInfos.toString(); } } }
<think>我们使用AOP来实现订单和商品操作日志记录功能。主要思路是:定义日志切面,通过环绕通知或后置通知捕获目标方法的执行,提取操作信息并记录日志。
步骤:
1. 定义操作日志注解,用于标记需要记录日志的方法,并可以传递操作类型、操作对象等信息。
2. 编写切面类,定义切入点(使用自定义注解)和通知(建议使用环绕通知,可以获取方法执行前后信息,以及方法参数)。
3. 在切面中解析注解,获取操作类型(如新增、删除、编辑)和操作对象(订单、商品)。
4. 从方法参数中提取业务ID(如订单ID、商品ID)和操作详情(如修改前后的值)。
5. 记录日志到数据库或其他存储。
具体实现:
1. 定义操作日志注解 `@OperateLog`:
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {
// 操作类型:新增、删除、编辑等
String operateType() default "";
// 操作对象:订单、商品等
String operateObject() default "";
}
```
2. 编写切面类 `OperateLogAspect`:
```java
@Aspect
@Component
public class OperateLogAspect {
// 定义切入点:所有被@OperateLog注解的方法
@Pointcut("@annotation(com.example.demo.annotation.OperateLog)")
public void operateLogPointCut() {}
// 环绕通知
@Around("operateLogPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解
OperateLog operateLog = method.getAnnotation(OperateLog.class);
String operateType = operateLog.operateType();
String operateObject = operateLog.operateObject();
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 获取请求信息(如果需要,可以从HttpServletRequest中获取操作人信息)
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 执行目标方法,并获取返回值(用于获取新增操作的ID等)
Object result = joinPoint.proceed();
// 记录日志
try {
// 提取业务ID:根据不同的操作类型和操作对象,从参数或返回值中提取ID
String businessId = extractBusinessId(operateType, operateObject, args, result);
// 构建操作详情
String operateDetail = buildOperateDetail(operateType, operateObject, args, result);
// 创建操作日志对象
OperateLogEntity log = new OperateLogEntity();
log.setOperateType(operateType);
log.setOperateObject(operateObject);
log.setBusinessId(businessId);
log.setOperateDetail(operateDetail);
log.setOperateTime(new Date());
// 操作人信息(从session或token中获取,这里用默认值)
log.setOperateUserId("default_user_id");
log.setOperateUserName("default_user_name");
// 保存日志(这里可以异步保存,例如使用线程池或消息队列)
saveOperateLog(log);
} catch (Exception e) {
// 日志记录异常不应影响业务
e.printStackTrace();
}
return result;
}
// 提取业务ID的方法
private String extractBusinessId(String operateType, String operateObject, Object[] args, Object result) {
// 根据操作类型和操作对象,从参数或返回值中提取ID
// 例如:新增操作,返回值中可能包含新生成的ID;编辑、删除操作,参数中可能包含ID
// 这里需要根据实际业务方法参数设计来提取
// 假设订单操作的方法中,第一个参数是订单对象(包含ID)或订单ID
// 示例:如果是编辑订单,参数中第一个是订单ID
if ("订单".equals(operateObject)) {
if ("编辑".equals(operateType) || "删除".equals(operateType)) {
// 假设第一个参数是订单ID(String类型)
return (String) args[0];
} else if ("新增".equals(operateType)) {
// 新增操作,返回值可能是订单对象,包含ID
if (result instanceof Order) {
return ((Order) result).getId();
}
}
}
// 其他情况类似,需要根据实际业务调整
return null;
}
// 构建操作详情
private String buildOperateDetail(String operateType, String operateObject, Object[] args, Object result) {
// 根据操作类型和操作对象,构建操作详情
// 例如:编辑操作,可以记录修改前后的值(需要获取修改前的数据,这里可能需要查询数据库,或者方法参数中包含新旧对象)
// 简单做法:记录操作类型和对象,以及业务ID
return String.format("%s了%s,业务ID:%s", operateType, operateObject, extractBusinessId(operateType, operateObject, args, result));
}
// 保存日志到数据库
private void saveOperateLog(OperateLogEntity log) {
// 这里调用日志服务保存
// operateLogService.save(log);
System.out.println("保存操作日志:" + log);
}
}
```
3. 在需要记录日志的方法上添加注解,例如:
```java
@Service
public class OrderService {
// 新增订单
@OperateLog(operateType = "新增", operateObject = "订单")
public Order addOrder(Order order) {
// ... 业务逻辑
return order; // 返回订单,包含生成的ID
}
// 编辑订单
@OperateLog(operateType = "编辑", operateObject = "订单")
public void updateOrder(String orderId, Order newOrder) {
// ... 业务逻辑
}
// 删除订单
@OperateLog(operateType = "删除", operateObject = "订单")
public void deleteOrder(String orderId) {
// ... 业务逻辑
}
}
```
4. 商品操作日志记录同理,只需在商品相关方法上添加注解,并设置对应的操作对象为“商品”。
注意:以上示例中,提取业务ID和构建操作详情的方法需要根据实际业务方法参数和返回值进行调整。对于编辑操作,如果需要记录修改前后的值,可以在方法执行前(在环绕通知中)保存旧值,然后与方法执行后的新值进行比较。
优化:为了减少对业务方法性能的影响,保存日志的操作可以异步执行(如使用线程池或消息队列)。
引用说明:
我们通过自定义注解和切面实现了操作日志的记录,这样可以减少对业务代码的侵入,便于统一管理日志逻辑[^2][^3]。
§§相关问题§§
1. 如何确保在AOP中获取操作人信息?
2. 对于修改操作,如何记录修改前后的变化?
3. 如何实现操作日志的异步存储以提高性能?
4. 操作日志记录失败时,如何保证不影响主业务流程?</think>### Spring AOP 实现订单和商品操作日志记录
#### 一、实现方案概述
使用 Spring AOP 实现操作日志记录的核心步骤:
1. **定义操作日志注解**:标记需要记录日志的方法
2. **创建切面类**:拦截注解方法并记录日志
3. **设计日志实体**:存储操作信息
4. **实现日志详情查看**:通过业务ID查询相关日志
#### 二、核心代码实现
##### 1. 定义操作日志注解
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
// 操作类型:新增/删除/编辑
String operateType();
// 操作对象:订单/商品
String operateObject();
// 业务ID在参数中的位置(从0开始)
int businessIdParamIndex() default 0;
}
```
##### 2. 创建日志切面类
```java
@Aspect
@Component
public class OperationLogAspect {
private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
@Autowired
private OperationLogService logService;
// 切入点:所有带@OperationLog注解的方法
@Pointcut("@annotation(com.example.annotation.OperationLog)")
public void operationLogPointcut() {}
// 后置通知记录日志
@AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
public void recordOperationLog(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog annotation = method.getAnnotation(OperationLog.class);
// 构建日志实体
OperationLogEntity log = new OperationLogEntity();
log.setOperateType(annotation.operateType());
log.setOperateObject(annotation.operateObject());
log.setOperateTime(new Date());
// 提取业务ID(从参数或返回值)
String businessId = extractBusinessId(joinPoint, annotation, result);
log.setBusinessId(businessId);
// 记录操作详情(JSON格式)
log.setOperateDetail(buildOperateDetail(joinPoint));
// 异步保存日志
CompletableFuture.runAsync(() -> logService.saveLog(log));
}
// 提取业务ID
private String extractBusinessId(JoinPoint joinPoint, OperationLog annotation, Object result) {
// 优先从参数获取
Object[] args = joinPoint.getArgs();
if (args.length > annotation.businessIdParamIndex()) {
Object param = args[annotation.businessIdParamIndex()];
if (param instanceof String) return (String) param;
if (param instanceof Long) return String.valueOf(param);
}
// 从返回值获取(如新增操作返回ID)
if (result != null) {
if (result instanceof String) return (String) result;
if (result instanceof BaseEntity) return ((BaseEntity) result).getId();
}
return "UNKNOWN";
}
// 构建操作详情
private String buildOperateDetail(JoinPoint joinPoint) {
Map<String, Object> detailMap = new HashMap<>();
detailMap.put("method", joinPoint.getSignature().getName());
detailMap.put("params", Arrays.toString(joinPoint.getArgs()));
return JSON.toJSONString(detailMap);
}
}
```
##### 3. 日志实体类设计
```java
@Entity
@Table(name = "operation_log")
public class OperationLogEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 操作类型:新增/删除/编辑
private String operateType;
// 操作对象:订单/商品
private String operateObject;
// 关联的业务ID(订单ID/商品ID)
private String businessId;
// 操作详情(JSON格式)
@Column(columnDefinition = "TEXT")
private String operateDetail;
// 操作时间
private Date operateTime;
// 操作人信息(需集成权限系统)
private String operatorId;
private String operatorName;
// Getters and Setters
}
```
##### 4. 业务层使用示例
```java
@Service
public class OrderService {
// 订单新增操作
@OperationLog(operateType = "新增", operateObject = "订单")
public Order createOrder(OrderDTO dto) {
// 业务逻辑...
return order;
}
// 订单编辑操作
@OperationLog(
operateType = "编辑",
operateObject = "订单",
businessIdParamIndex = 0 // 第一个参数是订单ID
)
public void updateOrder(String orderId, OrderUpdateDTO dto) {
// 业务逻辑...
}
}
@Service
public class ProductService {
// 商品删除操作
@OperationLog(
operateType = "删除",
operateObject = "商品",
businessIdParamIndex = 0 // 第一个参数是商品ID
)
public void deleteProduct(String productId) {
// 业务逻辑...
}
}
```
#### 三、日志详情查看实现
```java
@RestController
@RequestMapping("/logs")
public class LogController {
@Autowired
private OperationLogService logService;
// 按业务ID查询相关操作日志
@GetMapping("/byBusiness/{businessId}")
public List<OperationLogEntity> getLogsByBusinessId(
@PathVariable String businessId,
@RequestParam(required = false) String operateType) {
return logService.findByBusinessId(businessId, operateType);
}
// 查看日志详情
@GetMapping("/{logId}")
public OperationLogDetailVO getLogDetail(@PathVariable Long logId) {
OperationLogEntity log = logService.findById(logId);
OperationLogDetailVO vo = new OperationLogDetailVO();
vo.setBasicInfo(log);
vo.setDetailData(JSON.parseObject(log.getOperateDetail()));
return vo;
}
}
```
#### 四、关键优化点
1. **异步日志记录**:使用 `CompletableFuture` 异步保存日志,避免影响主业务流程
2. **业务ID灵活提取**:支持从方法参数或返回值中提取业务ID
3. **操作详情存储**:使用 JSON 格式存储方法参数,便于后续解析
4. **操作人集成**:结合 Spring Security 获取当前用户信息
```java
// 在切面中添加
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
log.setOperatorId(auth.getName());
log.setOperatorName(auth.getPrincipal().toString());
}
```
#### 五、注意事项
1. **异常处理**:添加 `@AfterThrowing` 通知记录操作失败日志
2. **日志脱敏**:对敏感参数(如密码、手机号)进行脱敏处理
3. **性能监控**:记录切面执行时间,确保不影响主业务性能
4. **日志清理**:添加定时任务清理过期日志
> 通过此方案,系统会自动记录所有标记了 `@OperationLog` 的操作,并通过业务ID关联操作对象(订单/商品),实现操作日志的全生命周期管理[^1][^2]。
若依框架二次开发详解controller
### 若依框架 Controller 的二次开发详解
若依框架是一个基于 Spring Boot 和 Vue.js 开发的企业级前后端分离架构平台,其设计目标是快速构建中小型互联网系统。在实际项目中,Controller 层作为前端请求与业务逻辑之间的桥梁,通常需要进行大量的定制化开发以满足不同的需求。
#### 1. **Controller 基础结构**
若依框架的 Controller 主要遵循 RESTful API 设计风格,采用统一返回格式封装响应数据。以下是典型的 Controller 方法结构:
```java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 查询用户列表
*/
@GetMapping("/list")
public AjaxResult list(User user, PageRequest pageRequest) {
startPage(pageRequest);
List<User> userList = userService.selectUserList(user);
return AjaxResult.success(new PageInfo<>(userList));
}
/**
* 新增用户
*/
@PostMapping("/add")
public AjaxResult add(@RequestBody User user) {
return toAjax(userService.insertUser(user));
}
}
```
上述代码展示了如何通过 `@RestController` 注解定义一个控制器类,并利用 `@RequestMapping` 明确 URL 路径映射关系[^2]。
---
#### 2. **参数校验**
为了提高系统的健壮性和用户体验,在接收客户端传参时应加入必要的校验机制。可以通过 Hibernate Validator 提供的标准注解完成字段级别的约束设置。
```java
@Data
public class AddUserDto {
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 0L, message = "年龄不能小于零")
private Integer age;
}
```
当接收到不符合规则的数据时,会自动触发全局异常处理器并返回友好的提示信息给调用方。
---
#### 3. **分页查询增强**
若依默认集成了 MyBatis-Plus 工具库来简化 CRUD 操作流程,同时也支持配合第三方插件如 PageHelper 来实现复杂场景下的分页功能。下面给出一段自定义分页服务的例子:
```java
@Service
public class UserServiceImpl implements IUserService {
@Override
public IPage<User> selectUserPage(PageRequest pageRequest, User queryParam) {
LambdaQueryWrapper<User> wrapper = new QueryWrapper<>();
if (StringUtils.isNotEmpty(queryParam.getUsername())) {
wrapper.like(User::getUsername, queryParam.getUsername());
}
return this.page(
new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize()),
wrapper
);
}
}
```
此片段说明了如何灵活运用动态 SQL 构建条件表达式以及适配不同类型的筛选项。
---
#### 4. **文件上传下载扩展**
针对大文件传输或者批量处理的需求,可以借助 Apache Commons FileUpload 或者 Spring MVC 自带的支持能力来进行改造升级。例如以下展示了一个安全可靠的文档存储方案:
```java
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws IOException {
// 文件大小限制判断
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.getSize() > maxSize) {
throw new CustomException("单个文件不得超过10MB!");
}
// 存储路径拼接
String fileName = UUID.randomUUID().toString();
String filePath = System.getProperty("user.dir") + "/uploads/" + fileName;
Files.copy(file.getInputStream(), Paths.get(filePath), StandardCopyOption.REPLACE_EXISTING);
return AjaxResult.success(fileName);
}
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws MalformedURLException {
Path path = Paths.get(System.getProperty("user.dir"), "uploads", fileName);
Resource resource = new UrlResource(path.toUri());
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + URLEncoder.encode(resource.getFilename(), Charset.defaultCharset()) + "\"");
return ResponseEntity.ok()
.headers(headers)
.contentLength(Files.size(path))
.body(resource);
}
```
这里不仅实现了基本的功能模块,还考虑到了边界情况比如超限检测、随机命名防覆盖等问题。
---
#### 5. **事务管理与幂等性保障**
对于涉及资金变动或者其他敏感操作的任务来说,确保每次提交都能唯一生效至关重要。推荐使用 Redis 分布锁加数据库乐观锁相结合的方式解决并发冲突风险。
```java
@Transactional(rollbackFor = Exception.class)
public void updateStock(String productId, int quantity) {
Product product = productService.findById(productId);
if (product == null || product.getQuantity() < quantity) {
throw new RuntimeException("库存不足");
}
try (Jedis jedis = redisPool.getResource()) {
String lockKey = "lock:" + productId;
boolean acquiredLock = false;
while (!acquiredLock) {
Long result = jedis.setnx(lockKey, String.valueOf(System.currentTimeMillis()));
if (result != null && result.equals(1L)) { // 成功获得锁
jedis.expire(lockKey, 10); // 设置有效时间为10秒防止死锁
acquiredLock = true;
synchronized (this.getClass()) { // 双重锁定模式进一步减少竞争开销
product.decrease(quantity);
productService.save(product);
break;
}
} else {
Thread.sleep(100); // 等待一段时间再尝试重新获取资源占用权
}
}
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
```
该算法综合考量了效率与可靠性两方面因素,适用于大多数高并发环境下的更新作业。
---
#### 6. **日志记录与性能追踪**
最后别忘了为重要环节增加审计痕迹以便后续排查定位问题所在之处。结合 AOP 切面技术和 ELK 日志收集体系能够极大地方便运维人员的工作负担。
```java
@Aspect
@Component
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.ruoyi.common.annotation.Log)")
public void logPointCut() {}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
StopWatch stopwatch = new StopWatch();
try {
stopwatch.start();
Object proceed = point.proceed();
stopwatch.stop();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
log.info("{} {} {} ms", request.getMethod(), request.getRequestURI(), stopwatch.getTotalTimeMillis());
return proceed;
} finally {
stopwatch.close();
}
}
}
```
以上实例体现了良好的编码习惯有助于延长软件生命周期的同时降低维护成本。
---
阅读全文
相关推荐








