四、AOP 面向切面编程
4.1 AOP 概念
AOP(Aspect Oriented Programming):通过预编译和运行期动态代理实现程序功能统一维护的技术,将横切关注点(如日志、事务、安全)与业务逻辑分离。
核心概念:
切面(Aspect):横切关注点的模块化
连接点(Join Point):程序执行过程中的特定点
通知(Advice):切面在特定连接点执行的动作
切入点(Pointcut):匹配连接点的表达式
织入(Weaving):将切面应用到目标对象的过程
引入(Introduction):为现有类添加新方法或属性
4.2 基于 XML 的 AOP 配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="...">
<!-- 定义切面类 -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
<bean id="securityAspect" class="com.example.aspect.SecurityAspect"/>
<!-- AOP 配置 -->
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut id="serviceMethods"
expression="execution(* com.example.service.*.*(..))"/>
<aop:pointcut id="daoMethods"
expression="execution(* com.example.dao.*.*(..))"/>
<!-- 定义切面 -->
<aop:aspect ref="loggingAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="serviceMethods"/>
<aop:after method="afterMethod" pointcut-ref="serviceMethods"/>
<aop:after-returning method="afterReturning"
pointcut-ref="serviceMethods"
returning="result"/>
<aop:after-throwing method="afterThrowing"
pointcut-ref="serviceMethods"
throwing="ex"/>
<aop:around method="aroundMethod" pointcut-ref="serviceMethods"/>
</aop:aspect>
<aop:aspect ref="securityAspect">
<aop:before method="checkSecurity"
pointcut="@annotation(com.example.annotation.RequiresAuth)"/>
</aop:aspect>
</aop:config>
</beans>
4.3 基于注解的 AOP 配置
java
@Aspect
@Component
@Order(1)
public class LoggingAspect {
// 定义切入点表达式
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("within(@org.springframework.stereotype.Repository *)")
public void repositoryMethods() {}
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggableMethods() {}
// 前置通知
@Before("serviceMethods() && args(id,..)")
public void beforeMethod(JoinPoint joinPoint, Long id) {
System.out.println("方法执行前: " + joinPoint.getSignature().getName());
System.out.println("参数: " + Arrays.toString(joinPoint.getArgs()));
System.out.println("ID参数: " + id);
}
// 后置通知
@After("serviceMethods()")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("方法执行后: " + joinPoint.getSignature().getName());
}
// 返回通知
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.err.println("方法异常: " + ex.getMessage());
}
// 环绕通知
@Around("loggableMethods()")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("开始执行: " + pjp.getSignature().getName());
try {
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
System.out.println("执行完成,耗时: " + elapsed + "ms");
return result;
} catch (Exception e) {
System.err.println("执行失败: " + e.getMessage());
throw e;
}
}
}
4.4 切入点表达式
java
@Aspect
@Component
public class PointcutExpressions {
// 匹配方法执行
@Pointcut("execution(public * com.example.service.*.*(..))")
public void publicMethod() {}
// 匹配返回类型
@Pointcut("execution(String com.example.service.*.*(..))")
public void stringReturnType() {}
// 匹配参数
@Pointcut("execution(* com.example.service.*.*(Long, ..))")
public void firstParamLong() {}
// 匹配包和类
@Pointcut("within(com.example.service.*)")
public void inServicePackage() {}
@Pointcut("within(com.example.service.UserServiceImpl)")
public void inUserService() {}
// 匹配 Bean 名称
@Pointcut("bean(userService)")
public void userServiceBean() {}
@Pointcut("bean(*Service)")
public void allServiceBeans() {}
// 匹配注解
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceAnnotated() {}
@Pointcut("@annotation(com.example.annotation.Cacheable)")
public void cacheableMethods() {}
@Pointcut("@args(com.example.annotation.Validated)")
public void validatedParams() {}
// 组合切入点
@Pointcut("publicMethod() && inServicePackage()")
public void publicServiceMethods() {}
@Pointcut("publicMethod() && !cacheableMethods()")
public void publicNonCacheableMethods() {}
// 使用多个切入点
@Before("publicServiceMethods() || inUserService()")
public void combinedAdvice() {}
}
五、数据访问
5.1 JDBC 模板
xml
<!-- 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maximumPoolSize" value="10"/>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
java
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// 查询单条记录
@Override
public User selectById(Long id) {
String sql = "SELECT id, username, age FROM user WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
}
// 查询列表
@Override
public List<User> selectAll() {
String sql = "SELECT id, username, age FROM user";
return jdbcTemplate.query(sql, new UserRowMapper());
}
// 自定义 RowMapper
private static class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setAge(rs.getInt("age"));
return user;
}
}
// 使用 Lambda 表达式
public List<User> selectByAge(int age) {
String sql = "SELECT id, username, age FROM user WHERE age > ?";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setAge(rs.getInt("age"));
return user;
}, age);
}
// 使用 NamedParameter
public List<User> selectByCondition(String username, Integer age) {
String sql = "SELECT id, username, age FROM user WHERE 1=1 " +
"AND (:username IS NULL OR username LIKE :username) " +
"AND (:age IS NULL OR age = :age)";
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("username", username != null ? "%" + username + "%" : null);
params.addValue("age", age);
return namedParameterJdbcTemplate.query(sql, params, new BeanPropertyRowMapper<>(User.class));
}
// 插入数据
@Override
public int insert(User user) {
String sql = "INSERT INTO user (username, age, create_time) VALUES (?, ?, NOW())";
return jdbcTemplate.update(sql, user.getUsername(), user.getAge());
}
// 插入并返回主键
public long insertAndGetId(User user) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO user (username, age) VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS
);
ps.setString(1, user.getUsername());
ps.setInt(2, user.getAge());
return ps;
}, keyHolder);
return keyHolder.getKey().longValue();
}
// 批量插入
public int[] batchInsert(List<User> users) {
String sql = "INSERT INTO user (username, age) VALUES (?, ?)";
return jdbcTemplate.batchUpdate(sql, users, 100, (ps, user) -> {
ps.setString(1, user.getUsername());
ps.setInt(2, user.getAge());
});
}
// 批量更新
public int[] batchUpdate(List<User> users) {
String sql = "UPDATE user SET username = ?, age = ? WHERE id = ?";
return jdbcTemplate.batchUpdate(sql, users, (ps, user) -> {
ps.setString(1, user.getUsername());
ps.setInt(2, user.getAge());
ps.setLong(3, user.getId());
});
}
// 查询 Map 结果
public Map<String, Object> selectAsMap(Long id) {
String sql = "SELECT id, username, age FROM user WHERE id = ?";
return jdbcTemplate.queryForMap(sql, id);
}
// 查询单值
public int countByAge(int age) {
String sql = "SELECT COUNT(*) FROM user WHERE age > ?";
return jdbcTemplate.queryForObject(sql, Integer.class, age);
}
// 执行 DDL
public void createTable() {
String sql = "CREATE TABLE IF NOT EXISTS temp (id INT, name VARCHAR(100))";
jdbcTemplate.execute(sql);
}
}
5.2 事务管理
// 声明式事务管理
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 使用 @Transactional
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private LogDao logDao;
// 默认事务传播行为 REQUIRED
@Override
public void createUser(User user) {
userDao.insert(user);
logDao.insertLog("创建用户: " + user.getUsername());
// 抛出异常,事务回滚
if (user.getAge() < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
}
// 只读事务,优化性能
@Transactional(readOnly = true)
@Override
public User getUser(Long id) {
return userDao.selectById(id);
}
// 指定回滚异常
@Transactional(rollbackFor = {SQLException.class, DataAccessException.class},
noRollbackFor = IllegalArgumentException.class)
public void updateUser(User user) throws SQLException {
userDao.update(user);
if (user.getAge() > 150) {
throw new IllegalArgumentException("年龄无效"); // 不回滚
}
if (user.getAge() < 0) {
throw new SQLException("年龄无效"); // 回滚
}
}
// 设置超时时间(秒)
@Transactional(timeout = 30)
public void batchInsert(List<User> users) {
for (User user : users) {
userDao.insert(user);
}
}
// 设置隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)
public User getUserWithLock(Long id) {
return userDao.selectForUpdate(id);
}
// 编程式事务管理
@Autowired
private TransactionTemplate transactionTemplate;
public void transferMoney(final Long fromId, final Long toId, final BigDecimal amount) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
User from = userDao.selectForUpdate(fromId);
User to = userDao.selectForUpdate(toId);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
userDao.update(from);
userDao.update(to);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
}
}
// 事务传播行为
@Service
public class PropagationDemo {
@Autowired
private UserService userService;
// REQUIRED:支持当前事务,如果没有则新建(默认)
@Transactional(propagation = Propagation.REQUIRED)
public void required() {}
// SUPPORTS:支持当前事务,如果没有则以非事务方式执行
@Transactional(propagation = Propagation.SUPPORTS)
public void supports() {}
// MANDATORY:支持当前事务,如果没有则抛出异常
@Transactional(propagation = Propagation.MANDATORY)
public void mandatory() {}
// REQUIRES_NEW:新建事务,挂起当前事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNew() {}
// NOT_SUPPORTED:以非事务方式执行,挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupported() {}
// NEVER:以非事务方式执行,如果有事务则抛出异常
@Transactional(propagation = Propagation.NEVER)
public void never() {}
// NESTED:如果当前存在事务,则在嵌套事务内执行
@Transactional(propagation = Propagation.NESTED)
public void nested() {}
}
5.3 ORM 集成
Spring + Hibernate 配置:
xml
<!-- Hibernate 配置 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.example.entity"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="hibernateTransactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
java
@Repository
@Transactional
public class UserHibernateDao {
@Autowired
private SessionFactory sessionFactory;
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
public User findById(Long id) {
return getCurrentSession().get(User.class, id);
}
@SuppressWarnings("unchecked")
public List<User> findAll() {
Criteria criteria = getCurrentSession().createCriteria(User.class);
return criteria.list();
}
public void save(User user) {
getCurrentSession().save(user);
}
public void update(User user) {
getCurrentSession().update(user);
}
public void delete(User user) {
getCurrentSession().delete(user);
}
public User findByUsername(String username) {
Query query = getCurrentSession().createQuery("from User where username = :username");
query.setParameter("username", username);
return (User) query.uniqueResult();
}
@SuppressWarnings("unchecked")
public List<User> findByAgeRange(int minAge, int maxAge) {
Query query = getCurrentSession().createQuery(
"from User where age between :minAge and :maxAge");
query.setParameter("minAge", minAge);
query.setParameter("maxAge", maxAge);
return query.list();
}
public int updateAgeBatch(int newAge, int oldAge) {
Query query = getCurrentSession().createQuery(
"update User set age = :newAge where age = :oldAge");
query.setParameter("newAge", newAge);
query.setParameter("oldAge", oldAge);
return query.executeUpdate();
}
}
六、Web 开发
6.1 Spring MVC 架构
Spring MVC 请求处理流程:
请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller
↑ ↓
响应 ← ViewResolver ← View ← ModelAndView ← 处理结果 ← 业务逻辑
6.2 Spring MVC 配置
XML 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="...">
<!-- 开启注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 组件扫描 -->
<context:component-scan base-package="com.example.controller"/>
</beans>
Java 配置:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
// 配置静态资源
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/")
.setCachePeriod(3600);
}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/admin/**")
.excludePathPatterns("/login", "/register");
registry.addInterceptor(new PerformanceInterceptor())
.addPathPatterns("/**");
}
// 配置跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
// 配置消息转换器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jacksonConverter =
new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(new ObjectMapper()
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.setSerializationInclusion(JsonInclude.Include.NON_NULL));
converters.add(jacksonConverter);
converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
}
// 配置异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new CustomExceptionResolver());
}
}
6.3 Controller 开发
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET 请求
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
}
// 多路径变量
@GetMapping("/dept/{deptId}/user/{userId}")
public User getUserByDept(@PathVariable Long deptId, @PathVariable Long userId) {
return userService.findByDeptAndId(deptId, userId);
}
// 请求参数
@GetMapping
public PageResult<User> getUsers(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
return userService.findByPage(page, size, keyword);
}
// 请求参数绑定到对象
@GetMapping("/search")
public List<User> searchUsers(UserQuery query) {
return userService.findByCondition(query);
}
// POST 请求 - JSON 数据
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@Valid @RequestBody User user) {
return userService.create(user);
}
// 表单数据
@PostMapping(value = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public User createUserForm(User user) {
return userService.create(user);
}
// 文件上传
@PostMapping("/avatar")
public String uploadAvatar(@RequestParam("file") MultipartFile file) throws IOException {
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
Path path = Paths.get("/uploads/avatars", fileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return fileName;
}
// PUT 请求
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @Valid @RequestBody User user) {
user.setId(id);
return userService.update(user);
}
// PATCH 请求(部分更新)
@PatchMapping("/{id}")
public User patchUser(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.patch(id, updates);
}
// DELETE 请求
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
// 返回 JSONP
@GetMapping("/jsonp")
public JSONPObject getUserJsonp(@RequestParam String callback) {
User user = userService.findById(1L);
return new JSONPObject(callback, user);
}
// 响应头设置
@GetMapping("/header")
public ResponseEntity<User> getUserWithHeader() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Custom-Header", "CustomValue");
headers.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS));
return ResponseEntity.ok().headers(headers).body(userService.findById(1L));
}
// 异步请求
@GetMapping("/async")
public Callable<User> asyncUser() {
return () -> {
Thread.sleep(2000);
return userService.findById(1L);
};
}
// SSE 推送
@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamEvents() {
SseEmitter emitter = new SseEmitter();
// 异步推送事件
return emitter;
}
}
6.4 参数绑定与数据校验
// 实体类
public class User {
@NotNull(message = "ID不能为空", groups = {UpdateGroup.class})
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,20}$",
message = "密码必须包含字母和数字,长度6-20")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Past(message = "生日必须是过去日期")
private Date birthday;
@Future(message = "过期时间必须是将来日期")
private Date expireTime;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@AssertTrue(message = "必须同意协议")
private Boolean agree;
// 分组校验接口
public interface CreateGroup {}
public interface UpdateGroup {}
}
// Controller 中使用校验
@RestController
public class UserController {
// 普通校验
@PostMapping("/users")
public Result createUser(@Valid @RequestBody User user) {
return Result.success(userService.save(user));
}
// 分组校验
@PutMapping("/users")
public Result updateUser(@Validated(User.UpdateGroup.class) @RequestBody User user) {
return Result.success(userService.update(user));
}
// 路径变量校验
@GetMapping("/users/{id}")
public Result getUser(@PathVariable @Min(1) Long id) {
return Result.success(userService.findById(id));
}
// 自定义校验
@GetMapping("/search")
public Result searchUsers(@Valid UserQuery query) {
return Result.success(userService.search(query));
}
}
// 自定义校验注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true;
}
return PHONE_PATTERN.matcher(value).matches();
}
}
// 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleValidationException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<String> errors = result.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
return Result.error(400, "参数校验失败", errors);
}
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBindException(BindException ex) {
return Result.error(400, "参数绑定失败", ex.getMessage());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleConstraintViolation(ConstraintViolationException ex) {
return Result.error(400, "参数校验失败", ex.getMessage());
}
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException ex) {
return Result.error(ex.getCode(), ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleException(Exception ex) {
log.error("系统异常", ex);
return Result.error(500, "系统内部错误");
}
}
6.5 拦截器与过滤器
// 拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 请求处理前执行
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
response.sendRedirect("/login");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
// 请求处理后,视图渲染前执行
if (modelAndView != null) {
modelAndView.addObject("currentUser", request.getSession().getAttribute("user"));
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// 视图渲染后执行
if (ex != null) {
log.error("请求处理异常", ex);
}
}
}
// 性能监控拦截器
@Component
public class PerformanceInterceptor implements HandlerInterceptor {
private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
startTime.set(System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long elapsed = System.currentTimeMillis() - startTime.get();
startTime.remove();
if (elapsed > 1000) {
log.warn("慢请求: {} {} - 耗时: {}ms",
request.getMethod(), request.getRequestURI(), elapsed);
}
}
}
// 过滤器
@Component
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
long start = System.currentTimeMillis();
try {
chain.doFilter(request, response);
} finally {
long elapsed = System.currentTimeMillis() - start;
log.info("请求: {} {} - 耗时: {}ms",
req.getMethod(), req.getRequestURI(), elapsed);
}
}
}
// 配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private PerformanceInterceptor performanceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(performanceInterceptor)
.addPathPatterns("/**");
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/login", "/admin/register")
.order(1);
}
}