Spring AOP思想(动态代理)

阅读数:212 评论数:0

跳转到新版页面

分类

python/Java

正文

一、Spring的AOP

Spring的AOP本质是一种动态代理,常用于权限控制、缓存、日志处理、事务控制等,实现中使用JDK动态代理(接口)和CGLAB动态代理(子类)。

二、AOP的常用术语

1、连接点(Joinpoint)

被拦截到需要被增强的方法。

2、切点(Pointcut)

是一组连接点的集合。

3、增强(Advice)

增强是织入到目标类连接点上的一段程序代码,表示要在连接点上做的操作。

根据时机分为:

@Before 前置增强 在执行目标方法之前运行。
@After 后置增强 在目标方法执行结束之后。
@AfterReturning 在目标方法正常返回值后运行。
@AfterThrowing 在目标方法出现异常后运行
@Around 在目标方法完成前、后做增强处理,核心是ProceedingJoinPoint,需要手动执行joinPoint.procced()。

4、切面(Aspect)

由切点和增强组成,SpringAOP就是负责实施切面的框架。

三、相关注解

1、@Aspect

定义切面类,将被标识的类作为一个切面bean

2、@Pointcut

由两部分组成:

(1)Pointcut signature

(2)Pointcut expression

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

四、pointcut expression

通配符:

*: 匹配任意数量字符。

..:匹配任意数量字符的重复,如在类型模型中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。

+:匹配指字类型的子类型,仅能作为后缀放在类型模式后面

spring aop支持指示符:

1、execution

(1)语法

execution表达式是我们在开发过程中最常用的,它可以匹配方法的返回类型、方法名、参数类型等。它的语法如下:

execution(
    modifier-pattern?  // 用于匹配public、private等访问修饰符
    ret-type-pattern  // 匹配返回类型,不可省略
    declaring-type-pattern?  // 匹配包类型
    name-pattern(param-pattern)  // 匹配类中的方法,不可省略
    throws-pattern?  // 匹配抛出异常方法
)

(2)示例

//任意public方法
execution(public * *(..))
//任意名称以set开头的方法	
execution(* set*(..))	
//com.xyz.service.AccountService接口中定义的任意方法
execution(* com.xyz.service.AccountService.*(..))	
//com.xyz.service包中定义的任意方法
execution(* com.xyz.service.*.*(..))
//com.xyz.service包中及其子包中定义的任意方法
execution(* com.xyz.service..*.*(..))	

2、within

限制匹配特定类型内的连接点(例如,匹配指定类内的所有方法)。

within(com.xyz.service.*)	//com.xyz.service包中的任意方法
within(com.xyz.service..*)	//com.xyz.service包中及其子包中的任意方法
within(java..IPointcutService+) //  java包或所有子包下IPointcutService类型及子类型的任何方法
within(@cn..Secure *) 持有cn..Secure注解的任何类型的任何方法

3、this

匹配可以向上转型为this指定的类型的代理对象中的所有join point。

//实现了com.xyz.service.AccountService接口的代理类中的所有方法
this(com.xyz.service.AccountService)	

4、target

匹配target指定类型的目标对象中的所有join point。

//实现了com.xyz.service.AccountService接口的目标类中的所有方法
target(com.xyz.service.AccountService)	

它与this有何区别呢?

public interface TargetClassInterface {
	void sayHello();
}
//------------------
@Component("targetClass")
public class TargetClass implements TargetClassInterface {
	@Override
	public void sayHello() {
		System.out.println("Hello World!");
	}
}
//---------------------
@Component
@Aspect
public class AspectClass {
	@Pointcut("this(com.qyh.test.aspectj.TargetClass)")
	public void pointCutInTargetClass() {}

	@Before("pointCutInTargetClass()")
	public void beforeAdvice() {
		System.out.println("我是前置增强");
	}
}
//---------------------
public class Test {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		TargetClassInterface targetClass = applicationContext.getBean("targetClass", TargetClassInterface.class);
		targetClass.sayHello();
	}
}
//---------------输出
Hello World!
// 如果改为
@Component
@Aspect
public class AspectClass {
	@Pointcut("target(com.qyh.test.aspectj.TargetClass)")
	public void pointCutInTargetClass() {}

