观察者模式优化代码

你是否老是在觉得自己的代码不够简洁美观?

日志记录写在业务方法,与业务耦合让你糟心?

不想引用中间件付出额外成本,又想让代码变得优秀?

请你好好的阅读这篇文章,相信会给你带来收获 !献给优秀的你!

在阅读业务代码之前,我们先来了解几个概念

观察者模式: 

观察者(Observer)模式中包含两种对象:

分别是目标对象和观察者对象。在目标对象和观察者对象间存在着一种一对多的对应关系,当这个目标对象的状态发生变化时,所有依赖于它的观察者对象都会得到通知并执行它们各自特有的行为。

ApplicationContextAware接口:

在我们开发Spring项目时,不管是使用别人的开源框架,还是使用自己公司框架,或者是自己搭建框架的时候,经常会见到ApplicationContextAware这个接口

那么这个接口的作用是什么呢?

这个接口其实就是获取Spring容器的Bean,Spring容器会检测容器中的所有Bean,如果发现某个Bean实现了ApplicationContextAware接口,Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContextAware()方法,调用该方法时,会将容器本身作为参数传给该方法

DisposableBean接口:

该接口的作用是:允许在容器销毁该bean的时候获得一次回调。DisposableBean接口也只规定了一个方法:destroy();

AOP技术:

aop 意为面向切面编程,本质 是由反射 + 预编译技术实现的

这些大部分小伙伴应该都知道,可是它到底可以应用在什么场景呢?你有没有真正的使用呢?

本文主要讲得就是 AOP在我们做日志记录时如何运用

正文

Spring其实提供了实现观察者模式,不知道小伙伴你有没有用过 Spring的观察者模式呢?

ApplicationContext.publishEvent 是Spring提供的解耦的一种方式。同样可以使用 MQ 组件 / 线程池 代替。

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口来提供的

通过ApplicationContext的publishEvent()方法发布到ApplicationListener;  

在这里包含三个角色:被发布的事件,事件发布者,事件的监听者。 
事件发布者在发布事件的时候->通知事件的监听者。 

1.要发布的事件

import com.oppo.cvs.web.springContextHolder.entity.Test;
import org.springframework.context.ApplicationEvent;

/**
 * @author 秦书金
 */
public class TestEvent extends ApplicationEvent {
	/**
	 * @param source 事件监听实体
	 */
	public TestEvent(Test source) {
		super(source);
	}

}

2.发布事件

核心工具类 

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

/**
 * @author qinshujin
 * Spring上下文保持器
 * 可参考 Spring官网文档
 * https://docs.spring.io/spring-framework/docs/5.2.19.RELEASE/spring-framework-reference/core.html#context-functionality-events
 */
@Slf4j
@Service
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

	private static ApplicationContext applicationContext = null;

	/**
	 * 取得存储在静态变量中的ApplicationContext.
	 */
	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * 实现ApplicationContextAware接口, 注入Context到静态变量中.
	 */
	@Override
	public void setApplicationContext(ApplicationContext context) {
		SpringContextHolder.applicationContext = context;
	}

	/**
	 * 清除SpringContextHolder中的ApplicationContext为Null.
	 */
	public static void clearHolder() {
		if (log.isDebugEnabled()) {
			log.debug("Clear SpringContextHolder ApplicationContext:{}",applicationContext);
		}
		applicationContext = null;
	}

	/**
	 * @param event 发布事件
	 */
	public static void publishEvent(ApplicationEvent event) {
		if (applicationContext == null) {
			return;
		}
		applicationContext.publishEvent(event);
	}

	/**
	 * 实现DisposableBean接口, 在Context关闭时清理静态变量.
	 */
	@Override
	@SneakyThrows(value = NullPointerException.class)
	public void destroy() {
		SpringContextHolder.clearHolder();
	}

}

发布事件案例

@Test
    public void test(){
        Test test = new Test();
        test.setMsg("测试信息");
        test.setUserName("测试数据");
        SpringContextHolder.publishEvent(new TestEvent(test));
    }

事件实体

import lombok.Data;
@Data
public class Test {
    private String msg;
    private String userName;
}

3.监听事件

import com.oppo.cvs.web.springContextHolder.entity.Test;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * @author qinshujin 监听事件
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class TestListener {
	/**
     * @Async 异步处理  可替换 @Transactional 同步处理 默认同步处理
	 * @Order 加载配置顺序
	 * @EventListener(classes = TestEvent.class) 监听具体事件
	 * @param event 事件
	 */
	@Async
	@Order
	@EventListener(classes = TestEvent.class)
	public void receiveTestEvent(TestEvent event) {
		Test test = (Test) event.getSource();
		log.info("监听事件测试 :{}",test);
	}

}

Aop

整体看完,是不是觉得有这个工具类,就很简单就能把一些日志代码抽离出业务呢?

好像还是不行,有这个工具类,我还是没法获得参数,这该如何解决呢?

那就写一个Aop切面,把我们需要的参数获取出来吧!

目前获取Aop切面我这里介绍两种常用方式

1.自定义注解 + 注解Aop切面

注解实现

 import java.lang.annotation.*;

/**
 * @author qinshujin
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
}

切面拦截实现

import com.oppo.cvs.web.springContextHolder.annotation.SysLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 操作日志使用spring event异步入库
 * @author qinshujin
 */
@Aspect
@Component
@EnableAspectJAutoProxy
public class SysLogAspect {

	/**
	 * 注解执行拦截
	 */
	@Pointcut("@annotation(com.oppo.cvs.web.springContextHolder.annotation.SysLog)")
	public void executeAnnotation(){}
	/**
	 * 拦截处理
	 */
	@Before("executeAnnotation()")
	public void around(JoinPoint point) throws NoSuchMethodException {
		Method proxyMethod = ((MethodSignature) point.getSignature()).getMethod();
		Method targetMethod = point.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
		SysLog executor = targetMethod.getAnnotation(SysLog.class);
		Object[] args = point.getArgs();
		System.out.println("arg:"+ Arrays.toString(args));
		//使用SpringContextHolder 发送
	}
}

2.Aop切面拦截指定方法 + 指定接口

/**
     * 添加接口拦截
     */
    @Pointcut("execution(public * com.ruoyi.biz.api.controller.LoginApiController..*(..))")
    public void interceptMethods() {}
    /**
     * 排除方法拦截
     */
    @Pointcut("execution(public * com.ruoyi.biz.api.controller.LoginApiController.smsCode*(..))")
    public void excludeMethods(){};
    /**
     * 执行拦截
     */
    @Pointcut("interceptMethods() && !excludeMethods()")
    public void executeMethods(){};


拦截处理

/**
     * 拦截处理
     */
    @Around("executeAnnotation()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 获取当前拦截的方法签名
        String signatureShortStr = point.getSignature().toShortString();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        Object result = point.proceed();
        Object[] args = point.getArgs();
        // 拦截业务处理
        return result;
    }

看到这里,你有没有收获呢 ?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有马大树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值