一篇文章带你明白AOP思想

一篇文章带你明白AOP思想

题记

所有java开发都知道三大特性封装继承多态;有些人称四大特性封装继承多态抽象,为什么提这些东西呢,答案是为了体现抽象的重要性。提起抽象,大家可能也都清楚,伟大的JDK给我们提供了抽象类和接口,为什么面向对象的java有了抽象类还要有接口呢,当然是因为java单继承多实现的特性。

java开发中Spring Framework的地位十分重要,市面上绝大多数的公司或多或少的使用到了Spring Framework,其中最重要的两点就是IOCAOP的特性,IOC是指把创建对象的权利交给Spring进行,AOP更侧重的是面向切面编程的思想,虽说不是所有框架中独有的,但是不得不说Spring做到了极致。

动态代理源码实现 AopProxyFactory

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				// 表示在有接口实现的时候采用JDK动态代理
                return new JdkDynamicAopProxy(config);
			}
            // 在没有接口实现的时候采用Cglib动态代理
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

Spring中AOP的实现

程序自上而下执行,与主业务逻辑的关系不大的横切性问题,aop面向切面编程,就是将主业务与横切代码分离,做到解耦。

常见实现有:

  • 统一日志处理
  • 统一异常处理
  • Spring事务管理

日志处理

log4j加载过程类似,而且分析更简单点,所以本次分析slf4j加载过程。

log4j.properties加载的位置

static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";

url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);org.apache.log4j.LogManager中加载

从代码入手

// 从代码日志打印位置开始getLogger

private static Logger logger = LoggerFactory.getLogger(LoggerTest.class);

进入getLogger方法

org.slf4j.LoggerFactory#getILoggerFactory

//开始执行初始化动作

org.slf4j.LoggerFactory#performInitialization

//进入对应的bind方法

org.slf4j.LoggerFactory#performInitialization -> org.slf4j.LoggerFactory#bind

//从classpath找出所有slf4j的实现,加载资源路径

org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet

//获取单例LoggerFactory对象

StaticLoggerBinder.getSingleton().getLoggerFactory()

//通过反射调用获得对应的logger实现

return StaticLoggerBinder.getSingleton().getLoggerFactory();

以上是slf4jlog打印的实现,是在容器启动或者是说在类加载的时候通过预先加载配置文件log4j.properties,而后使用动态代理的方式为特定的类或者说是对象生成单例的ILoggerFactory,最后在字节码顺序执行的时候调用心相应的日志打印方法。

Spring AOP日志统一打印

实现日志统一打印有两种方法,一种是使用@Aspect,拦截相应包下的相应方法,实现更为统一;另一种则是自定义注解方式实现,灵活性更强。

第一种实现

/**
 * @ClassName: LogHandlerInterceptor
 * @Description: 日志统一处理类
 * @Author: 尚先生
 * @CreateDate: 2019/1/30 20:11
 * @Version: 1.0
 */
@Component
@Aspect
public class LogHandlerInterceptor {
    private final Logger logger = Logger.getLogger(LogHandlerInterceptor.class);

    @Pointcut("execution(* com.sxs.demo.springfarmework.controller..*.*(..))")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object doInvoke(ProceedingJoinPoint joinPoint) {
        long start = System.currentTimeMillis();

        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error(throwable.getMessage(), throwable);
            throw new RuntimeException(throwable);
        } finally {
            long end = System.currentTimeMillis();
            long costTime = end - start;

            printLog(joinPoint, result, costTime);

        }

        return result;
    }

    /**
     * 打印日志
     * @param joinPoint   连接点
     * @param result      方法调用返回结果
     * @param costTime 方法调用花费时间
     */
    private void printLog(ProceedingJoinPoint joinPoint, Object result, long costTime) {
        Log log = getLog(joinPoint);
        if (null != log) {
            log.setThreadId(String.valueOf(Thread.currentThread().getId()));
            log.setResult(JSON.toJSONString(result));
            log.setCostTime(costTime);
            logger.info(log.toString());
        }
    }

    /**
     * 获取请求对应的类名,方法名,参数类型,参数值
     * @param joinPoint
     * @return
     */
    private Log getLog(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        Log log = new Log();
        log.setClassName(className);
        log.setMethodName(methodName);
        log.setArguments(args);
        return log;
    }
}

上述为主要实现代码,旨在拦截controller包下面的所有方法,具体实现和依赖可参照下方GitHub地址

第二种实现

采用注解实现,通过对某个方法的注解,达到便捷可控的目的

/**
 * @ClassName: AspectAnnotationHandler
 * @Description: 拦截器实现
 * @Author: 尚先生
 * @CreateDate: 2019/1/31 13:10
 * @Version: 1.0
 */
public class AspectAnnotationHandler implements HandlerInterceptor {

