Java的反射,动态代理,模版设计模式,

1、反射的概念:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

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

2、类加载:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载时机
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类

3、有3种方法获得class对象:

a.Object类的getClass()方法,判断两个对象是否是同一个字节码文件
b.静态属性class,锁对象
c.Class类中静态方法forName(),读取配置文件

因为class是java的关键字,所以我们用clazz来代替。
    Class clazz1 = Class.forName("com.example.bean.Person");//第三种方法
    Class clazz2 = Person.class;//第二种方法
    Person p = new Person();//第一种方法,通过对象来获取Class对象
    Class clazz3 = p.getClass();    

4、通过Class.forName读取配置文件:

为什么要使用配置文件呢?因为我们可以只改配置文件就可以改变你的需求。
例如:
        public class Demo {
            /**
             * * 榨汁机(Juicer)榨汁的案例
             * 分别有水果(Fruit)苹果(Apple)香蕉(Banana)桔子(Orange)榨汁(squeeze)
             * @throws IOException 
             */
            public static void main(String[] args) throws Exception {
                Juicer j = new Juicer();//创建榨汁机
                j.run(new Apple());
                j.run(new Orange());
            }
        }
        interface Fruit {//定义一个接口,下面所有的水果都实现这个接口,一种多态的思想。
            public void squeeze();
        }
        class Apple implements Fruit {
            public void squeeze() {
                System.out.println("榨出一杯苹果汁儿");
            }
        }
        class Orange implements Fruit {
            public void squeeze() {
                System.out.println("榨出一杯橘子汁儿");
            }
        }

        class Juicer {
        //参数定义为接口,我们传实现了这个接口的对象就可以实现调用不同对象的方法。
            public void run(Fruit f) {
                f.squeeze();
            }
        }
上面的代码有个问题就是,我们如果不想苹果和橘子,就需要改源码
    j.run(new Melon());
我们的解决方法是,我们可以通过配置文件将你需要传的类写在配置文件中。
例如:
    a.config.properties(工程目录下)文件内容:
        com.example.reflect.Apple
    b.我们将main函数改造下
            public static void main(String[] args) throws Exception {
                Juicer j = new Juicer();//创建榨汁机
                BufferedReader br = new BufferedReader(new FileReader("config.properties"));
                Class clazz = Class.forName(br.readLine()); //获取该类的字节码文件
                Fruit f = (Fruit) clazz.newInstance();  //创建实例对象
                j.run(f);//通过读取到配置文件,new的一个对象
            }
这样我们就可以通过配置文件到达动态改变的目的的,不要修改代码。

5、反射-获取有参构造方法:

Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数,
就不能这样创建了。
可以调用Class类的getConstructor()方法,里面可以给参数。
例如:
        Person.java:
        public class Person {
            private String name;
            private int age;
            public Person() {
                super();
            }
            public Person(String name, int age) {
                super();
                this.name = name;
                this.age = age;
            }

            //...生成get set方法

            @Override
            public String toString() {
                return "Person [name=" + name + ", age=" + age + "]";
            }
        }
    使用:
        public static void main(String[] args) throws Exception {
            Class clazz = Class.forName("com.example.bean.Person");
            Constructor c = clazz.getConstructor(String.class,int.class);   //获取有参构造
            Person p = (Person) c.newInstance("张三",23);//通过有参构造创建对象
            System.out.println(p);
        }
    注意参数也是使用class字节码,clazz.getConstructor(String.class,int.class);

6、反射-获取成员变量:

Class.getField(String)方法可以获取类中的指定字段(可见的,如果是private就拿不到)。
但是可以通过getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的值。
修改私有属性的值之前,需要先调用setAccessible(true)设置访问权限。
用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值。
例如:
        public static void main(String[] args) throws Exception {
            Class clazz = Class.forName("com.example.bean.Person");
            Constructor c = clazz.getConstructor(String.class,int.class);   //获取有参构造
            Person p = (Person) c.newInstance("张三",23);

            Field f = clazz.getDeclaredField("name");//暴力反射获取字段
            f.setAccessible(true);//去除私有权限
            f.set(p, "李四"); 
            System.out.println(p);
        }
这个可以修改一个对象的私有属性,可以看到反射机制强大。

7、反射-获取方法:

Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String,Class...)方法可以获取类中的指定方法,
加了Declasred的都是可以获取到私有的。
调用invoke(Object,Object...)可以调用该方法。
第一个Object是你传的对象,第二个Object是你方法的参数。

