栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

三、有关Spring AOP的理解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

三、有关Spring AOP的理解

一、Spring AOP:

详情参考一:狂神说Spring07:AOP就这么简单
详情参考一:Spring(2)之 (2.2 使用 AspectJ实现 AOP)

1. 什么是AOP?

AOP:面向切面编程,即通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 , 其本质还是动态代理(即将公共代码与业务代码分离,将其独立出来(即切面类),在执行业务代码时动态织入进来;使开发人员只关注业务代码的编写,提升开发效率。)

涉及名词:
切面(Aspect):切面类;
切入点(PointCut):切面通知 执行的 “地点”的定义,要在哪个类或者哪个方法进行织入;
通知(Advice):它是切面类中的一个方法;

2. 通知类型?

通知: Spring中支持5种类型的通知:
@Before:前置通知,业务代码之前执行;
@After: 后置通知,业务代码之后执行,无论是否出现异常都执行;
@Around: 环绕通知,环绕业务代码,有参数joinPoint:joinPoint.proceed();方法proceed()相当于目标对象业务代码中的save();
@AfterThrowing: 异常抛出通知,业务代码后,出现异常执行;
@AfterReturning:业务代码后执行,若出现异常不执行(即@AfterReturning与@AfterThrowing 两者不会同时存在)。

@PointCut(" execution( * com. asd. service. UserServiceImpl. * (… ))" ) :表示对哪些类的哪些方法进行拦截切入;(execution后是包名、类名或方法名字)
@Pointcut("@annotation(com.asd.utils.log.Log4Controller)") :表示对所有 @Log4Controller 注解注释的类或方法进行拦截切入;(annotation后是注解)
注:@PointCut()可换成任一类型的其他通知(如 @Before(…)、@After(…)…);

3. Spring中AOP的实现?

一、使用XML 方式,通过自定义类来实现AOP:

目标业务类是userServiceImpl:
第一步:自定义一个切入类:

public class MyAop{
	public void beginTrans(){
		System.out.println("方法执行前---开始事务");
	}
	public void commitTrans(){
		System.out.println("方法执行后---提交事务");
	}
	public void afterReturning(){
		System.out.println("afterReturning");
	}
	public void afterThrow(){
		System.out.println("afterThrow");
	}
	public void around(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("begin");
		joinPoint.proceed();
		System.out.println("end");
	}
}

第二步:在Spring配置文件中,注册bean,并通过使用AOP的标签来进行AOP的配置:







    
    
        
        
        
    

二、使用注解实现:
第一步:编写一个注解实现的增强类:

package com.asd.config;

@Aspect
public class AnnotationPointcut {
	//法一:单个注解自己定义
    @Before("execution(* com.asd.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("方法执行前----开始事务");
    }
    @After("execution(* com.asd.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("方法执行后----提交事务");
    }
    @Around("execution(* com.asd.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("begin环绕前");
        joinPoint.proceed();//执行目标方法proceed
        System.out.println("end环绕后");
    }

	//法二:使用 @PointCut 注解进行统一定义
	@PointCut("execution(* com.asd.service.UserServiceImpl.*(..))")
    public void pointCut(){
    }
    @Before("pointCut()")
    public void beginTrans(){
        System.out.println("方法执行前----开始事务");
    }
    @After("pointCut()")
    public void commitTrans(){
        System.out.println("方法执行后----提交事务");
    }
    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("begin环绕前");
        joinPoint.proceed();//执行目标方法proceed
        System.out.println("end环绕后");
    }		
}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置:






说明:

  1. 上方是配置完成之后直接生效,直接对某些包下的类或者方法进行拦截处理;
  2. 下方在使用时还需要添加注解,自定义的切面注解,要在某个类或者方法上加上此注解来对其进行拦截;
二、示例一:使用AOP实现自定义日志

第一步:自定义切面类:

import lombok.extern.slf4j.Slf4j;

@Component
@Aspect
@Slf4j //下方log.info()使用,需添加lombok依赖
public class LogAspect {
    @Autowired
    private User user;

    @Pointcut("@annotation(com.asd.utils.log.Log4Controller)")
    public void logPointcut(){
    }

    @Pointcut("@annotation(com.asd.utils.log.Log4Service)")
    public void logPointcut2(){
    }

