一篇文章带你明白AOP思想
题记
所有java开发都知道三大特性封装
、继承
和多态
;有些人称四大特性封装
、继承
、多态
和抽象
,为什么提这些东西呢,答案是为了体现抽象
的重要性。提起抽象,大家可能也都清楚,伟大的JDK
给我们提供了抽象类和接口,为什么面向对象的java
有了抽象类还要有接口呢,当然是因为java
单继承多实现的特性。
在java
开发中Spring Framework
的地位十分重要,市面上绝大多数的公司或多或少的使用到了Spring Framework
,其中最重要的两点就是IOC
和AOP
的特性,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();
以上是slf4j
对log
打印的实现,是在容器启动或者是说在类加载的时候通过预先加载配置文件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
由@Transactional
从SpringTransactionAnnotationParser#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
功能均已实现,当然AOP
在Spring Framework
中的体现远不止于此,笔者所做的也就是抛砖引玉,仅此而已,相互分享,相互学习共同进步!
完整代码及详情
具体依赖及相关源码参照
https://github.com/dwyanewede/spring-farmework/tree/master/src/main/java/com/sxs/demo/springfarmework