spring-AOP结合注解动态获取指定参数-实现特定的访问权限功能

什么是aop

链接: spring中的AOP.

AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP的优点就是降低代码之间的耦合,提高代码的复用性。
例如转账功能,在转账代码的前后需要一些非业务方面的处理,权限控制,记录日志,事务的开启与结束,这些代码就可以使用AOP将其切入到转账代码的前后,这样就可以很好地分离业务代码和非业务代码。

spring底层就是采用动态代理模式实现AOP的。采用了两种代理:
JDK 的动态代理,如果被代理了实现了接口,会默认使用jdk的动态代理。底层通过反射方式创建代理类的对象
CGLIB的动态代理,如果类没有实现接口,会使用CGLIB动态代理。底层是对代理类生成的class文件加载进来,通过修改其字节码生成子类来创建代理对象

AOP的术语

(1)目标对象(Target)
目标对象指 将要被增强的对象。即包含主业务逻辑的类的对象。
上例中的UserDaoImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。
当然,不被增强,也就无所谓目标不目标了。

(2)切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知,实际就是对业务逻辑的一种增强。

(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。

(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,
则 addUser()为切入点,而 doOther()仅为连接点。 
被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。

(5)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。
上例中的MyInvocationHandler 就可以理解为是一种通知。
换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
Advice有下面几种,这里使用常用的AspectJ方式:

前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
异常通知(After throwing advice):在连接点抛出异常后执行
最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
环绕通知(Around advice):在连接点之前和之后均执行。

(6)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。

(7)aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。

导入pom依赖

<dependency>
	<groupId>org.aspectjgroupId>
	<artifactId>aspectjweaverartifactId>
dependency>


<dependency>
	<groupId>com.alibabagroupId>
	<artifactId>fastjsonartifactId>
	<version>1.2.49version>
dependency>

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

自定义aop切点注解

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

/**
 * 自定义aop切点注解
 */
@Target({
     ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Power {
     

    String value()  default "";

}

自定义aop切面类

import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class PowerAspect {
     

    /**
     * Controller层切点 注解拦截
     */
    @Pointcut("@annotation(com.zm.aop.Power)")
    public void controllerAspect() {
     
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作的开始时间
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint){
     
        Power method = getMethod(joinPoint);
        String shopId = getAnnotationValue(joinPoint, method.value());
        System.out.println("shopId :" + shopId);
        // 获取参数,执行逻辑...
        if("1001".equals(shopId)){
     
            System.out.println("1001无权限访问");
            return;
        }
        System.out.println("正常访问");
    }

    /**
     * 后置通知 用于拦截Controller层记录用户的操作
     */
    @After("controllerAspect()")
    public void doAfter(JoinPoint joinPoint){
     

    }

    /**
     * 获取注解中对方法的描述信息
     */
    public static Power getMethod(JoinPoint joinPoint) {
     
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod().getAnnotation(Power.class);
    }

    /**
     * 获取注解中传递的动态参数的参数值
     *
     * @param joinPoint
     * @param name
     * @return
     */
    public String getAnnotationValue(JoinPoint joinPoint, String name) {
     
        String paramName = name;
        // 获取方法中所有的参数
        Map<String, Object> params = getParams(joinPoint);
        // 参数是否是动态的:#{paramName}
        if (paramName.matches("^#\\{\\D*\\}")) {
     
            // 获取参数名
            paramName = paramName.replace("#{", "").replace("}", "");
            // 是否是复杂的参数类型:对象.参数名
            if (paramName.contains(".")) {
     
                String[] split = paramName.split("\\.");
                // 获取方法中对象的内容
                Object object = getValue(params, split[0]);
                // 转换为JsonObject
                JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);
                // 获取值
                Object o = jsonObject.get(split[1]);
                return String.valueOf(o);
            }
            // 简单的动态参数直接返回
            return String.valueOf(getValue(params, paramName));
        }
        // 非动态参数直接返回
        return name;
    }

    /**
     * 根据参数名返回对应的值
     *
     * @param map
     * @param paramName
     * @return
     */
    public Object getValue(Map<String, Object> map, String paramName) {
     
        for (Map.Entry<String, Object> entry : map.entrySet()) {
     
            if (entry.getKey().equals(paramName)) {
     
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * 获取方法的参数名和值
     *
     * @param joinPoint
     * @return
     */
    public Map<String, Object> getParams(JoinPoint joinPoint) {
     
        Map<String, Object> params = new HashMap<>(8);
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] names = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
     
            params.put(names[i], args[i]);
        }
        return params;
    }
}

测试参数类

public class TestReq {
     

    private String shopId;

    public String getShopId() {
     
        return shopId;
    }

    public void setShopId(String shopId) {
     
        this.shopId = shopId;
    }


}

模拟接口

import com.zm.aop.TestReq;

public interface TestService {
     

    String testService2(TestReq testReq);

}

模拟接口实现类

import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.stereotype.Service;

// 业务层无侵入使用
@Service
public class TestServiceImpl implements TestService {
     

	// 使用对象参数
    @Power("#{testReq.shopId}")
    @Override
    public String testService2(TestReq testReq) {
     
        return "testService2";
    }

}

TestController

import com.zm.aop.Power;
import com.zm.aop.TestReq;
import com.zm.aop.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.server.PathParam;

@RestController
public class TestController {
     

    @Autowired
    private TestService testService;
	
	// 控制层无侵入使用
	// 使用参数
    @Power("#{shopId}")
    @GetMapping("/test1")
    public String test1(@PathParam("shopId")String shopId){
     
        return shopId;
    }

    @PostMapping("/test2")
    public String test2(@RequestBody TestReq testReq){
     
        return testService.testService2(testReq);
    }

}

测试

spring-AOP结合注解动态获取指定参数-实现特定的访问权限功能_第1张图片

spring-AOP结合注解动态获取指定参数-实现特定的访问权限功能_第2张图片

你可能感兴趣的:(业务场景,spring,java,aop)