基于springboot自定义注解+redis实现分布式锁

文章目录

  • 1、描述
  • 2、涉及注解介绍
    • Java注解
      • @Target
      • @Retention
      • @Documented
    • Spring注解
      • @Configuration
      • @EnableAspectJAutoProxy
    • Aspectj注解
      • @Aspect
      • @Pointcut
      • @Before
      • @AfterReturning
      • @AfterThrowing
      • @After
      • @Around
  • 3、案例
    • 引入依赖
    • 配置redis
    • 定义注解
    • 定义切面
    • 使用分布式锁

1、描述

ava多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看做是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同一个JVM进程里的时候,使用Java语言提供的锁机制可以起到对共享资源进行同步的作用。如果分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。本文将以Sping核心aop+自定义注解@AspectJ 来实现与介绍分布式锁。

Sping的aop即面向切面编程,底层是通过动态代理的机制来实现的,支持jdk和cglib两种。默认通过jdk动态代理。其中jdk的动态代理要求业务类必须得实现业务接口,底层是通过生成业务接口的动态代理实现类来完成功能增强,cglib不需要业务类实现接口,底层是通过衍生出当前业务类的子类对象来完成功能增强。默认情况下,如果业务对象未实现接口,则使用 cglib。由于面向接口而不是面向类进行编程是一种好习惯,因此业务类通常实现一个或多个业务接口。在某些情况下(可能极少发生),需要使用未在接口上声明的方法,或者需要将代理对象作为具体类型传递给方法,则可以使用 强制使用cglib

aop 通过设置切面,当切面设置的目标类的方法被调用时,aop 框架会拦截此次调用,源码中 pointCut 类里有两个核心属性,即 ClassFilter 类过滤器与MethodMatcher 方法匹配器,aop基于其两个核心来进行拦截,拦截之后aop机制会通过jdk或cglib生成动态代理对象,调用增强类的增强方法进行功能织入。

@AspectJ 是一种将切面声明为带有注解的常规 Java 类的样式。 @AspectJ 样式是AspectJ project作为 AspectJ 5 版本的一部分引入的。 Spring 使用 AspectJ 提供的用于切入点解析和匹配的库来解释与 AspectJ 5 相同的 注解。但是,AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。

要在 Spring 配置中使用@AspectJ 切面,需要启用 Spring 支持以基于@AspectJ 切面配置 Spring AOP,Sping根觉这些配置进行自动代理。自动代理的意思是,如果 Spring 确定一个或多个切面指向一个 bean,它会自动为该 bean 生成一个代理来拦截方法调用并确保按需执行建议。

2、涉及注解介绍

可以使用 XML 或 Java 注解配置来启用@AspectJ 支持。无论哪种情况,都需要确保 AspectJ 的aspectjweaver.jar库位于应用程序的 Classpath(版本 1.8 或更高版本)上。该库在 AspectJ 发行版的lib目录中或从 Maven Central 存储库中可用。

Java注解

涉及到的jdk 8提供的注解的介绍

@Target

指示注解适用的上下文,并以源代码通过枚举常数java.lang.annotation.ElementType表示 。默认值为任何元素。

package java.lang.annotation;
 
/**
 * The constants of this enumerated type provide a simple classification of the
 * syntactic locations where annotations may appear in a Java program. These
 * constants are used in {@link Target java.lang.annotation.Target}
 * meta-annotations to specify where it is legal to write annotations of a
 * given type.
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 4.1 The Kinds of Types and Values
 */
public enum ElementType {
    /** 类, 接口 (包括注解类型), 或 枚举 声明 */
    TYPE,
 
    /** 字段声明(包括枚举常量) */
    FIELD,
 
    /** 方法声明(Method declaration) */
    METHOD,
 
    /** 正式的参数声明 */
    PARAMETER,
 
    /** 构造函数声明 */
    CONSTRUCTOR,
 
