Spring学习知识点大全(二)

简介: 教程来源 https://app-aaqhxe29haf5.appmiaoda.com 本节详解Spring AOP面向切面编程与数据访问、Web开发核心内容:涵盖AOP概念(切面、通知、切入点等)、XML/注解两种配置方式、JDBC模板、声明式/编程式事务管理、Hibernate集成,以及Spring MVC架构、控制器开发、参数校验、拦截器与全局异常处理。

四、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);
    }
}

来源:
https://app-aaqhxe29haf5.appmiaoda.com

相关文章
|
4天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10686 60
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
4天前
|
人工智能 IDE API
2026年国内 Codex 安装教程和使用教程:GPT-5.4 完整指南
Codex已进化为AI编程智能体,不仅能补全代码,更能理解项目、自动重构、执行任务。本文详解国内安装、GPT-5.4接入、cc-switch中转配置及实战开发流程,助你从零掌握“描述需求→AI实现”的新一代工程范式。(239字)
2967 126
|
1天前
|
人工智能 自然语言处理 供应链
【最新】阿里云ClawHub Skill扫描:3万个AI Agent技能中的安全度量
阿里云扫描3万+AI Skill,发现AI检测引擎可识别80%+威胁,远高于传统引擎。
1188 1
|
10天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
2535 6
|
24天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
24315 122

热门文章

最新文章