6.统一记录日志

能否使用控制器通知来进行日志的统一记录,答案是不行的。因为控制器通知是在控制器发生异常的时候才统一的处理,而这里的统一记录日志并不是只在发生异常实现,平时不发生异常也要记录日志,所以这个时候控制器通知就不太管用了。

那能否使用拦截器呢,拦截器也是针对控制器的一个处理,那我们记录日志未必是对控制器进行记录,也可能是针对业务组件以及数据访问层进行日志记录。

目前有帖子模块,评论模块,将来越来越多service,想要对所有帖子所有方法记日志,那传统的方法我们是把记录日志的代码封装到一个组件,然后在不同的service里的方法去调用它,这样肯定是可以解决问题的,

 不过它存在弊端:这个业务组件里的方法主要是用来处理业务的,而在处理业务的同时,又需要去加入记录日志的需求,而记录日志又不是业务需求,它属于系统需求(很多功能都具备的一种需求),所以我们在业务方法里耦合了系统需求,这样是有很大坏处:比如有一天系统需求发生变化,不想在前面记日志想在后面记日志,或者抛异常时候记日志,一旦发生变化要很大改动。(所以我们需要将像记录日志这样的系统需求拆分出去,单独去实现而不是硬编码,而AOP就可以实现这种技术)。

AOP(面向切面编程)

很抽象的一个概念,AOP是一种编程思想,是对OOP的补充,它和OOP是一种互补的状态并非竞争状态,可以进一步提高编程的效率。

要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

 项目中有很多业务模块,而每一个业务模块都有相同的系统需求,比如对它们做统一的记录日志、权限检查以及事务管理。而使用AOP的时候我们只需要单独定义一个组件,这个组件不会和业务组件发生任何直接的关系,我们不用去业务组件里面调用它,所以我们额外定义了系统组件,就好像这个组件横跨了多个业务组件,横向扩展业务组件的需求,所以为面向方面或者切面编程,变成的角度是面向这个横切组件。

AOP的术语

 这里还是先给出一个比较专业的概念定义:

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

然后举一个容易理解的例子:
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系:
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?

  • Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
  • Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
  • Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
  • Aspect::Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.
     

AOP的实现

AspectJ:

  • AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。
  • AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。

Spring AOP(不是一个全面的解决方案而是一个高性价比的解决方案):

  • Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
  • Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点。
  • Spring支持对AspectJ的集成。

Spring AOP运行时zhi入程序采用代理对象

  1. JDK动态代理:

Java提供的动态代理技术,可以在运行时创建接口的代理实例。

Spring AOP默认采用此种方式,在接口的代理实例中织入代码。

       2.CGLib动态代理:

采用底层的字节码技术,在运行时创建子类代理实例。

当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。

AOP代码演示
@Component;交给Spring容器管理,@Aspect表示这是一个方面组件,而不是一个简单的组件。

定义切点@Pointcut, 快速筛选出一切想要的连接点。

定义通知:5类通知:@Before @After @AfterReturning @AfterThrowing @around

@Component
@Aspect
public class AlphaAspect {
    //定义连接点
    //所有的业务组件的所有的方法所有的参数以及所有的返回值
    @Pointcut("execution(* com.newcoder.community.service.*.*(..))")
    public void pointcut(){
    }
    //定义通知:在连接点的开始织入代码
    @Before("pointcut()")
    public void before(){
        System.out.println("before");
    }
 
    @After("pointcut()")
    public void after(){
        System.out.println("after");
    }
 
    //在有了返回值之后织入代码,处理一些逻辑
    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    //在抛出异常时候织入代码
    @AfterThrowing("pointcut()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    // 同时在方法的前后都织入逻辑
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //joinPoint表示程序织入的部位
        //目标方法调用之前的处理逻辑
        System.out.println("around before");
        //调用目标组件的方法(可能有返回值)
        Object obj = joinPoint.proceed();
        //目标方法调用之后的处理逻辑
        System.out.println("around after");
        return obj;
    }
 
}

针对每一个业务组件,相应的方法都会被触发,且业务组件中的代码未做任何修改,降低耦合度。

利用AOP完成项目记录日志的功能

想在程序一开始记录日志,格式:“用户XXX(ip地址,因为可能有用户没登录)在某时刻访问了某功能”

在每一个业务组件方法调用之前(前置通知),

统一日志管理采用AOP的思想在内一个方法内都织入记录日志的行为
很简单Spring提供了@Aspect注解只要在一个类上加上这个注解就可以实现该功能

@Component
@Aspect
public class ServiceLogAspect {
//实例化logger
    private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);

//1.定义切点
    // 第一个*:方法的返回值
    // com.nowcoder.community.service:包名
    @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()].
       
        // 用户的IP可以通过request获取,可以利用RequestContextHolder这个工具类
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        // IP地址和时间的获取
        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));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值