Spring AOP思想(动态代理)

阅读数:92 评论数: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

匹配指定类内的所有 join point(在Spring AOP中仅是方法执行点)

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)	

五、示例:

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