java基础加强四(反射,代理模式,自定义注解)

一、反射

反射机制是java中一个很强大的功能,可以跳过jvm实例化对象的过程,直接实例化类的class对象,然后直接操作class对象,再运行过程中,几乎可以得到任何一个类的属性和方法,能够调用任何一个对象的方法和属性。

Class对象:一个类的描述,存储了类的信息。看一下api:

比如一个类叫Demo,在jvm加载的时候就会创建一个class对象,里边存储了Demo类的信息,有什么属性,方法,构造器,都在里边由描述了。但是对一个一个类来讲,就只有一个类对象,有多个实例也只用一个类对象。也就是说如果new两个Demo用的都是同一个class对象,class类是确实存在的一个类,是java.lang包下的一个类,所以它有很多方法,再调用之前需要先获得它的对象才行。

什么时候初始化Class类?

1.创建一个类的实例时,比如 new Demo()时

2.调用一个类的静态方法,访问一个类的静态变量的或者为静态变量赋值的时候。因为静态属性或方法是随着类的加载而加载,与对象无关,所以要想调用就需要先创建class对象。

3.使用反射强制创建某个类或者接口的class对象

4.使用java.exe命令直接运行某个主类时(比如直接在cmd中用java.exe来运行一个class文件)

当创建了class对象时,也就是说该类被加载了,只是不一定有实例对象,需要注意的是,静态代码是随着类的加载而加载,与对象无关。

class对象的获取方法

想获得一个类的class对象,有三种方式。下面直接用代码和注释说明。

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种,使用Object类的方法,getClass()
        User u =new User();//创建User的Class对象
        Class c1 = u.getClass();
        //第二种,调用内置属性class
        Class c2 = User.class;
        //第三种,使用Class类的静态方法forName(全名限定)
        Class c3 = Class.forName("wang.User");

        System.out.println(c1==c2);
        System.out.println(c2==c3);

    }
}

这三种相对于方便一点的是第二种,第三种更万能一些,可以在项目的所有位置得到class对象,只要知道类的全名限定类名就可以。所以用的也比较多,但如果代码种已经有实例对象,直接获得对象的class对象最方便。但无论哪种方式,获得的都是同一个class对象,因为每个类只有一个class对象,两个打印都是true。

使用Class对象

得到class对象以后就可以获得存储的类信息。使用Class类的一些方法。

1.获得构造方法

         批量获取构造方法:
           public Constructor[] getConstructors():获取所有"公有"的构造方法,得到Constructor数组
           public Constructor[] getDeclaredConstructors():获取所有的构造方法,包括私有、受保护、默认、公有
         获取单个的构造方法:
            public Constructor getConstructor(Class ... parameterTypes):获取某个"公有"的构造方法
          public Constructor getDeclaredConstructor(Class ... parameterTypes):获取某个构造方法,包括私有、受保护、默认、公有
       调用某个构造方法:
             Constructor的newInstance()

Constructor类封装的是单个的构造方法信息并提供访问权限。

例子:给User类添加一个公共String参数构造方法,然后通过反射获取并调用

public class Demo {
    public static void main(String[] args) throws Exception {
        //第三种,使用Class类的静态方法forName(全名限定)
        Class c3 = Class.forName("wang.User");
        Constructor con = c3.getConstructor(String.class);
        con.newInstance("小明");//调用方法并传值
    }
}

这里要注意一个地方,就是con.newInstance("小明")这一步,我们说Class对象是类信息,但不是类实例,要想调用方法必须实例化一个User,newInstance就相当于new了一个User对象,在堆中开辟了一块空间,然后再调用的。对于任何一个类来讲,如果没有定义任何构造器,是默认有一个无参的构造器。

2.获得成员属性:和获取构造方法很像,结果用Field类封装。

          批量获取成员属性:
            public Field[] getFields():获取所有"公有"的成员属性
            public Field[] getDeclaredFields():获取所有的成员属性,包括私有、受保护、默认、公有
         获取单个的成员属性,并调用:
             public Field getField(Class ... parameterTypes):获取某个"公有"的成员属性
            public Field getDeclaredField(Class ... parameterTypes):获取某个成员属性,包括私有、受保护、默认、公有
        为成员属性赋值:
            Field类的set(Object target,Object value)
例子:获取User的name私有属性,并赋值(注意,赋值也要有实例对象,所以要先使用newInstance方法)

