异常统一处理
功能分析
在 Spring 项目中,我们可以通过如下三种常见方案来实现全局统一异常处理。
- 基于 SpringBoot 的全局统一异常处理,需要实现 ErrorController 接口。
- 基于 Spring AOP 实现全局统一异常处理。
- 基于 @ControllerAdvice 注解实现 Controller 层全局统一异常处理。
使用统一异常处理的优点:
- 标准统一的返回结果,系统交互更加友好
- 有效防止业务异常没有被捕获的情况
- 代码更加干净简洁,不需要开发者自己定义维护异常
本项目采用@ControllerAdvice注解的方式实现异常统一处理;
@ControllerAdvice作用于类上,使用该注解可以实现三个方面的功能:1. 全局异常处理;2. 全局数据绑定;3. 全局数据预处理。在项目中使用这个注解可以帮我们简化很多工作,它是 SpringMVC 提供的功能,并且在 SpringBoot 中也可以直接使用。
在进行全局异常处理时,需要配合 @ExceptionHandler 注解使用。
-
@ExceptionHandler
- 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常
-
@ModelAttribute
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数。
-
@DataBinder
- 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。
在 controller 包下新建 advice 包,添加 ExceptionAdvice 类,代码如下:
@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);
@ExceptionHandler({Exception.class})
public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.error("服务器发生异常: " + e.getMessage());
for (StackTraceElement element : e.getStackTrace()) {
logger.error(element.toString());
}
String xRequestedWith = request.getHeader("x-requested-with");
//异步请求
if ("XMLHttpRequest".equals(xRequestedWith)) {
response.setContentType("application/plain;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
} else {
response.sendRedirect(request.getContextPath() + "/error");
}
}
}
在HomeController中添加getErrorPage方法,代码如下:
@GetMapping("/error")
public String getErrorPage() {
return "/error/500";
}
功能测试
日志记录
功能分析
- AOP(面向切面编程)
很抽象的一个概念,AOP是一种编程思想,是对OOP的补充,它和OOP是一种互补的状态并非竞争状态,可以进一步提高编程的效率。
- Spring AOP
- JDK动态代理
ava提供的动态代理技术,可以在运行时创建接口的代理实例。
Spring AOP默认采用此种方式,在接口的代理实例中织入代码。 - CGLib动态代理
采用底层的字节码技术,在运行时创建子类代理实例。
当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。
- JDK动态代理
在controller包下新建aspectt包,新建ServiceLogAspect类,代码如下:
@Component
@Aspect
public class ServiceLogAspect {
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
@Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 用户[1.2.3.4],在[xxx],访问了[com.gerrard.community.service.xxx()].
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ip = request.getRemoteHost();
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));
}
}
功能测试
Redis
为什么用Redis
- 读写性能优异
- 数据类型丰富
Redis 支持二进制案例的String,Lists,Hashes,Sets及Ordered Sets数据类型操作 - 原子性:Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 丰富的特性
Redis支持publish/subscribe,通知,key过期等特性 - 持久化:Redis支持RDB,AOF等持久化方式
- 发布订阅
- 分布式
为什么Redis是单线程还那么快
Redis一般使用场景
- 热点数据的存储
- 限时业务的运用
- 计数器相关问题
- 分布式锁
Redis五大数据类型
spring整合Redis
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 参数配置
- 配置数据库参数
# RedisProperties spring.redis.database=11 spring.redis.host=localhost spring.redis.port=6379
- 编写配置类,构造 RedisTemplate
在config包下新建RedisConfig类,代码如下:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 设置key的序列化方式 template.setKeySerializer(RedisSerializer.string()); // 设置value的序列化方式 template.setValueSerializer(RedisSerializer.json()); // 设置hash的key序列化方式 template.setHashKeySerializer(RedisSerializer.string()); // 设置hash的value序列化方式 template.setHashValueSerializer(RedisSerializer.json()); template.afterPropertiesSet();; return template; } }
- 访问Redis
redisTemplate.opsForValue()
redisTemplate.opsForHash()
redisTemplate.opsForList()
redisTemplate.opsForSet()
redisTemplate.opsForZSet()
添加 RedisTests 测试类,各种测试的代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testStrings() {
String redisKey = "test:count";
System.out.println("result:");
redisTemplate.opsForValue().set(redisKey, 1);
System.out.println(redisTemplate.opsForValue().get(redisKey));
System.out.println(redisTemplate.opsForValue().increment(redisKey));
System.out.println(redisTemplate.opsForValue().decrement(redisKey));
}
@Test
public void testHashes() {
String redisKey = "test:user";
redisTemplate.opsForHash().put(redisKey, "id", 1);
redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");
System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
}
@Test
public void testLists() {
String redisKey = "test:ids";
redisTemplate.opsForList().leftPush(redisKey, 101);
redisTemplate.opsForList().leftPush(redisKey, 102);
redisTemplate.opsForList().leftPush(redisKey, 103);
System.out.println(redisTemplate.opsForList().size(redisKey));
System.out.println(redisTemplate.opsForList().index(redisKey, 0));
System.out.println(redisTemplate.opsForList().range(redisKey,0, 2));
System.out.println(redisTemplate.opsForList().leftPop(redisKey));
System.out.println(redisTemplate.opsForList().leftPop(redisKey));
System.out.println(redisTemplate.opsForList().leftPop(redisKey));
}
@Test
public void testSet() {
String redisKey = "test:teacher";
redisTemplate.opsForSet().add(redisKey,"张三", "李四", "王五", "赵六");
System.out.println(redisTemplate.opsForSet().size(redisKey));
System.out.println(redisTemplate.opsForSet().pop(redisKey));
System.out.println(redisTemplate.opsForSet().members(redisKey));
}
@Test
public void testSortedSet() {
String redisKey = "test:students";
redisTemplate.opsForZSet().add(redisKey, "张三", 10);
redisTemplate.opsForZSet().add(redisKey, "李四", 20);
redisTemplate.opsForZSet().add(redisKey, "王五", 40);
redisTemplate.opsForZSet().add(redisKey, "赵六", 30);
System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
System.out.println(redisTemplate.opsForZSet().score(redisKey,"张三"));
System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey,"张三"));
System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey,0, 2));
}
@Test
public void testKeys() {
redisTemplate.delete("test:user");
System.out.println(redisTemplate.hasKey("test:user"));
redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
}
// 多次访问同一个key
@Test
public void testBoundOperations() {
String redisKey = "test:count";
BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
operations.increment();
operations.increment();
operations.increment();
operations.increment();
operations.increment();
operations.increment();
System.out.println(operations.get());
}
// 编程式事务
@Test
public void testTransactional() {
Object obj = redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String redisKey = "test:tx";
operations.multi();
operations.opsForSet().add(redisKey, "aa");
operations.opsForSet().add(redisKey, "bb");
operations.opsForSet().add(redisKey, "cc");
return operations.exec();
}
});
System.out.println(obj);
}
}