    /** 局部变量声明 */
    LOCAL_VARIABLE,
 
    /** 注解类型声明 */
    ANNOTATION_TYPE,
 
    /** 包声明 */
    PACKAGE,
 
    /**
     * 类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * 使用的类型
     *
     * @since 1.8
     */
    TYPE_USE
}

//----------------------------------------------------------------------------------------------
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})  //表示声明该注解Token可以用于类、接口(包括注解类型)、枚举、方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Token {

}

@Retention

指示要注释具有注释类型的注释的保留策略。 如果注释类型声明中没有保留策略,则保留策略默认为RetentionPolicy.CLASS 。

package java.lang.annotation;
/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
public enum RetentionPolicy {
    /**
     * 注解只在源代码级别保留,编译时被忽略
     */
    SOURCE,
    /**
     * 注解将被编译器在类文件中记录
     * 但在运行时不需要JVM保留。这是默认的
     * 行为.
     */
    CLASS,
    /**
     *注解将被编译器记录在类文件中
     *在运行时保留VM,因此可以反读。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

//------------------------------------------------------------------------------------------------------
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})  //表示声明该注解Token可以用于类、接口(包括注解类型)、枚举、方法上
@Retention(RetentionPolicy.RUNTIME) //表示注解将被编译器记录在类文件中在运行时保留VM,因此可以反读
@Documented
public @interface Token {

}

@Documented

jdk1.5版本及其以上。Documented注解表明这个注解是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注解了文档化,它的注解将成为公共API的一部分。

Spring注解

涉及到的spring提供的注解的介绍

@Configuration

从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

注意:@Configuration注解的配置类有如下要求:

  • @Configuration不可以是final类型;
  • @Configuration不可以是匿名类;
  • 嵌套的configuration必须是静态类。
package com.dxz.demo.configuration;

import org.springframework.context.annotation.Configuration;
/*
 *@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的 作用为:配置spring容器(应用上下文)
 */
@Configuration
public class TestConfiguration {
    public TestConfiguration() {
        System.out.println("TestConfiguration容器启动初始化。。。");
    }
}

//---------------------------------------------------------------------------------------
package com.dxz.demo.configuration;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

//测试
public class TestMain {
    public static void main(String[] args) {

        // @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);

        // 如果加载spring-context.xml文件:
        // ApplicationContext context = new
        // ClassPathXmlApplicationContext("spring-context.xml");
    }
}

@EnableAspectJAutoProxy

启用@AspectJ 支持,表示开启AOP代理自动配置,@EnableAspectJAutoProxy有两个参数,其一:proxyTargetClass为是否使用cglib动态代理,true为使用cglib动态代理,false表示使用jdk动态代理;其二:exposeProxy为控制代理的暴露方式,解决内部调用不能使用代理的场景。两个参数的默认值为false。

从@EnableAspectJAutoProxy的定义可以看得出,它引入AspectJAutoProxyRegister.class对象,该对象是基于注解@EnableAspectJAutoProxy注册一个AnnotationAwareAspectJAutoProxyCreator,该对象通过调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注册一个aop代理对象生成器。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {} 

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true) //使用cglib动态代理
public class AppConfig {} 

Aspectj注解

涉及到的AspectJ提供的注解的介绍

@Aspect

启用@AspectJ 支持后,Spring 会自动检测到在应用程序上下文中使用@AspectJ 切面(具有@Aspect注解)的类定义的任何 bean,并用于配置 Spring AOP。与任何其他类相同,切面(带有@Aspect注解 的类)可以具有方法和字段,它们还可以包含切面。请注意,@Aspect注解 不足以在 Classpath 中进行自动检测。为此,需要添加一个单独的@Component注解(或者按照 Spring 的组件扫描程序的规则,自定义构造型注解)。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class NotVeryUsefulAspect {

}

@Pointcut