public class Demo {
    public static void main(String[] args) throws Exception {
        //第三种,使用Class类的静态方法forName(全名限定)
        Class c3 = Class.forName("wang.User");
        Field  f= c3.getDeclaredField("name");//获取私有的属性
        Object o = c3.getConstructor().newInstance();//调用构造器实例化对象
        f.setAccessible(true);//如果是私有的属性访问前要设置“强制访问”
        f.set(o,"小明");
    }
}

3.获取成员方法:和前边两个很像,结果用Method类封装。

           批量获取成员方法:
             public Method[] getMethods():获取所有"公有"的成员方法
             public Method[] getDeclaredMethods():获取所有的成员方法,包括私有、受保护、默认、公有
          获取单个的成员方法,并调用:
              public Method getMethod(Class ... parameterTypes):获取某个"公有"的成员方法
              public Method getDeclaredMethod(Class ... parameterTypes):获取某个成员方法,包括私有、受保护、默认、公有
         调用成员方法:
             Method类的invoke(Object target,Object... args)

例子:获取User的show方法,调用并传入姓名和年龄

public static void main(String[] args) throws Exception {
        //第三种,使用Class类的静态方法forName(全名限定)
        Class c3 = Class.forName("wang.User");
        Object o = c3.getConstructor().newInstance();//调用构造器实例化对象
        Method m = c3.getMethod("show", String.class, int.class);
        m.setAccessible(true);//如果是私有设置“暴力访问”
        m.invoke(m,"小明",30);
    }

以上就是获得class对象后如何获得类的信息,我们可以看到,无论是公有还是私有的,都可以访问到并调用,这就绕开了封装的保护,所以反射是一个很强大的功能,在java框架中有很多应用的地方。

利用反射运行配置文件

如果配置一个内置管理员用户,如果在User类中写死,name是“小明”,年龄20岁,那么如果我想变成“小李”,年龄30岁,或者想换一个新类,就需要写一个新的User2类,源代码中使用User的地方也需要更改,然后重新生成安装包,非常麻烦,但如果我们能将信息写道配置文件中,然后通过反射来完成,就可以不改变源代码也能完成转换。

例子:user1和user2,并调用类中的成员方法设置名字

,我们可以假定开发时需要废掉user1换成user2,那么源代码就需要改写,例如

public static void main(String[] args) throws Exception {
       // User1 u =new User1();
       // u.setName("小明");
        User2 u =new User2(); u.setName("小李");
    }

如果写成反射并使用配置文件,首先创建配置文件my.properties

#类的全名限定
ClassName=wang.User1
#方法名
MethodName=setName
#实参
name=小明

使用properties类读取

 public static Object getValue(String key) throws IOException {
        Properties p =new Properties();
        //String path =Demo.class.getClassLoader().getResource("my.properties").getPath();
        FileReader in =new FileReader("my.properties");//注意,需要本类的class文件可以访问到该资源才可以这样写,或者直接写成绝对路径,
                                                               // 因为idea默认的输出目录时out,所以class文件,配置文件和源码可能并不在一起,需要自己设置
                                                               //或者使用上边的写法,使用ClassLoader找到和class文件一起的资源

        p.load(in);
        in.close();
        return p.getProperty(key);
    }

改造源代码

public class Demo {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName((String) getValue("ClassName"));
        Method m = c.getMethod((String) getValue("MethodName"),String.class);
        Object o = c.getConstructor().newInstance();
        m.invoke(o,getValue("name"));

    }

改成反射的形式,把设置名字的代码和具体的User1和User2解耦,直接读取配置文件中的内容,以后直接改配置文件即可,不需要修改源码,不需要重新安装和重启(不重启的前提是修改class文件能访问到的配置文件,而不是源文件能访问到的配置)等。

利用反射的java动态代理

所谓代理就是代为行事,在实际开发过程中会有类似这样的需求,需要在执行某个操作之前做一些事,之后做一些事,比如开启事务,提交事务,记录日志等等。这时候我们可以为原来的类写一个代理类。在代理类中完成操作。

比如写一个userService类来进行操作数据,有一个show方法。我想在show方法执行之前进行操作,或者之后执行操作,但是我不想改变show方法,因为有些地方用前后操作,这样就需要写一个代理类,代理类将show方法拿过来,可以复制,也可以继承。然后直接将操作加入进去即可。

public void show(){
        beforeDo();
        super.show();
        endDo();
    }
    //之前要做的事
    public void beforeDo(){
        System.out.println("============方法之前做的");
    }
    //之后要做的事
    public void endDo(){
        System.out.println("============方法之后做的");
    }

使用的时候直接使用代理类

public static void main(String[] args) {
       UserServiceProxy u =new UserServiceProxy();
       u.show();
    }