    private final Logger logger = Logger.getLogger(AspectAnnotationHandler.class);
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //此处可做鉴权,或者是准入校验 TODO
        if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
            // 获取请求路径
            StringBuffer requestURL = request.getRequestURL();
            //本次所做操作是判断是否有自定义注解AspectAnnotation
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法名
            Method method = handlerMethod.getMethod();
            MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
            AspectAnnotation annotation = method.getAnnotation(AspectAnnotation.class);
            if (null != annotation) {
                // 获取参数 map
                Map<String, String[]> map = request.getParameterMap();
                if (null != map) {
                    List<String> listValue = new ArrayList<>();
                    List<String> listkey = new ArrayList<>();

                    for (Map.Entry<String, String[]> entry : map.entrySet()) {
                        String key = entry.getKey();
                        listkey.add(key);
                        String[] value = entry.getValue();
                        listValue = Arrays.asList(value);
                    }

                    logger.info("请求URL: " + requestURL + " 请求方法:" + method + " 请求参数名: "
                            + listkey.toString() + " 请求参数: " + listValue.toString());
                }else {
                    logger.info("请求URL: " + requestURL + " 请求方法:" + method);
                }
            }
        }
        return true;
    }
}

自定义注解类

/**
 * @ClassName: AspectAnnotation
 * @Description: 自定义切面注解
 * @Author: 尚先生
 * @CreateDate: 2019/1/31 13:07
 * @Version: 1.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface AspectAnnotation {
    /** 添加注解描述信息 **/
    String value() default "";
    /** 判断注解是否生效,默认生效 **/
    boolean flag() default true;
}

实现拦截器的织入

/**
 * @ClassName: LoggerTest
 * @Description: 分析spring在什么时候加载log4j
 * @Author: 尚先生
 * @CreateDate: 2019/1/30 17:01
 * @Version: 1.0
 */
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 自定义统一日志管理器
        registry.addInterceptor(new AspectAnnotationHandler());
    }
}

测试

测试方法编写

/**
     * 非必输参数,根据flag真假模拟测试事务的成功与否
     * @param flag
     * @return
     */
    @AspectAnnotation
    @RequestMapping("/transaction")
    public String transaction(@RequestParam(required = false) String flag) {
        logger.info("请求/transaction到达...");
        String msg = helloWorldService.transaction(flag);
        return msg;
    }

测试结果(浏览器输入:http://localhost:8081/transaction?flag=true)

注解实现输出结果
请求URL: http://localhost:8081/transaction 请求方法:public java.lang.String com.sxs.demo.springfarmework.controller.HelloWorldController.transaction(java.lang.String) 请求参数名: [flag] 请求参数: [true]

请求/transaction到达...
执行第一个原子操作...
执行第二个原子操作...
执行第三个原子操作...
    
@Aspect实现输出结果
Log{threadId='40', className='com.sxs.demo.springfarmework.controller.HelloWorldController', methodName='transaction', arguments=[true], result='"successful"', costTime=3}

异常处理

(浏览器输入:http://localhost:8081/transaction)可模拟自定义异常处理实现
部分源码

/**
 * @ClassName: ExceptionHandler
 * @Description: 异常处理拦截器,
 * 同理可在com.sxs.demo.springfarmework.config.WebMvcConfig#addInterceptors实现
 * @Author: 尚先生
 * @CreateDate: 2019/1/30 18:44
 * @Version: 1.0
 */
@ControllerAdvice(basePackages = "com.sxs.demo.springfarmework.controller")
public class ExceptionHandler {

    private final Logger logger = Logger.getLogger(ExceptionHandler.class);


    @org.springframework.web.bind.annotation.ExceptionHandler(value = RuntimeException.class)
    public String exceptionHandler(RuntimeException exception){
        logger.error("系统调用异常,异常信息:[{}]",exception.getCause());
        return "fail";
    }
}

事务管理

由于事务管理Spring Framework自身提供了实现,这里不再过多赘述,实现思路跟注解实现日志公共输出类似。

源码导读@Transactional

@TransactionalSpringTransactionAnnotationParser#parseTransactionAnnotation()入手

// 解析含有@Transactional注解的类或者方法
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				element, Transactional.class, false, false);
		if (attributes != null) {
			return parseTransactionAnnotation(attributes);
		}
		else {
			return null;
		}
	}

TransactionAspectSupport#invokeWithinTransaction找到对应调用方法,可标记处理类,也可以标记处理方法

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {
		// 当获取不到TransactionAttribute时默认没有实现事务控制.
		TransactionAttributeSource tas = getTransactionAttributeSource();
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// 标准的事务控制 commit/rollback 调用实现.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			try {
				// 处理拦截器中对应的方法
				retVal = invocation.proceedWithInvocation();
			}
			
		else {
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// callback
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					}
					
				// 检查返回结果状态,根据结果状态处理后续逻辑.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			}
		}
	}

最终的调用方TransactionInterceptor#invoke通过动态代理的方式,反射调用实现拦截处理

//public abstract class TransactionAspectSupport implements BeanFactoryAware, //InitializingBean 该类实现BeanFactoryAware接口
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
public Object invoke(MethodInvocation invocation) throws Throwable {
    ...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}
    ...
}

结语

至此文中所描述的的所有AOP功能均已实现,当然AOPSpring Framework中的体现远不止于此,笔者所做的也就是抛砖引玉,仅此而已,相互分享,相互学习共同进步!

完整代码及详情

具体依赖及相关源码参照

https://github.com/dwyanewede/spring-farmework/tree/master/src/main/java/com/sxs/demo/springfarmework

猜你喜欢

转载自blog.csdn.net/shang_xs/article/details/86715780