反射机制-注解-AOP案例-记录日志-接口限流

一. 反射机制

1. 什么是反射

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

核心,本质

Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

作用

Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

使用反射机制可以动态获取当前class的信息 比如方法的信息、注解信息、方法的参数、属性等;

2. 反射机制的优缺点

第三方框架---创建对象 不是直接new 反射机制创建

目的:提供开发者能够更好封装框架实现扩展功能。

在一个类中 定义了一个私有属性/方法 反射机制破解私有属性

优点

在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点

  1. 反射会消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射;
  2. 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

3. 反射的用途:

反编译:.class-->.java

  1. 通过反射机制访问java对象的属性,方法,构造方法等

  2. JDBC加载驱动连接 class.forname

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动

  1. Spring容器框架IOC实例化对象

    <bean id="mayikt" class="com.mayikt.UserEntity"  />  
    
  2. 自定义注解生效(反射+Aop)

  3. 第三方核心的框架 mybatis orm

4. 反射技术的使用

Class类 代表类的实体,在运行的Java应用程序中表示类和接口

Field类 代表类的成员变量(成员变量也称为类的属性)

Method类 代表类的方法

Constructor类 代表类的构造方法

  1. getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。
  2. getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
  3. getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

4.1 反射常用的Api

  1. Object-->getClass
  2. 任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
  3. 通过class类的静态方法:forName(String className)(最常用)

Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");

// 1.第一种获取class方式
//        UserEntity userEntity = new UserEntity();
//        Class userClass = userEntity.getClass();
//        // 默认执行无参构造函数
//        UserEntity user2 = (UserEntity) userClass.newInstance();
//        System.out.println(user2==userEntity);
// 2.第二种方式 直接获取class
//        Class userClass = UserEntity.class;
//        UserEntity user2 = (UserEntity) userClass.newInstance();
//        System.out.println(user2);
// 3.第三种方式 类的完整路径地址
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity user3 = (UserEntity) aClass.newInstance();
System.out.println(user3);

4.2 运行期间,一个类,只有一个Class对象产生

验证:

public class Test01 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        // 1.第一种获取class方式
        UserEntity userEntity = new UserEntity();
        Class userClass1 = userEntity.getClass();
        // 2.第二种方式 直接获取class
        Class userClass2 = UserEntity.class;
        // 3.第三种方式 类的完整路径地址
        Class<?> userClass3 = Class.forName("com.mayikt.entity.UserEntity");
        System.out.println(userClass3);// 
        System.out.println(userClass1==userClass2);// true
        System.out.println(userClass1==userClass3);// true
    }
}

4.3 反射执行构造函数

1. 执行无参数构造函数
Class<?> userClass3 = Class.forName("com.mayikt.entity.UserEntity");
// 1.默认执行无参构造函数
UserEntity userEntity = (UserEntity) userClass3.newInstance();
2. 执行有参数构造函数
Constructor<?> constructor = userClass3.getConstructor(String.class, Integer.class);
UserEntity userEntity2 = (UserEntity) constructor.newInstance("mayikt", 22);
System.out.println(userEntity2.toString());

4.4 反射执行给属性赋值

1. 反射执行给公有属性赋值
// 1.给共有属性赋值
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
Field userNameField = aClass.getField("pubUserName");
UserEntity userEntity = (UserEntity) aClass.newInstance();
userNameField.set(userEntity, "mayikt");
System.out.println(userEntity.pubUserName);
2. 反射执行给私有属性赋值
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Field userName = aClass.getDeclaredField("userName");
// 设置允许访问私有属性
userName.setAccessible(true);
userName.set(userEntity, "mayikt");
System.out.println(userEntity.getUserName());

注意:userName.setAccessible(true); 该代码是设置允许访问私有属性

xception in thread "main" java.lang.IllegalAccessException: Class com.mayikt.test.Test03 can not access a member of class com.mayikt.entity.UserEntity with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.set(Field.java:761)
    at com.mayikt.test.Test03.main(Test03.java:28)

4.5 反射执行调用方法