那么得到同样的结果:

============方法之前做的
========我是show方法======
============方法之后做的

现实开发中,代理模式比这要复杂,为了代码的灵活性,一般会让目标类和代理类实现同一个接口,然在代理类中使用构造方法传入目标类,然后调用目标类的方法。但是道理和这个一样,就是用代理类执行目标类的方法,并加上特有的操作,完成代理。

但是这样会有一个问题,就是代理类完全取代了原来的类,也就是说如果我有很多这样的操作,比如加日志,就需要写很多的代理类,然后每个调用的地方都要改。这就很麻烦。所以java提供了一种基于反射机制的动态代理。使得加日志这样的业务可以单独拿出来配给所有需要的类,实现解耦合。主要用到两个类,1.proxy,2.InvocationHandler

proxy英文释义就是代理,这个好理解,就是这个类负责进行动态代理相关的业务。

InvocationHandler这是个接口,它就是我们实现自定义操作需要的接口。

主要步骤有三个。

1.写一个自定义类实现InvocationHandler接口,重写invoke方法,如果懒得写可以直接在newProxyInstance中直接使用内部类,直接new InvocationHandler,然后重写invoke方法。

public class MyHandler implements InvocationHandler {
    private Object obj;
    public MyHandler(Object obj) {
        this.obj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeDo();
        Object invoke = method.invoke(obj, args);
        endDo();
        return invoke;
    }

    //之前要做的事
    public void beforeDo(){
        System.out.println("============方法之前做的");
    }
    //之后要做的事
    public void endDo(){
        System.out.println("============方法之后做的");
    }
}

除了beforeDo和endDo前面基本都是固定写法。

2.将UserService写一个接口和一个实现类的形式,我把UserService写成接口,用UserServiceImpl实现。

public class UserServiceImpl implements UserService{

    public void show(){
        System.out.println("========我是show方法======");
    }

3.使用proxy生成代理类。

 public static void main(String[] args) {
        UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),//代理目标类的加载器
                                                             UserServiceImpl.class.getInterfaces(),//代理目标类所实现的接口
                                                             new MyHandler(new UserServiceImpl()));//自定义操作类
        u.show();
    }

结果;

============方法之前做的
========我是show方法======
============方法之后做的
 

proxy的newProxyInstance方法,就是创建代理类的方法。主要有三个参数,因为我的show方法在UserServicImpl中,那么我想在show方法前执行beforeDo,之后执行endDo,代理目标就是UserServicImpl类。我们要生成一个UserServiceImpl的代理类。Myhandler的参数传入代理目标。

newProxyInstance方法的返回值是一个Object,我们可以用UserServic来接收,然后调用show方法。这样一来,MyHandler就可以给所有想加BeforeDo和endDo的类使用,只需要使用proxy.newProxyInstance方法生成代理就可以。但是前提是要有第二步,你要代理的类需要有接口。因为proxy生成代理是基于接口的,第二个参数就是接口数组,所以必须有接口。这样就可以实现多态,接收的时候可以用代理目标类来接收,也可以用接口来接收。如果我再写一个CustomerServiceImpl也实现了UserService接口,也同样可以生成动态代理执行show方法。同样用userService接收,这就是多态。

在开发中,经常将mian方法设计所在的类设计成创建动态代理工厂类,main方法设计成创建方法,在需要实例化目标类时候通过代理工厂创建,直接得到代理对象。

那么动态代理实现是逻辑原理是什么呢,答案就是反射。如果我们看newProxyInstance方法的源码,就可以看到这样的代码

第一个是使用代理工厂根据loader(我们传入的加载器)和intfs(我们传入的接口)生成代理类,就想我们写的UserServiceProxy一样,然后用反射里的getConstructor方法获取构造方法,最后调用newInstance方法,使用h(我们传入的MyHandler)生成实例对象,返回给我们。所以我们得到的代理类实例对象是拥有了自定义操作的。

三、java的注解

注解是给程序的标注,用于描述程序如何运行及在什么阶段来运行,英文Java Annotation ,实际开发中,最大的功能是用于替换配置文件,注解有多种类型,平时我们常用的有jdk种的几种,剩下的可以自定义。

1.jdk中的三个常用基本注解

@Override:检查子类是否是覆盖了父类的方法。

@Deprecated:说明已经过时了。

@SuppressWarnings({ "unused", "deprecation" }):抑制程序中的警告。unused警告的类型。{}数组。all抑制所有警告。

2.自定义注解

