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

来源:互联网 发布:做菜单用什么软件 编辑:程序博客网 时间:2024/06/06 05:38

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());        }
总结:    模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现    优点:使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求    缺点:如果算法骨架有修改的话,则需要修改抽象类
原创粉丝点击