声明切入点,切入点即拦截方法调用的入口,拦截后可以在目标方法执行前或后或执行中等时机执行切面中定义的增强方法。 Spring AOP 仅支持在 Spring Bean 的方法上执行切入点,因此可以将切入点视为与 Spring Bean 上的方法执行相匹配。切入点声明由两部分组成:一个包含名称和任何参数的指示符,以及一个切入点表达式,该切入点表达式准确指向目标方法。在 AOP 的@AspectJ 注解样式中,常规方法定义提供了切入点指示符。 ,并通过使用@Pointcut注解 指示切入点表达式(用作切入点签名的方法必须具有void返回类型)。

/*
 * 示例定义一个名为anyOldTransfer的切入点,该切入点与任何名为transfer的方法的执行相匹配
 */
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

/*
 * 切入点表达式可以使用 &&, ||和! 进行组合。也可以按名称引用切入点表达式。以下示例显示了三个切入点表达式
 */
@Pointcut("execution(public * *(..))")  //anyPublicOperation切入点与任何公共方法匹配执行
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.someapp.trading..*)")//inTrading切入点与trading模块中的方法匹配执行
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")//tradingOperation切入点与trading模块中的public方法匹配执行
private void tradingOperation() {} 

/*
 * 一些常用的切入点表达式示例
 */
execution(public * *(..)) //任何公共方法
execution(* set*(..)) //名称以set开头的任何方法
execution(* com.xyz.service.AccountService.*(..)) //AccountService接口定义的任何方法
execution(* com.xyz.service.*.*(..)) //service包中定义的任何方法
execution(* com.xyz.service..*.*(..))  //service包或其子包之一中定义的任何方法
within(com.xyz.service.*)  //service包中的任何切入点(仅在 Spring AOP 中执行方法)
within(com.xyz.service..*)  //service包或其子包之一中的任何切入点(仅在 Spring AOP 中执行方法)
this(com.xyz.service.AccountService)  //代理实现AccountService接口的任何切入点(仅在 Spring AOP 中执行方法)
target(com.xyz.service.AccountService)  //目标对象实现AccountService接口的任何切入点(仅在 Spring AOP 中执行方法)
/*
 * 任何采用单个参数且运行时传递的参数为Serializable的连接点(仅在 Spring AOP 中是方法执行)
 * 注意,此示例中给出的切入点不同于execution(* *(java.io.Serializable))。如果在运行时传递的参数为Serializable,则 args 版本匹配,
 * 如果方法声明单个类型为Serializable的参数,则execution版本匹配
 */
args(java.io.Serializable) 
//目标对象带有@Transactional注解的任何切入点(仅在 Spring AOP 中执行方法)
@target(org.springframework.transaction.annotation.Transactional)
//目标对象的声明类型具有@Transactional注解的任何切入点(仅在 Spring AOP 中是方法执行)
@within(org.springframework.transaction.annotation.Transactional)
//执行方法带有@Transactional注解的任何切入点(仅在 Spring AOP 中是方法执行)    
@annotation(org.springframework.transaction.annotation.Transactional)
//任何采用单个参数且传递的参数的运行时类型具有@Classified注解的切入点(仅在 Spring AOP 中是方法执行)   
@args(com.xyz.security.Classified)    
//名为tradeService的 Spring bean 上的任何切入点(仅在 Spring AOP 中执行方法)    
bean(tradeService)
//pring Bean 上具有与通配符表达式*Service匹配的名称的任何切入点(仅在 Spring AOP 中是方法执行)    
bean(*Service)    