1. 反射调用公有方法
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getMethods("mayikt");
mayikt.invoke(userEntity);
2. 反射调用私有方法
// 1.使用反射调用共有方法
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getDeclaredMethod("mayikt");
// 2.设置允许调用私有方法
mayikt.setAccessible(true);
mayikt.invoke(userEntity);
Exception in thread "main" java.lang.IllegalAccessException: Class com.mayikt.test.Test04 can not access a member of class com.mayikt.entity.UserEntity with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Method.invoke(Method.java:491)
    at com.mayikt.test.Test04.main(Test04.java:23)

解决办法:

mayikt.setAccessible(true) // 设置允许调用私有方法

3. 反射调用方法传递参数
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getDeclaredMethod("sum", Integer.class, Integer.class);
// 2.设置允许调用私有方法
mayikt.setAccessible(true);
Integer result = (Integer) mayikt.invoke(userEntity, 1, 2);
System.out.println(result);
4. 通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的

ArrayList<String> strings = new ArrayList<String>();
strings.add("mayikt");
//        strings.add(1);
Class<? extends ArrayList> aClass = strings.getClass();
Method add = aClass.getDeclaredMethod("add", Object.class);
Object invoke = add.invoke(strings, 1);
System.out.println(strings.size());
//        strings.forEach((t) -> {
//            System.out.println(t);
//        });

二. 注解

1. 注解概念

什么是注解

注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理

SpringBoot 全部都是采用注解化

常用注解

  • @Override 只能标注在子类覆盖父类的方法上面,有提示的作用
  • @Deprecated 标注在过时的方法或类上面,有提示的作用
  • @SuppressWarnings("unchecked") 标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial、unchecked、unused、all

元注解

元注解用来在声明新注解时指定新注解的一些特性

  • @Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
  • @Retention 指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME等
  • @Inherited 指定新注解标注在父类上时可被子类继承

常用注解

@Target(ElementType.METHOD) // 指定新注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期
@Inherited // 指定新注解标注在父类上时可被子类继承
public @interface MayiktName {
    public String name();
}

自定义注解 运行 :反射+aop

注解的Target

  • TYPE:类、接口(包括注解类型)和枚举的声明
  • FIELD:字段声明(包括枚举常量)
  • METHOD:方法声明
  • PARAMETER:参数声明
  • CONSTRUCTOR:构造函数声明
  • LOCAL_VARIABLE:本地变量声明
  • ANNOTATION_TYPE:注解类型声明
  • PACKAGE:包声明
  • TYPE_PARAMETER:类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
  • TYPE_USE:JavaSE8引进,此类型包括类型声明和类型参数声明

2. 获取注解信息

// 1.获取当前类上的注解
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
//        MayiktName declaredAnnotation = aClass.getDeclaredAnnotation(MayiktName.class);
//        System.out.println(declaredAnnotation);
// 2.获取当前方法上的注解
//        Method userNameMethod = aClass.getDeclaredMethod("getUserName");
//        MayiktName declaredAnnotation = userNameMethod.getDeclaredAnnotation(MayiktName.class);
//        System.out.println(declaredAnnotation);
// 3.获取字段上的注解
Field pubUserName = aClass.getDeclaredField("pubUserName");
final MayiktName declaredAnnotation = pubUserName.getDeclaredAnnotation(MayiktName.class);
System.out.println(declaredAnnotation);
// 4.获得构造方法注解
Constructor<TestAnnotation> constructors = clazz.getConstructor(new Class[] {});// 先获得构造方法对象
MyConstructorAnnotation myConstructorAnnotation = constructors.getAnnotation(MyConstructorAnnotation.class);// 拿到构造方法上面的注解实例
System.out.println(myConstructorAnnotation.desc() + "+" + myConstructorAnnotation.uri());

3. 注解如何生效

实际项目中 注解想生效通过反射+aop机制

4. 注解实现案例

4.1 自定义限流注解

对我们接口实现 限流 比如 每s 只能访问1次 或者每s 访问两次。