    @Pointcut("@annotation(com.asd.utils.log.Log4System)")
    public void logPointcut3(){
    }

    
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = method.getName();
        Object[] args = joinPoint.getArgs();
        //将参数名称和参数值拼接
        String[] parameterNames = signature.getParameterNames();
        String[] a = new String[parameterNames.length];
        for (int i = 0; i  

第二步:自定义切面注解:
如:@Log4Controller、@Log4Service、@Log4System
@Pointcut("@annotation(com.asd.utils.log.Log4Controller)")
@Pointcut("@annotation(com.asd.utils.log.Log4Service)")
@Pointcut("@annotation(com.asd.utils.log.Log4System)"

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log4Controller {
    String value() default "";
}

第三步:业务类中使用切面注解:

//Controller层的类
public class UserController {
    @Log4Controller
    @GetMapping("/listUser")
    public ResponseEntity getUser(String name){
        List list=userService.getUser(name);
        return ResponseEntity.ok("查询成功!");
    }
}
//Service层的类
public class UserService {
    @Log4Service
    @Override
    public List getUser(String name){
        List list=userDao.getUser(name);
        return list;
    }
}
二、示例二:使用AOP实现Redis分布式锁

第一步:自定义切面类:

@Component
@Aspect
@Slf4j
public class RedisLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisProperties redisProperties;

    @Around("@annotation(com.asd.core.annotation.RedisLock)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        Method proxyMethod = ((MethodSignature)joinPoint.getSignature()).getMethod();
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
        RedisLock redisLock = realMethod.getAnnotation(RedisLock.class);
        if (joinPoint.getArgs().length>0&&joinPoint.getArgs()[0] instanceof UserDTO){
            String certNo=((UserDTO)joinPoint.getArgs()[0]).getCertNo();
            //Redis分布式锁,同一用户同一时间只能单一查询
            RLock lock=null;
            try{
                lock = redissonClient.getLock(redisLock.prefix()+realMethod.getName()+":"+certNo);
                log.debug("lock:{}",lock.getName());

                if (!lock.tryLock(-1, redisProperties.getLockExpireInSeconds(), TimeUnit.SECONDS)){
                    throw new SametimeQueryException();
                }
            }catch (RedisException e){
                log.error("Redis异常,加锁失败",e);
            }
            try {
                return joinPoint.proceed();
            }finally {
                try{
                    if (lock!=null){
                        lock.unlock();
                    }
                }catch (RedisException e){
                    log.error("Redis异常,解锁失败",e);
                }
            }
        }

        //一般不会走到这步,除非方法参数异常
        throw new UnknownException();
    }
}

第二步:自定义切面注解:

@Inherited
@documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    String prefix() default "shop:Lock:";
    long waitSecond() default -1;
    long expireTime() default 10;
    TimeUnit unit() default TimeUnit.SECONDS;
}

第三步:业务类中使用切面注解:
查询时:同一用户同一时间只能单一查询;

public class QueryServiceImpl implements QueryService{
    @RedisLock
    @Override
    public User queryUser(String name){
        ...
    }
}

注意一: tryLock()方法介绍::

详细参考一:Lock的tryLock()方法
详细参考二:redisson锁 tryLock的使用及正确用法

tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待;具有三种构造方法:①无参;②包含 失效时间、单位 两个参数的构造方法;③包含 等待时间,失效时间,单位三个参数的构造方法; 示例二的自定义切面类中用的是第三种。

public class TestReentrantLock {
    public void testReentrantLock(RedissonClient redisson){
        RLock lock = redisson.getLock("anyLock");
        try{
            //1.无参 (最常见的使用方法)
            //lock.lock();
            
            //2.两个参数:失效时间,单位 (支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁)
            //lock.lock(10, TimeUnit.SECONDS);
            
            //3.三个参数:等待时间,失效时间,单位 (尝试加锁,最多等待3秒,上锁以后10秒自动解锁)
            boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if(res){ //成功
                // do your business
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

注意二:1. RedisProperties.java:

@Component
@Data
@ConfigurationProperties("asd.core.redis")
public class RedisProperties {
    private Integer lockExpireInSeconds;
    private Integer rateLimiterTimeout;
}

2. application.yml:

pboc:
  core:
    redis:
      lock-expire-in-seconds: 30
      rate-limiter-timeout: 10

3. 对于@ConfigurationProperties(“asd.core.redis”)的使用:

详细参考:@ConfigurationProperties注解的基本使用

在SpringBoot使用@ConfigurationProperties注解读取yml/properties配置文件参数:在配置文件中所配置的参数都正确的注入到Java类对象中,而类中存在,但配置文件中没有配置的属性值默认为null。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/270138.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号