Spring aop支持在在切入点表达式中使用的AspectJ指示符主要有:

  • execution:用于匹配方法执行的切入点。这是使用 Spring AOP 时要使用的主要切入点指示符。

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
    

    除了返回类型模式 (ret-type-pattern)名称模式 (name-pattern)参数模式 (param-pattern) 以外的所有其他部分都是可选的。返回类型模式确定该方法的返回类型必须是什么才能使切入点匹配。***** :最常用作返回类型模式。它匹配任何返回类型。仅当方法返回给定类型时,完全合格的类型名称才匹配。名称模式与方法名称匹配。可以使用 ***** 通配符用作名称模式的全部或一部分。如果指定了声明类型模式,请在其末尾添加 . 并将其连接到名称模式组件。参数模式稍微复杂一些:() 匹配不带参数的方法,而 (…) 匹配任意数量(零个或多个)的参数。 (*) 模式与采用任何类型的一个参数的方法匹配。 **(*,String)**与采用两个参数的方法匹配。第一个可以是任何类型,而第二个必须是String。

  • within:将匹配限制为某些类型内的切入点(使用 Spring AOP 时,在匹配类型内声明的方法的执行)。

  • this:将匹配限制为切入点(使用 Spring AOP 时方法的执行),其中 bean 引用(Spring AOP 代理)是给定类型的实例, “ this”通常以绑定形式使用。

  • target:将目标对象(正在代理的应用程序对象)是给定类型的实例的切入点(使用 Spring AOP 时,方法的执行)限制为匹配。“target”通常以绑定形式使用。

  • args:将参数限制为给定类型的实例的切入点(使用 Spring AOP 时方法的执行)限制匹配。“ args”更通常以绑定形式使用。

  • @target:将执行对象的类具有给定类型的注解的切入点(使用 Spring AOP 时,方法的执行)限制为匹配。以在绑定形式中使用“ @target”。

  • @args:限制匹配的切入点(使用 Spring AOP 时方法的执行),其中传递的实际参数的运行时类型具有给定类型的 注解。可以在绑定形式中使用“ @args”。

  • @within:将匹配限制为具有给定注解的类型内的切入点(使用 Spring AOP 时,使用给定注解的类型中声明的方法的执行)。以在绑定形式中使用“ @within”。

  • @annotation:将匹配限制为切入点的目标(在 Spring AOP 中正在执行的方法)具有给定注解的连接点。可以在绑定形式中使用“ @annotation”。

因为 Spring AOP 将切入点匹配限制为方法执行,所以对切入点指示符的的定义比 AspectJ 编程指南中的定义要窄,即还有好多AspectJ 切入点在 Spring 中不支持。此外,AspectJ 本身具有基于类型的语义,并且在执行切入点处this和target引用同一对象:执行该方法的对象。 Spring AOP 是基于代理的系统,可区分代理对象本身(绑定到this)和代理后面的目标对象(绑定到target)。由于 Spring 的 AOP 框架基于代理的性质,因此根据定义,不会拦截目标对象内的调用。对于 JDK 代理,只能拦截代理上的 public 接口方法调用。CGLIB 可以拦截代理上的 public 方法和 protected 方法。

@Pointcut注解的切入点表达式是一个常规的 AspectJ 5 切入点表达式。有关 AspectJ 的切入点语言的完整叙述,可以参照Aspectj编程指南J与AspectJ 5 开发人员笔记。

示例:

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

    /**
     * inWebLayer切入点与web模块中的方法匹配执行
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}
    
    /**
     * businessService切入点与service中的方法匹配执行,如: com.xyz.someapp.abc.service 和 com.xyz.someapp.def.service中的方法
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * businessService切入点与dao中的方法匹配执行
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

@Before

前置增强注解,与切入点相关,在切入点对应的方法拦截后,在目标方法执行前执行本注解对应的增强方法,然后在执行目标方法。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")//指定方法
    public void doAccessCheckOne() {
        // 业务代码
    }
    
    
	@Before("execution(* com.xyz.myapp.dao.*.*(..))")  // 切入点表达式
    public void doAccessCheckTwo() {
        // .业务代码
    }
    
}

@AfterReturning

正常返回增强注解,在切入点对应的目标方法执行完正常返回时拦截进行业务增强。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperationOne()") //指定方法
    public void doAccessCheckOne() {
        // 业务代码
    }
    
    /*
     * 有时需要在增强方法中访问返回的实际值。可以使用@AfterReturning的形式绑定返回值以获取该访问权限
     * returning属性中使用的名称必须与增强方法中的参数名称相对应,当方法执行返回时,该返回值将作为相应的参数值传递到增强方法
     * returning子句还可以匹配仅限制为返回指定类型值(在这种情况下为Object,该值与任何返回值匹配)的那些方法,即除指定返回类型,其余的不作拦截
     * 请注意,正常返回增强使用时,不可能返回完全不同的参考。
     */
    @AfterReturning(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperationTwo()",returning="retVal")
    public void doAccessCheckTwo(Object retVal) {
        // 业务代码
    }

}

