仿牛客网社区项目(二十)统一记录日志

全局异常控制日志

需求

  • 帖子模块
  • 评论模块
  • 消息模块

AOP的概念

  • Aspect Oriented Programing,即面向方面(切面)编程。
  • AOP是一种编程思想,是对OOP的补充,可以进一步提高编程的效率。

AOP的术语

AOP的实现

  • AspectJ
    • AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。
    • AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。
  • Spring AOP
    • Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
    • Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点。
    • Spring支持对AspectJ的集成。

Spring AOP

  • JDK动态代理
    • Java提供的动态代理技术,可以在运行时创建接口的代理实例。
    • Spring AOP默认采用此种方式,在接口的代理实例中织入代码。
  • CGLib动态代理
    • 采用底层的字节码技术,在运行时创建子类代理实例。
    • 当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。

1、AOP#

上一节中统一处理Controller的异常,是通过控制器通知,当控制器异常时,统一处理。但是想记录日志,不一定有异常。而拦截器也是针对控制器的。没有对业务组件、数据访问层统一处理。

想对业务层统一记录日志,而统一记录日志是系统功能,不要和业务功能混在一起实现。否则在想对记录日志的位置进行改变时,将会非常麻烦,因为业务bean有很多个,不好一个一个改。

由此引入了AOP的方式,即面向切面编程,切面是一个一个组件。业务Bean是一个一个target。我们要先声明切点的位置,再通知要做什么事。只需要对切面组件编程即可,不需要再进到业务Bean中去改,提升了编程效率。

框架为切面提供了织入的功能,有编译时(运行快)、类装载、运行时织入(满足特殊要求,所有条件都知道,慢一些)。

Aspect切面:

  • 注解@Component @Aspect
  • 声明切点的位置@Pointcut(切点的位置:返回值 包.类.方法.参数) pointcut()
  • 通知具体逻辑,5个注解@Before @After AfterReturning @AfterThrowing @Around
    Target: 是业务Bean

AOP实现有两种:
AspectJ和Spring AOP。一般用后者即可。它是运行时织入通过代理的方式只在方法处有连接点

Spring AOP 有两种动态代理方式:
为什么要代理:在织入切面代码时,不在原来的实例中织入,而是在代理对象中织入。调用时也是调用代理对象,而不是调用原始对象。容器调用对象,如果有AOP作用,就调用代理对象。

JDK动态代理(自带的)和CGLib动态代理(第三方)。前者生成接口的实现类,在代理实例中织入,要求必须有业务接口;后者在业务不存在接口时,创建子类实例以实现代理、

2、使用演示#

AlphaAspect

//@Component
//@Aspect
public class AlphaAspect {

    // 切点:返回值 包 类 方法 参数
    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    public void pointcut() {

    }

    // 通知:连接点前、后、返回值、抛异常、连接点前后。
    @Before("pointcut()")// 针对该切点pointcut()
    public void before() {
        System.out.println("before");
    }

    @After("pointcut()")
    public void after() {
        System.out.println("after");
    }

    @AfterReturning("pointcut()")
    public void afterRetuning() {
        System.out.println("afterRetuning");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 参数:连接点
        System.out.println("around before");
        Object obj = joinPoint.proceed();// 连接点调用目标组件的方法,返回目标组件的返回值
        System.out.println("around after");
        return obj;
    }

}

3、统一记录日志#

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.nowcoder.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));

    }
}
posted @   卷皇  阅读(424)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示
主题色彩