Alter dataSource in Spring By AOP And Annotation

简介:

Here is an article of how to use AOP and Annotation mechanism to alter dataSource elegantly.
First, I want make sure that everyone knows how to build multiple dataSource. Please check this article Dynamic-DataSource-Routing
After this, we will have a DataSourceHolder class, in the case above, it is called CustomerContextHolder.
Let's remove the customer logic and make Holder purer.

public class DataSourceHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static String getCurrentDataSource() {
        return (String) contextHolder.get();
    }   

    public static void setDataSource(String dataSource){
        contextHolder.set(dataSource);
    }

    public static void setDefaultDataSource(){
        contextHolder.set(null);
    }

    public static void clearCustomerType() {
        contextHolder.remove();   
    }  

}

When should we call setDataSource

In the project I take charge of, they invoke setDataSource in each controller. IMHO, I don't think it's an elegant way. I think dataSource should be an attribute of a DAO method or a Service method. And since transactionManager is aadvice to Service method in this project, dataSource must be an attribute of a Service method.

Use Annotation to describe a Service method

First, we should define a runtime annotation.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String name() default DataSource.DEFAULT;

    public final static String DEFAULT     = "foo";

    public final static String BAR           = "bar";

    public final static String BAZ           = "baz";

}

Then, we use the annotation to describe a Service method.

    @Override
    @DataSource(name=DataSource.BAR)
    public Object getSomething() {
        return dao.getSomething();
    }

Use AOP to invoke setDataSource

First, define a pointcut.

        <aop:pointcut id="serviceWithAnnotation"
    expression="@annotation(com.yourpackageName.DataSource)" />

Second, define a advisor.

    <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="serviceWithAnnotation" order="1"/>
    <bean id="dataSourceExchange" class="com.yourpackageName.DataSourceExchange"/>

Now, the AOP mechanism will make sure that some methods of DataSourceExchange will run if Service method whichDataSource annotation decorated is invoked.

Last, define DataSourceExchange.

class DataSourceExchange implements MethodInterceptor {

    private Logger             logger = LoggerFactory.getLogger(DataSourceExchange.class);

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Method name : "
                + invocation.getMethod().getName());
        System.out.println("Method arguments : "
                + Arrays.toString(invocation.getArguments()));
        DataSource dataSource = this.getDataSource(invocation);
        if(dataSource == null) {
            logger.error("dataSource in invocation is null");
        }
        String dbnameString = dataSource.name();
        Object result;
        try {
            DataSourceHolder.setDataSource(dbnameString);
            result = invocation.proceed();
        } finally {
            DataSourceHolder.setDefaultDataSource();
        }
        return result;
    }

    private DataSource getDataSource(MethodInvocation invocation) throws Throwable {
  //TODO
  }

The hardest part in this bunch of code is how should us impl the getDataSource method. I spent several hours of this method. First, I've seen some code online, which tell me it's quite simple to do this. Just like the code below

    private DataSource getDataSource(MethodInvocation invocation) throws Throwable {
        return invocation.getMethod().getAnnotation(DataSource.class);
  }

But it won't work, because invocation.getMethod() will not return the method you defined above, it will return a proxymethod. It's a mechanism called Proxy in Spring framework.
So we should find out the real method.
Again I searched stackoverflow.com, some answers tell me AnnotationUtils.findAnnotation will be useful to me.

    private DataSource getDataSource(MethodInvocation invocation) throws Throwable {
        return AnnotationUtils.findAnnotation(invocation.getMethod(), DataSource.class);
  }

AnnotationUtils.findAnnotation will recursively find the super class of the proxy method, to find the annotation decorated on the real method you defined above.
But it's not the complete answer. 
Let's see the source code of AnnotationUtils.findAnnotation

    /**
     * Get a single {@link Annotation} of <code>annotationType</code> from the supplied {@link Method},
     * traversing its super methods if no annotation can be found on the given method itself.
     * <p>Annotations on methods are not inherited by default, so we need to handle this explicitly.
     * @param method the method to look for annotations on
     * @param annotationType the annotation class to look for
     * @return the annotation found, or <code>null</code> if none found
     */
    public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
        A annotation = getAnnotation(method, annotationType);
        Class<?> cl = method.getDeclaringClass();
        if (annotation == null) {
            annotation = searchOnInterfaces(method, annotationType, cl.getInterfaces());
        }
        while (annotation == null) {
            cl = cl.getSuperclass();
            if (cl == null || cl == Object.class) {
                break;
            }
            try {
                Method equivalentMethod = cl.getDeclaredMethod(method.getName(), method.getParameterTypes());
                annotation = getAnnotation(equivalentMethod, annotationType);
                if (annotation == null) {
                    annotation = searchOnInterfaces(method, annotationType, cl.getInterfaces());
                }
            }
            catch (NoSuchMethodException ex) {
                // We're done...
            }
        }
        return annotation;
    }

Here we have a precondition to let AnnotationUtils.findAnnotation works, that is the Proxy mechanism is implemented by inherit. There are two ways of proxy in Spring. What is the difference between JDK dynamic proxy and CGLibCGLib is implemented by inherit but JDK dynamic proxy is not.
So AnnotationUtils.findAnnotation won't work for JDK dynamic proxy. We should write some more code to deal with this situation. Here is my final solution.

    private DataSource getDataSource(MethodInvocation invocation) throws Throwable {
        DataSource dataSource = AnnotationUtils.findAnnotation(invocation.getMethod(), DataSource.class);
        if(dataSource != null) {
            return dataSource; // if use CGlib proxy
        }

        Method proxyedMethod = invocation.getMethod(); // or use jdk proxy
        Method realMethod = invocation.getThis().getClass().getDeclaredMethod(proxyedMethod.getName(), proxyedMethod.getParameterTypes());
        dataSource =  AnnotationUtils.findAnnotation(realMethod, DataSource.class);
        return dataSource;
    }

Summary

In this case, I learnt

  • how to use AOP and annotation
  • there is a mechanism called proxy used by Spring
  • there are two implements of proxy mechanism, they are different
  • how to use reflection in Java

I hope it would help u.


目录
相关文章
|
5天前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
|
13天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
56 8
|
2月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
2月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
96 5
|
2月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
91 8
|
2月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
2月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
53 5
|
2月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
59 4
|
2月前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
51 1
|
2月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
54 0