@AfterThrowing

抛出异常增强注解,在切入点对应的目标方法执行时抛出异常结束时拦截进行业务增强。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperationOne()") //指定方法
    public void doRecoveryActionsOne() {
        // 业务代码
    }
    
    /*
     * 通常希望仅在引发给定类型的异常时才进行拦截进行业务增强,并且通常还需要访问通知正文中的异常。
     * 如果需要 可以使用throwing属性来限制匹配(否则,请使用Throwable作为异常类型),并将抛出的异常绑定到 advice 参数
     * throwing属性中使用的名称必须与增强方法中的参数名称相对应。当通过抛出异常退出方法执行时,该异常将作为相应的参数值传递给通知方法。 
     * throwing子句还将匹配仅限制为抛出指定类型(在本例中为DataAccessException)的异常的方法执行
     */
    @AfterThrowing(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperationTwo()",throwing="ex")
    public void doRecoveryActionsTwo(DataAccessException ex) {
        // 业务代码
    }

}

@After

方法执行退出时增强注解,当目标方法执行退出时,@After注解的增强方法运行,之后是处理正常和异常返回条件的增强方法。本方法通常用于释放资源与进行类似目的的业务。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")//指定方法
    public void doReleaseLock() {
        // 业务代码
    }

}

@Around

方法环绕增强注解,该增强方法在匹配方法的执行过程中“环绕”运行。它有机会在方法执行之前和之后进行工作,并确定何时,或根本不执行该方法。如果需要以线程安全的方式 (例如:启动和停止计时器 或 对分布式锁的加锁与解锁 ) 在方法执行之前和之后共享状态,则通常使用本增强方法。

环绕增强方法的第一个参数必须为 ProceedingJoinPoint 类型。在环绕增强的方法内,使用ProceedingJoinPoint调用proceed()会使目标方法执行。 proceed方法也可以传入Object[]。数组中的值用作目标方法执行时的参数。

环绕增强方法返回的值是目标方法的调用者看到的返回值。例如,如果一个简单的缓存,在缓存中有调用者需要的值,则可以从缓存中返回这个值;如果没有,则调用proceed()。请注意,proceed可能在环绕增强方法中被调用一次,多次或完全不被调用都是合法的。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()") //指定方法
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // 前置业务增强
        Object retVal = pjp.proceed(); //执行目标方法
        // 后置业务增强
        return retVal;
    }

}

任何增强方法都可以将org.aspectj.lang.JoinPoint类声明为第一个参数(请注意:在环绕增强中必须声明JoinPoint类型的子类ProceedingJoinPoint为第一个参数)

JoinPoint接口提供了许多有用的方法(详情请参照 javadoc ):

  • getArgs():返回方法参数。

  • getThis():返回代理对象。

  • getTarget():返回目标对象。

  • getSignature():返回建议使用的方法的描述。

  • toString():打印有关所建议方法的有用描述。

以上已经介绍了如何绑定返回的值或异常值(在返回之后和引发异常之后使用)。要使参数值可用于增强方法中,可以使用args的绑定形式。如果在 args 表达式中使用参数名称代替类型名称,则在调用建议时会将相应参数的值作为参数值传递。