	@Before("pointCutInTargetClass()")
	public void beforeAdvice() {
		System.out.println("我是前置增强");
	}
}
//---------------输出
我是前置增强
Hello World!

由于Spring AOP默认使用的是JDK动态代理为接口生成代理对象,其他理对象实现了接口TargetClassInterface,与TargetClass没有关系,使用this关键字时,该代理对象无法向上转型为TargetClass,因此该切面失败。

5、args

用方法参数去匹配方法,它指定的参数是运行时的类型,而execution指定的参数是声明类型。

//只有一个参数,且参数的运行时类型是java.io.Serializable的方法
args(java.io.Serializable)	

6、@annotation

匹配带有指定注解的方法。它仅匹配方法执行级别,也就是说,只有那些实际上在运行时执行的并且被指定注解标记的方法才会被匹配。

//被标注了org.springframework.transaction.annotation.Transactional注解的所有方法
@annotation(org.springframework.transaction.annotation.Transactional)	

示例

7、bean

匹配bean实例内的所有join point

//id或name为tradeService的bean实例的所有方法
bean(tradeService)	
//id或name以Service结尾的bean实例的所有方法
bean(*Service)	

8、@within

 

  • 它是类型级别的匹配,意味着如果一个类被指定注解标记,那么这个类里面的所有方法都会被匹配,不管这些方法上是否直接标注了注解。
  • 例如,@within(org.springframework.stereotype.Controller)会匹配所有在类上标注了@Controller注解的类中的方法,而不管这些方法上是否有@Controller注解。

 

五、示例:

1、示例1

(1)定义切面类@Aspect

@Aspect // 声明这是切面类
@Component // 注册到spring容器中
public class LogAspect {
    /**
     * @Pointcut() 定义切点&切点范围
     */
    @Pointcut("execution(* com.practice.AspectPractice.AspectDemo.printHello(...))")
    public void logPointcut() {
    }
 
    /**
     * @Before 前置增强
     */
    @Before("logPointcut()")
    public void before(JoinPoint point) {
        System.out.println(point.getTarget());
        System.out.println("前置增强");
    }
 
    /**
     * @Around 环绕增强
     */
    @Around("logPointcut()")
    public void around(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("环绕增强-执行切点前");
            joinPoint.proceed();
            System.out.println("环绕增强-执行切点后");
 
        } catch (Throwable t) {
            System.out.println("抛出异常");
            System.out.println(t);
        }
    }
}

(2)编写被增强的类

上面的execution内是一个正则表达式,“*”表示任意返回类型的方法,“...”表示任意参数个数。

package com.practice.AspectPractice;
 
import org.springframework.stereotype.Service;
 
@Service
public class AspectDemo {
    // 可以看到这里方法的路径,是上面切面类切点定义的范围中
    public void printHello() {
        System.out.println("hello,这里是pointcut");
    }
}

2、示例2

@Aspect
@Component
@Slf4j
public class AuthAop {

    @Autowired
    AdminHelper adminHelper;

    @Before(value = "@annotation(check)", argNames = "check")
    public void doAdminRoleCheck(AdminRoleCheck check) {
        Admin admin = adminHelper.currentAdmin();
        if (Objects.isNull(admin) || admin.getRole() != check.requiredRole()) {
            throw new BusinessException("Permission denied for this action", ResponseCode.PERMISSION_DENIED);
        }
    }
}
// 二者是等价的
@Aspect
@Component
@Slf4j
public class AuthAop {

    @Autowired
    AdminHelper adminHelper;


    @Pointcut("@annotation(com.binance.bcf.annotations.AdminRoleCheck)")
    public void adminRoleCheck() {
    }

    @Around(value = "adminRoleCheck()")
    public void doAdminRoleCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        AdminRoleCheck annotation = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(AdminRoleCheck.class);
        Admin admin = adminHelper.currentAdmin();
        if (Objects.isNull(admin) || admin.getRole() != annotation.role()) {
            throw new BusinessException("Permission denied for this action", ResponseCode.PERMISSION_DENIED);
        }
        joinPoint.proceed();
    }

}