自定义注解的语法:@interface 它和class interface,enum一样是关键字,形容一个注解类,它的实际作用是扩展了lang.Annotation包下的Annotation类,但是你不能用extends或者implements关键字显性的去扩展,只能用@interface 来修饰。

public @interface MyAnnotation {
    
}

在注解类种可以定义属性,类似与int age();这样的,带括号的,在注解中是属性不是方法。基本类型、String、枚举类型、注解类型、Class类型、以上类型的一维数组类型。

public @interface MyAnnotation {

    int age() default 0;//基本数据类型

    String name() default "";//字符串

    String str() default "";//定义了default默认值,使用时该属性可以不传入

	String[] value();//定义为value,使用是可以不显性的传入,其他的名称必须显性使用

    //otherAnno anno();//其他的注解类

    Class clazz();//class对象,不是普通对象,是类对象。
}

使用时

@MyAnnotation("abc")
public class Demo {
    @MyAnnotation("abc")//多属性用逗号隔开,数组如果只传一个可以不加,
                        // 名字时value的属性如果只有value可以不写
    public void show(){
        System.out.println("执行了show方法");
    }
}

到此,你使用@MyAnnotation只是对show方法进行了标注,现在没有任何意义。所以要讲到注解的灵魂,“注解的反射”

在反射的包下有一个接口AnnotatedElement,像Class类,Method,Filed,Constructor等类都实现了该接口,该接口有几个方法

  1. <T extends Annotation> T getAnnotation(Class<T> annotationType):获得指定类型的注解的引用。如果有返回指定类型的注解,没有返回null。
  2. Annotation[] getAnnotations():得到所有的注解,包含从父类继承下来的。
  3. Annotation[] getDeclaredAnnotations():得到自己身上的注解。
  4. boolean isAnnotationPresent(Class<? extends Annotation> annotationType):判断有没有使用指定注解。

比如我们在

public class MyTest {
    public static void main(String[] args) {
        //通过反射得到demo类所有的方法
        Class<Demo> demoClass = Demo.class;
        Method[] methods = demoClass.getMethods();
        //使用isAnnotationPresent方法判断这些方法有没有使用MyAnnotation自定义注解,目前只有show方法使用
        for (Method method : methods) {
            boolean b = method.isAnnotationPresent(MyAnnotation.class);
            System.out.println(method.getName()+"使用注解:"+b);
        }
    }
}

打印结构全部时false,为什么呢?明明show方法上边有我们的注解,这里引出另一个概念,“元注解”

注解上的注解叫做元注解。(即:用于修饰注解的注解),比如supperessWarnings的注解类上还有注解@Target和@retention,他们时修改注解的注解。

1.@Retention:作用。改变自定义的注解的存活范围。

 那么自定义注解的存活范围时什么呢,默认时Class,也就是编译阶段,但是在运行阶段时不行的,所以要通过Retention重设,Retention接收一个RetentionPolicy,RetentionPolicy是一个枚举类,有三个值,SOURCE:源代码阶段,就是java文件,Class:编译阶段,class文件阶段,RUNTIME:运行阶段,加载阶段。

那么我们的MyAnnotation改 一下

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String[] value();
}

在运行MyTest,发现show放为true。说明 我们的设置生效了。如果现在我们想MyAnnotation只作用于show方法,不作用于demo类上,那么需要设置Target元注解

2.@Target:作用,指定该注解能用在什么地方。

@Target接收一个ElementType数组,ElementType是一个枚举类,里边是各种类型,比如TYPE表示类,FIELD属性,METHOD表示方法。

现在我们可以通过反射判断谁用了我们的自定义注解,如果判断为true,我们就可以模拟@Test注解,执行该方法(同过反射)

 public static void main(String[] args) throws Exception {
        //通过反射得到demo类所有的方法
        Class<Demo> demoClass = Demo.class;
        Method[] methods = demoClass.getMethods();
        //使用isAnnotationPresent方法判断这些方法有没有使用MyAnnotation自定义注解,目前只有show方法使用
        for (Method method : methods) {
            boolean b = method.isAnnotationPresent(MyAnnotation.class);
            if(b){
                method.invoke(demoClass.newInstance(),null);
            }
        }
    }

打印”执行了show方法“,在Demo类中所有使用了@MyAnnotation注解的方法都会被执行。这样自定义注解就有意了,我们还可以进一步使用注解中的属性,

if(b){
                //到该方法上的注解对象
                MyAnnotation at = method.getAnnotation(MyAnnotation.class);
                String[] value = at.value();
                if (value!=null){
                    System.out.println("使用了属性"+value[0]);
                }
                method.invoke(demoClass.newInstance(),null);
            }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值