示例:假设要在增强方法中需访问帐户,则可以以Account对象作为第一个参数

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

切入点表达式的args(account,…)部分有两个作用。首先,它将匹配限制为仅方法采用至少一个参数并且传递给该参数的参数是Account的实例的方法执行。其次,它通过account参数将实际的Account对象传递到增强方法中。

此案例可以由另一种方式实现:声明一个切入点validateAccount,当切入点Account对象值与连接点匹配时,引入accountDataAccessOperation切入点。

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

代理对象(this),目标对象(target)和 注解(@within,@target,@annotation和@args)都可以类似的方式绑定。

Spring AOP 可以处理类声明和方法参数中使用的泛型。假设有如下通用类型:

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}

可以通过在要拦截方法的参数类型中键入 advice 参数,将方法类型的拦截限制为某些参数类型:

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

这种方法不适用于通用集合。因此不能按以下方式定义切入点 (要达到类似的效果,必须使用Collection并手动检查元素的类型):

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}

3、案例

使用Spring boot自定义注解实现分布式锁的demo。

引入依赖

maven引入apo依赖

<dependency>
	<groupId>org.springframework.bootgroupId>
   	<artifactId>spring-boot-starter-aopartifactId>
dependency>

gradlel引入依赖

compile 'org.springframework.boot:spring-boot-starter-aop'

配置redis

package com.test.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableAutoConfiguration
@Slf4j
public class RedisConfig {

    @Bean(name = "jsonRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // 序列化方式全部为String
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        // hash value序列化方式为JSON
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        return redisTemplate;
    }
}

定义注解

使用注解加redis实现分布式锁

package com.test.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 分布式锁
 */
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

    /**
     * 锁过期时间,默认60
     *
     * @return
     */
    int timeOut() default 60;

    /**
     * 锁名称,默认取方法签名
     *
     * @return
     */
    String name() default "";

    /**
     * 执行完成后是否释放锁资源,默认立即释放
* * @return */
boolean afterExecuteRelease() default true; }

定义切面

package com.test.aspectConfig;

import com.test.annotations.Lock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
@SuppressWarnings("unused")
public class LockAspect {

    /**
     * 锁的配置是否打开
     */
    @Value(value = "${open.lock:true}")
    private boolean openLock;

    @Resource(name = "jsonRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @Pointcut("@annotation(com.test.annotations.Lock)")
    public void pointcutExecute() {
    }

    @Around("pointcutExecute()")
    public Object beforeExecute(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Lock lockAnnotation = method.getAnnotation(Lock.class);
        String lockName = lockAnnotation.name();
        if (lockName.length() == 0) {
            lockName = method.getDeclaringClass() + "." + method.getName();
        }
        try {
            if (!openLock) {
                log.info("全局锁配置关闭");
                return joinPoint.proceed();
            }
            BoundValueOperations<String, String> lock = redisTemplate.boundValueOps(lockName);
            if (lock.setIfAbsent("wpy")) {
                log.debug("获取锁:{}成功", lockName);
                lock.expire(lockAnnotation.timeOut(), TimeUnit.SECONDS);
                Object result = joinPoint.proceed();
                if (lockAnnotation.afterExecuteRelease()) {
                    redisTemplate.delete(lockName);
                    log.debug("释放锁:{}成功", lockName);
                }
                return result;
            } else {
                log.info("线程未竞争到锁:{},方法未执行", lockName,
                        method.getDeclaringClass() + "." + method.getName());
            }
        } catch (Throwable e) {
            if (openLock) {
                redisTemplate.delete(lockName);
            }
            log.error(e.getMessage());
        }
        return null;
    }

}

使用分布式锁

package com.test.service;

import com.test.Lock;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service("workservice")
public class WorkService {

    @Lock(timeOut = 30)
    public void toDoWork() {
        // 业务代码
    }

   

}

你可能感兴趣的:(springboot,分布式,spring,boot,redis,java)