例如:
        我们在Person类里面加一个方法:
        public void eat() {
            System.out.println("吃饭");
        }
        public void eat(int num) {
            System.out.println("吃了" + num + "顿饭");
        }

        public static void main(String[] args) throws Exception {
            Class clazz = Class.forName("com.example.bean.Person");
            Constructor c = clazz.getConstructor(String.class,int.class);   //获取有参构造
            Person p = (Person) c.newInstance("张三",23);

            Method m = clazz.getMethod("eat");//获取eat方法
            m.invoke(p);//调用方法

            Method m2 = clazz.getMethod("eat", int.class);  //获取有参的eat方法
            m2.invoke(p, 10);//调用方法,有参的
        }
注意:关键是invoke方法使用,在很多的地方都可看到。

8、反射-通过反射越过泛型检查:

泛型只在编译期有效,在运行期会被擦除掉
例如:
        public class Test {
            public static void main(String[] args) throws Exception {
                ArrayList<Integer> list = new ArrayList<>();

                list.add(123);
                list.add(45);
                System.out.println(list);

                Class clazz = list.getClass();
                Method method = clazz.getMethod("add", Object.class);
                method.invoke(list, "adb");
                System.out.println(list);
            }
        }
神奇。

9、反射-动态代理:

先演示下代码:
    有个User.java接口
        public interface User {
            public void add();
            public void delete();
        }
    有个User接口的实现类
        public class UserImp implements User {
            @Override
            public void add() {
                System.out.println("权限校验");
                System.out.println("添加");
                System.out.println("日志");
            }

            @Override
            public void delete() {
                //System.out.println("权限校验");
                System.out.println("删除");
                //System.out.println("日志");
            }
        }
现在有个问题,就是我们添加和删除都需要权限校验和日志记录,这样每个方法都去写太麻烦
所以我们可以使用反射功能生成代理类,使用代理去做。

在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象.
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
最终会调用InvocationHandler的方法
    InvocationHandler Object invoke(Object proxy,Method method,Object[] args)

例如我们演示下:
a.创建一个类,实现InvocationHandler。
        MyInvocationHandler.java
        public class MyInvocationHandler implements InvocationHandler {
            private Object target;
            public MyInvocationHandler(Object target) {
                this.target = target;
            }
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                System.out.println("权限校验");
                method.invoke(target, args);//执行被代理target对象的方法
                System.out.println("日志记录");
                return null;
            }
        }
b.使用:
        public static void main(String[] args) {
            UserImp ui = new UserImp();
            ui.add();
            ui.delete();

            MyInvocationHandler m = new MyInvocationHandler(ui);//这个ui就是你要代理的对象
            User u = (User)Proxy.newProxyInstance(ui.getClass().getClassLoader(), ui.getClass().getInterfaces(), m);
            u.add();
            u.delete();
        }
使用代理后,在调用方法的时候默认就添加了校验和记录功能。

总结:
    为什么叫动态代理?因为我们可以脱离具体的对象,我们传任意对象都可以进行这样的操作。
    我们为什么要传一个接口过去呢?因为我们代理类需要实现和UserImp同样的接口,这样我们才能强转为User。
    其实我们自己也可以手动写个实现User接口的代理类,实现代理功能,叫做静态代理。只是只能代理User的实现类,
    比较局限,所以实际的开发中我们使用动态代理来扩展类的功能。

10、设计模式-模版(Template)设计模式:

例如我们想统计某段代码的运行时间,下面这段代码:
        public static void main(String[] args) {
            long start = System.currentTimeMillis();
            for(int i = 0; i < 1000000; i++) {
                System.out.println("test");
            }
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
有个问题,这个代码写死了,我们需要在一个类实现,我们调用类里面的方法就可以,改造:
        class GetTime {
            public long getTime() {
                long start = System.currentTimeMillis();
                code();
                long end = System.currentTimeMillis();
                return end - start;
            }

            public void code(){//这个方法还是写死了
                for(int i = 0; i < 1000000; i++) {
                    System.out.println("test");
                }
            }
        }
所以我们再改造:
        abstract class GetTime {//抽象方法必须放在抽象类中
            public final long getTime() {//定义为final是不让重写
                long start = System.currentTimeMillis();
                code();
                long end = System.currentTimeMillis();
                return end - start;
            }
            public abstract void code();//定义为抽象的,让子类实现
        }

        编写一个类,继承GetTime
        class Demo extends GetTime {
            @Override
            public void code() {
                int i = 0;
                while(i < 100000) {
                    System.out.println("test");
                    i++;
                }
            }
        }
        使用:
        public static void main(String[] args) {
            Demo d = new Demo();
            System.out.println(d.getTime());
        }
总结:
    模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现
    优点:使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
    缺点:如果算法骨架有修改的话,则需要修改抽象类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值