3、示例3 获取相关内容

    @Pointcut("@annotation(com.sun.simpledemo.lock.DistributedLock)")
    public void pointcut() {
 
    }
 
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取切入点所在目标对象
        Object targetObj = joinPoint.getTarget();
        System.out.println("切入类名字:" + targetObj.getClass().getName());
        // 2.获取切入点方法的名字
        String methodName = joinPoint.getSignature().getName();
        System.out.println("切入方法名字:"+methodName);
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        System.out.println("切入方法名字:"+ methodSignature.getName());
        System.out.println("切入方法名字:"+ methodSignature.getMethod().getName());
        Class[] parameterTypes = methodSignature.getParameterTypes();
        System.out.println("切入方法入参类型:" + parameterTypes);
        /**
         * 获取方法上的注解
         * getDeclaredMethod可以获取任意方法,像protected修饰的
         * getMethod 只可以获取 修饰符为 public 的方法
         */
        //方式一,只支持cglib代理
        DistributedLock distributedLock = AnnotationUtils.findAnnotation(methodSignature.getMethod(), DistributedLock.class);
        System.out.println("切入方法的注释:" + (distributedLock != null ? distributedLock.expiration() : ""));
        //方式二,只支持cglib代理
        DistributedLock distributedLock1 = AnnotationUtils.getAnnotation(methodSignature.getMethod(), DistributedLock.class);
        System.out.println("切入方法的注释:" + (distributedLock1 != null ? distributedLock1.expiration() : ""));
        //方式三,只支持cglib代理
        Method method = methodSignature.getMethod();
        DistributedLock distributedLock2 = method.getAnnotation(DistributedLock.class);
        System.out.println("切入方法的注释:" + (distributedLock2 != null ? distributedLock2.expiration() : ""));
        //方式四,同时支持jdk代理和cglib代理
        DistributedLock distributedLock3 = targetObj
                .getClass()
                .getDeclaredMethod(methodSignature.getMethod().getName(), methodSignature.getParameterTypes())
                .getAnnotation(DistributedLock.class);
        System.out.println("切入方法的注释:" + (distributedLock3 != null ? distributedLock3.expiration() : ""));
 
        //4. 获取方法的参数
        Object[] args = joinPoint.getArgs();
        for(Object o :args){
            System.out.println("切入方法的参数:"+o);
        }
        return joinPoint.proceed();
    }

4、springboot获取请求参数(application/json)

Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();
Map<String,Object> paramMap = new HashMap<>();
for (int i=0;i<parameterValues.length;i++) {
    try {
         String s = JSON.toJSONString(parameterValues[i]);
         paramMap.put(parameterNames[i],s);
     } catch (Exception e) {
         continue;
     }
}



相关推荐

一、自定义注解 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

一、目标 默认对全局请求参数进行验证,无需加注解@Validated。 二、具体实现 1、定制全局异常处理器GlobalExceptionHandler /** * 全局异常处理 * * @au

当参数类型为json时,通过 Request().getParameterMap() 是获取不到参数的,可以通过  JSONObject.toJSONString(joinPoint.getArgs(

(1)Spring MVC是一个基于DispatcherServlet的MVC框架,DispatcherServlet是继承自HttpServlet的。Spring的IOC和AOP主要就用了java的

如果一个类交张spring管理,比如 &lt;bean id="foo1" class="demo.scope.foo" /&gt; 不指明scope就是单例。</p

@PathParam的声明允许你在URI路径中去映射你的方法将使用的参数。 @Path("/library") pu

方式1:通过@PostConstruct和@PreDestroy方法。 从Java EE5开始,Servlet增加了两个影响Servlet生命周期的注解。 方式2:通

一、概述 1、spring容器 spring中有三种bean容器:BeanFactory、ApplicationContext、WebApplicationContext。 (1)BeanFactor

有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,然后这样如果执行大量测试的话就很麻烦,并且不直观,如果想对执行的时间做进

一、request uri部分 @PathVariable 获取路径参数,形如url/{id} 二、request header部分 @RequestHeade