• java反射与注解详解,共同实现动态代理模式


    java反射与注解详解,共同实现动态代理模式

    个人主页:https://blog.csdn.net/hello_list
    id:学习日记
    不知不觉一年过去了,整整一年,这一年写了60多篇博客,其实具体22年四月份才开始有认真写,之前就随便发了几篇,博主内容持续输出,看到这里就点个关注吧,点关注不迷路
    在这里插入图片描述

    今天我们来学习下反射和注解

    思考:反射是什么?别的语言有没有反射,为什么会有反射,反射的作用有哪些?

    注解又是什么?注解的作用是什么?反射与注解是什么关系,怎么样产生关系相互使用?

    带着思考,我们开始学习java中的反射和注解

    回到问题,什么是反射?

    首先说明,反射是java特有的,jReflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

    要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。

    反射就是把java类中的各种成分映射成一个个的Java对象。
    例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把一个个组成部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
    加载的时候:Class对象的由来是将 .class 文件读入内存,并为之创建一个Class对象。

    不要想的太难,其实很简单,看完这一篇就理解了

    反射机制相关的包就都在java.lang.reflect.*;这个里面了

    反射获取class的三种方式

    我们想要获取一个类,就要获取它的.class文件,在获取类中的信息,那下面就是三种获取方式

    方式备注
    Class.forName(“完整类名带包名”)静态方法
    对象.getClass()
    任何类型.class

    比方说我这里有一个Student类

    package com.xuexi.springboottest.pojo;
    
    public class Student {
        private int sid;
        private String sname;
        private int sage;
        private String ssex;
    
        public int getSid() {
            return sid;
        }
    
        public void setSid(int sid) {
            this.sid = sid;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public int getSage() {
            return sage;
        }
    
        public void setSage(int sage) {
            this.sage = sage;
        }
    
        public String getSsex() {
            return ssex;
        }
    
        public void setSsex(String ssex) {
            this.ssex = ssex;
        }
    
        public Student(int sid, String sname, int sage, String ssex) {
            this.sid = sid;
            this.sname = sname;
            this.sage = sage;
            this.ssex = ssex;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "sid=" + sid +
                    ", sname='" + sname + '\'' +
                    ", sage=" + sage +
                    ", ssex='" + ssex + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    被jvm编译之后成为class文件,我想要再获取这个类,怎么获取呢,三种方式

    public class ReflectTest {
        public static void main(String[] args) throws ClassNotFoundException {
            // 方式一:通过包路径
            Class<?> aClass = Class.forName("com.xuexi.springboottest.pojo.Student");
            // 方式二:直接通过Student.class获取
            Class<Student> studentClass = Student.class;
            // 方式三:通过对象获取
            Student student = new Student();
            Class<? extends Student> aClass1 = student.getClass();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    不管通过哪种方式,我们总能够可以获取到类的字节码文件,也就是类,那我们有了这个类,我们可以做什么呢?我们既然能够拿到类的字节码文件,那这个类中的所有都是我们可以获取并且操作的了,干什么不可以呢?

    我们看下都有哪些操作方法

    我们可以看jdk的帮助文档,里面的所有方法属性信息都有

    在这里插入图片描述

    但是很多,我们这里挑几个主要的看看

    //获取包名、类名
    clazz.getPackage().getName()//包名
    clazz.getSimpleName()//类名
    clazz.getName()//完整类名
     
    //获取成员变量定义信息
    getFields()//获取所有公开的成员变量,包括继承变量
    getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
    getField(变量名)
    getDeclaredField(变量名)
     
    //获取构造方法定义信息
    getConstructor(参数类型列表)//获取公开的构造方法
    getConstructors()//获取所有的公开的构造方法
    getDeclaredConstructors()//获取所有的构造方法,包括私有
    getDeclaredConstructor(int.class,String.class)
     
    //获取方法定义信息
    getMethods()//获取所有可见的方法,包括继承的方法
    getMethod(方法名,参数类型列表)
    getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
    getDeclaredMethod(方法名,int.class,String.class)
     
    //反射新建实例
    clazz.newInstance();//执行无参构造创建对象
    clazz.newInstance(222,"韦小宝");//执行有参构造创建对象
    clazz.getConstructor(int.class,String.class)//获取构造方法
     
    //反射调用成员变量
    clazz.getDeclaredField(变量名);//获取变量
    clazz.setAccessible(true);//使私有成员允许访问
    f.set(实例,);//为指定实例的变量赋值,静态变量,第一参数给null
    f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
     
    //反射调用成员方法
    Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
    m.setAccessible(true);//使私有方法允许被调用
    m.invoke(实例,参数数据);//让指定实例来执行该方法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    测试

    public class ReflectTest {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            // 方式一:通过包路径
            Class<?> aClass = Class.forName("com.xuexi.springboottest.pojo.Student");
            // 方式二:直接通过Student.class获取
            Class<Student> studentClass = Student.class;
            // 方式三:通过对象获取
            Student student = new Student();
            Class<? extends Student> aClass1 = student.getClass();
    
            // 获取包名
            System.out.println(aClass.getPackage().getName());
            System.out.println(aClass.getSimpleName());
            System.out.println(aClass.getName());
    
            // 获取成员变量信息
            Field[] field = aClass.getFields();
            for (Field field1 : field) {
                System.out.println(field1.getName());
            }
    
            // 获取构造方法
            Constructor<?>[] constructors = aClass.getConstructors();
            System.out.println(constructors);
    
            // 获取方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                System.out.println(method.getName());
            }
    
            // 我们可以创建一个student对象
            Student o = (Student) aClass.newInstance();
            System.out.println(student);
    
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    结果

    在这里插入图片描述

    大家到这里可以尽情的去点方法,看看如何使用,不过一句话,只要能够拿到这个class类,我什么都可以获取到,包括调用它的方法,改造方法等等等等,都可以做到,当然这也是反射的缺点,因为有了反射,对于底层代码逻辑,就没有了安全。

    反射就是这样,没什么可多说的了,我们再来学习一个东西,就是注解。

    注解

    什么又是注解呢?注解本省没什么作用,就是用来标注的一种元数据,可以把注解理解成一种配置文件,这么说可能有人要反驳了,我们用到的注解都是有功能的,加上什么注解就可以做什么什么,确实是这样,但注解本身就是一种标记类型的元数据,那具体怎么产生意义性的实质行为呢,那配合着反射使用就再好不过了

    其实我们平常有会用到很多注解,比如

    • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
    • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
    • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

    作用在其他注解的注解(或者说 元注解)是:

    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
    • @Documented - 标记这些注解是否包含在用户文档中。
    • @Target - 标记这个注解应该是哪种 Java 成员。
    • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    从 Java 7 开始,额外添加了 3 个注解:

    • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
    • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
    • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次

    那我们先来了解如何自己去定义一个注解

    比如我这里就定义了一个注解,就这样就可以定义一个注解,我们来了解下这个注解的组成

    @Documented
    @Inherited
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.METHOD)
    public @interface MyAnnotation {
        String value() default "xuexi";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先是上面几个元注解,JDK5.0提供了四种元注解:Retention, Target, Documented, Inherited
    1、@Retention:用于指定修饰的注解的生命周期,@Rentention包含一个RetentionPolicy枚举类型的成员变量,使用@Rentention时必须为该value成员变量指定值:
    SOURCE:只在源文件中有效,编译器直接丢弃这种策略的注释,在.class文件中不会保留注解信息。反编译查看字节码文件:发现字节码文件中没有MyAnnotation这个注解
    CLASS: 在class文件中有效,保留在.class文件中,但是当运行Java程序时,不会继续加载了,不会保留在内存中,JVM不会保留注解。
    如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。
    RUNTIME:在运行时有效(即运行时保留),当运行 Java程序时,JVM会保留注释,加载在内存中了,那么程序可以通过反射获取该注释。
    2、@Target:用于修饰注解的注解,用于指定被修饰的注解能用于修饰哪些程序元素,即修饰位置。@Target也包含一个名为value的成员变量。
    3、@Documented:用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。默认情况下,javadoc是 不包括注解的,但是加上了这个注解生成的文档中就会带着注解了
    4、@Inherited: 被它修饰的Annotation将具有继承性。如果某个类使用了被 @Inherited修饰的Annotation,则其子类将自动具有该注解。

    其实两个没用,我们这要来了解这两个

    @Retention(RetentionPolicy.CLASS)  // 这个就是我们自定义的注解的生命周期,就像上面说的一样,一般我设置成RetentionPolicy.CLASS就可以了
    生命周期类型	描述
    RetentionPolicy.SOURCE	编译时被丢弃,不包含在类文件中
    RetentionPolicy.CLASS	JVM加载时被丢弃,包含在类文件中,默认值
    RetentionPolicy.RUNTIME	由JVM 加载,包含在类文件中,在运行时可以被获取到
    
    @Target(ElementType.METHOD)  // 表示注解可以使用在什么上面,我这里就是ElementType.METHOD方法上,或者还可以这些:
    Target类型	描述
    ElementType.TYPE	应用于类、接口(包括注解类型)、枚举
    ElementType.FIELD	应用于属性(包括枚举中的常量)
    ElementType.METHOD	应用于方法
    ElementType.PARAMETER	应用于方法的形参
    ElementType.CONSTRUCTOR	应用于构造函数
    ElementType.LOCAL_VARIABLE	应用于局部变量
    ElementType.ANNOTATION_TYPE	应用于注解类型
    ElementType.PACKAGE	应用于包
    ElementType.TYPE_PARAMETER	应用于类型变量
    ElementType.TYPE_USE	应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    就这样,就可以了,你已经学会注解了,注解就是一种标记,用来表示一个信息,数据,本身没有上面作用,那我们通过一个小例子就可以明白了,注解+反射的使用

    注解+反射的使用

    我们来做一个小例子,来学习注解+反射的使用,简单的模拟一下我们平时使用的注解都是怎么工作的

    首先我们定义一个注解

    @Documented
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAnnotation {
        String value() default "这里已经帮你写好了学习方法";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    光有一个注解没什么用,注解用来的是标记,标记上的数据有用啊,我们再配合反射的使用,原理:就是注解标记在类上一个信息,我们在类运行的使用,去扫描有没有,并且注解上的信息是什么,再通过注解上的信息用反射去动态实现需要帮这个类做些什么

    这也是代理模式的应用,大家可以去了解一下什么是代理,代理的实现方式有很多种,我们先用一种简单是实现下

    我们再创建一个对象,都没有去具体实现这个方法去做什么,但是我都用自己定义的注解,标记了这个方法应该做什么;

    public class User {
    
        @MyAnnotation("打游戏")
        public void play(){
    
        }
    
        @MyAnnotation("吃饭")
        public void eat(){
    
        }
    
        @MyAnnotation
        public void learning(){
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    怎么样让我们的注解发挥作用呢,怎么样用到反射呢,来一个代理

    public class ProxyUser {
    
        public Object proXY(User user,String methodName,@Nullable Object... param) throws InvocationTargetException, IllegalAccessException {
            // 通过反射拿到类信息
            Class<? extends User> aClass = user.getClass();
    
            // 看看类中的方法有没有被我们自定义注解标记的,并拿到注解信息
            Method[] methods = aClass.getMethods();
    
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    if (method.isAnnotationPresent(MyAnnotation.class)) {
                        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                        String value = annotation.value();
                        // 在这里我们就想当与可以把原本的方法覆盖了,
                        // 也可以拿到放的参数,以及参数类型,包括返回值,这里可以通过param接收参数
                        System.out.println("这里是代理对象帮你实现的方法,帮你实现了"+value+"方法");
                        return null;
                    }
                    // 如果方法本身就没有被我们的注解标记,我们就直接执行它自己的方法
                    return method.invoke(aClass, param);
                }
            }
            // 包括没有进for循环啊,这些都可以抛出异常什么的。进行下处理
            return null;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    测试

    public class Test01 {
        public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
    
            // 创建一个user对象
            User user = new User();
            // 我们可以先执行下本身的方法
            user.play(); // 当然就是什么都没有
    
            // 通过代理帮我们实现方法
            new ProxyUser().proXY(user,"play");
            new ProxyUser().proXY(user,"eat");
            new ProxyUser().proXY(user,"learning");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果

    在这里插入图片描述

    这样是不是就实现了,这就是代理的好处,有人可能认为,这样多写多少代码,这么多,但是这只是开始,往后只要是用到我这个注解的都可以通过这种方式,去实现一件事情;这就是代理,我不想做的就交给你,你帮我代理,做完就行

    这只是一种方式,通过自己去实现的代理,我说过代理有n种方式,这里再给大家演示一种,通过spring中的aop,来实现对一个方法执行的扫描,aop本质上横切就是代理

    是不是感觉这种方式比上面那个更好理解,其实都是一样的只是实现的步骤不一样吧,用spring有一个缺点,就是你这么做,你这个类必须是从spring容器中取出来的,为什么刚开始没有用aop,aop这个是spring的aop,我们要想用就必须用到spring,总所周知spring給java带来了春天,但是也有一个这样的说法,java现在已经严重被spring绑架了,所以说我们思考之前不要先想着具体架构实现,框架上的实现,在框架上实现,就通过最基本的代码来理解这个思想,总之一句话,要想自己轻松就要找到代理。

    @Component
    @Aspect
    public class UserAspect {
    
        @Pointcut("execution(* com.xuexi.springboottest.pojo..*.*(..))")
        private void myPointcut() {
        }
    
        /**
         * 环绕通知
         */
        @Around("myPointcut()")
        public void advice(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("around begin...");
            // 本身aop实现就是通过反射,这里的代理实现了更多的增强
            // 有before 环绕通知,方法执行后After
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            if (method.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                String value = annotation.value();
                // 在这里我们就想当与可以把原本的方法覆盖了,
                // 也可以拿到放的参数,以及参数类型,包括返回值,这里可以通过param接收参数
                System.out.println("这里是代理对象帮你实现的方法,帮你实现了"+value+"方法");
            }
    
            System.out.println("around after....");
        }
    
        @Before("myPointcut()")
        public void record(JoinPoint joinPoint) {
            System.out.println("Before");
        }
    
        @After("myPointcut()")
        public void after() {
            System.out.println("After");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    结果:

    在这里插入图片描述

    使用aop常用方法

    @Before("customerJoinPointerExpression()")
    public void beforeMethod(JoinPoint joinPoint){
    	joinPoint.getSignature().getName(); // 获取目标方法名
    	joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
    	joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
    	joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
    	Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
    	joinPoint.getTarget(); // 获取被代理的对象
    	joinPoint.getThis(); // 获取代理对象自己
    }
     
    // 获取目标方法上的注解
    private <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> clazz) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        return method.getAnnotation(clazz);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当然了,还有一种实现方式,基于java的原生jdk的动态代理,这里就留给大家实现以下了,可以参考我上面的代理,实现一样,这是一种思想,大家一定要理解。

    JDk动态代理

    本来想留下让大家自己去试着实现下,看到这里你可以不看,自己试着去实现下,然后回来看看是不是你的思路更好,如果更好可以把你的答案留在评论区,我们再来理解下,我觉得这几个理解特别好,摆出来就懂了。

    • 什么是动态代理
      1、使用 jdk 的反射机制,创建对象的能力, 创建的是代理类的对象。 而不用你创建类文件。不用写 java 文件。
      2、动态:在程序执行时,调用 jdk 提供的方法才能创建代理类的对象。
      3、jdk 动态代理,必须有接口,目标类必须实现接口,没有接口时,需要使用 cglib 动态代理。
    • 动态代理能做什么
      1、可以在不改变原来目标方法功能的前提下,可以在代理中增强自己的功能代码。
      2、背景示例。
      比如:你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用(但是只有 Class 文件):Demo.class。
    Demo dm = new Demo();
    dm.print();
    
    • 1
    • 2

    当这个功能缺少时, 不能完全满足我项目的需要。我需要在 dm.print() 执行后,需要自己在增加代码。
    这时就会用代理实现 dm.print() 调用时, 增加自己代码, 而不用去改原来的 Demo 文件。
    (在 mybatis,spring 中也有应用)

    2、就是当你写了一个接口,里面有方法,然后写了实现类实现方法,完成方法逻辑,然后有一天,想对这个方法进行修改,但是不想改源码,所以有两种方式可以实现。第一种是静态代理,创建一个类继承实现类,然后对方法进行修改,这样太局限,因为只能针对特定的类增强方法,有100个实现类就要创建100个子类去实现。第二种方式是动态代理,可以动态地生成代理类,这是可以接受的。

    实现动态代理,我们需要学会什么

    InvocationHandler 接口(调用处理器)

    就一个方法 invoke()。
    invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在 invoke() 方法中。

    • 代理类完成的功能:
      1、调用目标方法,执行目标方法的功能
      2、功能增强,在目标方法调用时,增加功能。
    • 方法原型:
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    
    • 1

    参数:

    Object proxy:jdk创建的代理对象,无需赋值。
    Method method:目标类中的方法,jdk提供method对象的
    Object[] args:目标类中方法的参数, jdk提供的。
    
    • 1
    • 2
    • 3

    Proxy类:核心的对象,创建代理对象。之前创建对象都是 new 类的构造方法()
    现在我们是使用Proxy类的方法,代替new的使用。
    方法: 静态方法 newProxyInstance()
    作用是: 创建代理对象, 等同于静态代理中的TaoBao taoBao = new TaoBao();

    参数:
    1、ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader类a,
    a.getCalss().getClassLoader(),目标对象的类加载器。
    2、Class[] interfaces:接口,目标对象实现的接口,也是反射获取的。 3、InvocationHandler h:我们自己写的,代理类要完成的功能。 返回值:就是代理对象 public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
    InvocationHandler h)

    还是之前的功能,但是我们需要添加一个东西,其实之前我们就应该遵守面向接口原则,但是之前的user类,确实没有遵守,不过刚好这里也做了强调了,使用JDK动态代理,被代理的对象必须要有接口

    添加接口

    public interface UserInter {
    
    
        public void play();
    
    
        public void eat();
    
    
        public void learning();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后我们写jdk的动态代理类,看到没spring中的aop简单实现

    // 必须实现实现InvocationHandler进行方法增强
    public class JDKProXYUser implements InvocationHandler {
    
        // 被代理对象
        private Object target;
    
        // 通过构造方法,传入被代理对象
        public JDKProXYUser(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Object proxy, Method method, Object[] args
            // 被代理对象,被代理对象方法,被代理对象执行方法的参数
    //        System.out.println(proxy);
    //        System.out.println(method);
    //        System.out.println(args);
            /**
             *  方法中的proxy,method都是拿的接口类中的
             *  比如这样:都是接口中的方法
             *  method.isAnnotationPresent(MyAnnotation.class)
             *                 || method.getDeclaringClass().isAnnotationPresent(MyAnnotation.class)
             */
    
            Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
            System.out.println("aop====方法前置增强====");
    
            // 看看类中的方法有没有被我们自定义注解标记的,并拿到注解信息
    
            if (target.getClass().isAnnotationPresent(MyAnnotation.class)
                    || targetMethod.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation = targetMethod.getAnnotation(MyAnnotation.class);
                String value = annotation.value();
                // 在这里我们就想当与可以把原本的方法覆盖了,
                // 也可以拿到放的参数,以及参数类型,包括返回值,这里可以通过param接收参数
                System.out.println("这里是代理对象帮你实现的方法,帮你实现了" + value + "方法");
            }
    
            System.out.println("aop====方法后置增强====");
    
            return null;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    测试

    public class Test02 {
        public static void main(String[] args) {
            // 面向接口创建一个user类
            User user = new User();
            // 创建代理类进行代理
            JDKProXYUser jdkProXYUser = new JDKProXYUser(user);
    
            // 这里需要这个帮我们实现代理,并且返回一个全新的代理对象,三个参数
            UserInter newUser = (UserInter) Proxy.newProxyInstance(
                    // 反射获取的被代理对向的类加载器
                    user.getClass().getClassLoader(),
                    // 目标对象实现的接口,所以这里为什么要实现接口
                    user.getClass().getInterfaces(),
                    // 实现代理的对象
                    jdkProXYUser
            );
    
            newUser.eat();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结果:

    在这里插入图片描述

    到这里就完事了,看下你的思路是什么样子的,最后这里再提一下,jdk的静态代理是什么,继承重写父类方法,这是静态代理。

    小结

    那都看到这里了,还不三连,点赞收藏关注吗,持续输出更好内容,bye~

  • 相关阅读:
    驱动:驱动相关概念,内核模块编程,内核消息打印printk函数的使用
    python安装 learn2learn库 || 在线安装方式或者本地安装
    MySQL数据库复习——索引
    asp.net core之Options
    STM32F10x SPL V3.6.2 集成 FreeRTOS v202112
    扁平数据转树形结构,让数据管理更清晰
    EL表达式
    Ribbon源码解析
    算法必刷系列之栈、队列、哈希
    先睹为快!界面控件DevExpress WPF这些功能即将发布
  • 原文地址:https://blog.csdn.net/hello_list/article/details/126990530