首页 > 技术文章 > spring aop+aspectj+rdis 解决重复流水号

vi-2525 2018-04-13 10:06 原文

  一、背景

  公司自研一套服务治理框架,只需实现特定接口即可在服务中心上注册服务,但该框架有一套自动重发机制,在网络抖动情况下可能会重复交易(例如重复放款),并非所有交易系统都有完善的交易流水控制机制。为了解决重复交易问题,尽可能的降低代码侵入性,所以使用aop+aspectj+redis解决这一问题。

  二、环境

   1、maven 项目。

   2、JDK 1.7

  三、示例

  总体介绍,spring aop分为jdk和cglib两种动态代理,通常我们使用cglib的方式,cglib会在启动初始化时加载稍慢,在后期运行时效率更高并且可以支持无接口的代理。通过 aop+aspectj方式是个人认为自动化程度最高,开发工作最少的实现方法,但是spring aop的实现方法远不止于此。

  aop+aspectj,主要任务为编写aspectj拦截类,bean上添加@Aspect注解,bean中可以添加@Around、@Before()、@After()等注解。在spring配置文件中开启aspectj即可,<aop:aspectj-autoproxy proxy-target-class="true"/>,true时使用cglib代码。以下为详细说明。

  1、服务接口、接口实现、实体对象。

package com.aop.api;

import com.aop.request.RequestData;
import com.aop.response.ResponseData;

public interface ApiInterface {

    ResponseData helloWorld(RequestData requestData);
}


 
package com.aop.service;

import com.aop.api.ApiInterface;
import com.aop.request.RequestData;
import com.aop.response.ResponseData;
import org.springframework.stereotype.Service;

@Service
public class ApiImpl implements ApiInterface {

    @Override
    public ResponseData helloWorld(RequestData requestData) {
        ResponseData responseData = new ResponseData("SUCCESS","请求成功!");
        System.out.println("impl 请求参数:" + requestData.getData());
        return responseData;
    }
}

package com.aop.request;

public class RequestData {
    private String data;

    public RequestData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "RequestData{" +
                "data='" + data + '\'' +
                '}';
    }
}

package com.aop.response;

public class ResponseData {
    private String respCode;
    private String respDesc;

    public ResponseData(String respCode, String respDesc) {
        this.respCode = respCode;
        this.respDesc = respDesc;
    }

    @Override
    public String toString() {
        return "ResponseData{" +
                "respCode='" + respCode + '\'' +
                ", respDesc='" + respDesc + '\'' +
                '}';
    }
}

 

  2、拦截AOP

package com.aop.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AopIntercept {

    /**
     * 只有around拦截可以使用joinPoint。我们就练习这个相对复杂的
     *
     * @param joinPoint
     * @return
     */
    //execution 拦截点介绍:第一个* 表示所有返回值,第二个*表示所有方法,后面的(..)表示这个方法中所有入参。即拦截ApiInterface下所有方法
    @Around("execution(* com.aop.api.ApiInterface.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) {
        before();
        //获得所有请求参数
        Object[] args = joinPoint.getArgs();
        for (Object obj : args) {
            System.out.println("拦截中请求参数:" + obj.toString());
        }
        Object obj = null;
        try {
            obj = joinPoint.proceed();
            System.out.println("拦截中方法返回值:" + obj.toString());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        after();

        return obj;
    }

    private void before() {
        //流水号重复校验
        System.out.println("拦截方法前执行!");
    }

    private void after() {
        System.out.println("拦截方法后执行!");
    }
}

 

  3、配置文件、pom文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd "
       xmlns:aop="http://www.springframework.org/schema/aop">

    <context:component-scan base-package="com.aop"/>
    <!-- 动态代理默认jdk使用cglib时设置成true-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aop.test</groupId>
    <artifactId>aop</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/aspectj/aspectjweaver -->
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.4</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>aop</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/**</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <!--<version>2.5</version>-->
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

        </plugins>

    </build>

</project>

 

  4、测试代码。

package com.aop;

import com.aop.api.ApiInterface;
import com.aop.request.RequestData;
import com.aop.response.ResponseData;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        ApiInterface apiInterface = context.getBean(ApiInterface.class);
        ResponseData responseData = apiInterface.helloWorld(new RequestData("请求参数:name=张三"));
        System.out.println("main end response:" + responseData);
    }
}

 

  5、输出结果。

  四、总结

  我们通过aop方式,在before()方法中加入redis,通过在redis中检查流水号的key是否已存在来判定是否为重复交易。

  下面在介绍一些常用的aop方式

  1、基于XML配置的Spring AOP。

  2、编程式aop等。

     https://github.com/binary-vi/binary.github.io/tree/master/aop

 

 

推荐阅读