Maven

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.9.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>18.0</version>
    </dependency>
</dependencies>

使用谷歌的guava例子

/**
     * 每秒生成2.0个令牌
     * //
     */
private RateLimiter rateLimiter = RateLimiter.create(2.0);
@GetMapping("/get")
//    @MayiktCurrentLimit(name = "get", token = 1)
public String get() {
    boolean result = rateLimiter.tryAcquire();
    if (!result) {
        return "当前访问人数过多,请稍后重试!";
    }
    return "my is get";
}

4.2 封装自定义注解限流框架

1. 自定义注解
@Target(ElementType.METHOD) // 指定新注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期
@Inherited // 指定新注解标注在父类上时可被子类继承
public @interface CurrentLimit {
    String name() default "";
    double token() default 10.0;
}
2. 整合AOP实现接口限流

里面有处理日志的也是用aop注解的方式去处理

@Aspect
@Component
public class LogAspect {
    @Autowired
    private TDemoSysLogService sysLogService;

    private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap();
    @Pointcut("@annotation(com.ruangh.aop.product.annotation.SysLog)")
    public void logPointCut(){
    }

    @Pointcut("@annotation(com.ruangh.aop.product.annotation.CurrentLimit)")
    public void limitPoinCut(){}

    @Around("limitPoinCut()")
    public Object around1(ProceedingJoinPoint point) throws Throwable {
        // 获取拦截的方法名
        MethodSignature methodSignature = (MethodSignature)point.getSignature();

        CurrentLimit declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(CurrentLimit.class);
        String name = declaredAnnotation.name();
        double token = declaredAnnotation.token();
        RateLimiter rateLimiter = rateLimiters.get(name);
        if (null == rateLimiter){
            rateLimiter = RateLimiter.create(token);
            rateLimiters.put(name,rateLimiter);
        }
        // 开始限流
        boolean b = rateLimiter.tryAcquire();
        if (!b){
            return "当前访问人数过多,请稍后重试!!!";
        }
        return point.proceed();

    }

    //配置环绕通知,使用在方法logPointCut()上注册的切入点
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{
        long beginTime=System.currentTimeMillis();
        //执行方法
        Object result=point.proceed();
        //可以设置time,监控方法执行的时间,但是我没有去实现
        long time=System.currentTimeMillis()-beginTime;
        saveSysLog(point,time);
        return result;
    }

    private void saveSysLog(ProceedingJoinPoint point,long time){
        MethodSignature signature=(MethodSignature) point.getSignature();
        Method method=signature.getMethod();
        HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        SysLog sysLog=method.getAnnotation(SysLog.class);
        Date date = new Date();
        DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
        String format = dateFormat.format(date);
        //获取请求参数
        Object[] args=point.getArgs();
        String ip = CusAccessObjectUtil.getIpAddress(request);
        TDemoSysLog demoSysLog = new TDemoSysLog();
        demoSysLog.setId(UUID.randomUUID().toString().replaceAll("-",""));
        demoSysLog.setExecutionTime((int) time);
        demoSysLog.setIp(ip);
        demoSysLog.setUserName(sysLog.value().getAction());
        demoSysLog.setVisitMethod(method.getName());
        demoSysLog.setVisitTime(format);
        sysLogService.insert(demoSysLog);
    }
3. 使用效果
@RestController
@RequestMapping("/product")
@CrossOrigin
public class TDemoProductController {
    /**
     * 服务对象
     */
    @Resource
    private TDemoProductService tDemoProductService;

    @CurrentLimit(name = "get",token = 2)
    @GetMapping("get")
    public String get(){
        return "get方法";
    }

    @CurrentLimit(name = "add")
    @GetMapping("add")
    public String add(){
        return "add方法";
    }

    @SysLog(LogActions.FIND_PRODUCT_LIST)
    @GetMapping("/findProductList/{page}/{limit}")
    public Result findProductList(@PathVariable("page") String page,
                                  @PathVariable("limit") String limit){
        return Result.ok(tDemoProductService.findProductList(page,limit));
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容