框架基础-反射、代理、注解

框架基本原理

前言

我们使用的框架底层实现原理都脱离不了java的高级特性,反射、注解,还有一个比较重要的设计模式:动态代理。今天就给大家分享下我自己对这些概念的理解。

一、反射

1.1 概念

反射是指程序可以访问、检测、修改它本身状态或行为的一种能力。

1.2 Java的反射机制

java反射机制是指在程序运行状态中,给定任意一个类,都可以获取到这个类的属性和方法;给定任意一个对象都可以调用这个对象的属性和方法,这种动态获取类的信息和调用对象的方法的功能称之为java的反射。

简言之:反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。

FAQ:
  1. 为什么是在运行时?
  • 编译期:检查是否有语法错误,如果没有就将其翻译成字节码文件。即.class文件。

  • 运行时:java虚拟机分配内存,解释执行字节码文件。

    反射是通过.class文件来获取类的属性及方法的。

1.3 java类类型

想要理解反射需要知道Class这个类,它的全称java.lang.Class类。

java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,简言之:普通类是Class类的对象,Class类是所有类的类。(代码清单-javase.reflect.chapter1_3_1)

  • 普通对象的创建
// 普通对象的创建
ReflectService reflectService = new ReflectService();
  • Class类的创建

查看Class类的源码如下图(1.1),可以看到它的构造函数私有化了,所有我们不能用new的方式创建:

图1.1:
在这里插入图片描述

测试代码:

// 通过对象的getClass()创建
Class aClass1 = reflectService.getClass();
// 通过类的.class创建
Class aClass2 = ReflectService.class;
// 通过Class的静态方法Class.forName创建
Class aClass3 = Class.forName("reflect.ReflectService");
if (aClass1 == aClass2) {
            System.out.println("aClass1和aClass2是同一个类");
        }
if (aClass2 == aClass3) {
    System.out.println("aClass2和aClass3是同一个类");
}

输出结果:

aClass1和aClass2是同一个类
aClass2和aClass3是同一个类

FAQ:
  1. Class类对象是否是单例的?(代码清单-javase.reflect.chapter1_3_2)

    是单列的,在生成Class对象的时候,首先判断内存中是否已经加载。

    当我们编写一个新的Java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象

1.4 反射的主要功能及操作
1.4.1运行时构造一个类的实例对象
  • 通过1.3的方法先构造类对象,然后再通过类对象的newInstance()方法创建实例对象。(代码清单-javase.chapter1_4_1)

测试代码:

Class<?> aClass1 = Class.forName("reflect.ReflectService");
ReflectService instance = (ReflectService) aClass1.newInstance();
instance.sayHello("zhangsan");

输出结果:

hello zhangsan

  • 通过获取类的构造函数来创建对象(代码清单-javase.reflect.chapter1_4_2)

类的构造函数是java.lang.reflect.Constructor类的对象,通过Class的下列方法可以获取构造函数对象:

// 获得该类所有的构造器,不包括其父类的构造器,但包括私有的构造器,传参可以获得指定参数的构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
// 获得该类所有public构造器,包括父类传参可以获得指定参数的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) 

测试代码:

Class<?> aClass1 = Class.forName("reflect.Person");
// 获取该类所有的public构造函数包括父类
Constructor<?>[] allConstructor = aClass1.getConstructors();
// 获取该类指定参数是string的构造函数
Constructor<?> publicConstructor = aClass1.getConstructor(String.class);
// 获取该类指定参数是string,int的私有构造函数
Constructor<?> privateConstructor = aClass1.getDeclaredConstructor(String.class, int.class);
for (Constructor<?> constructor : allConstructor) {
    System.out.println(constructor);
}
// 设置是否可访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不用设置
privateConstructor.setAccessible(true);
// 使用反射创建Person类的对象,并传入参数
Object instance = privateConstructor.newInstance("zhangsan", 30);
System.out.println(instance);

输出结果:

public reflect.Person(java.lang.String)
public reflect.Person()
Person{name=‘zhangsan’, age=30}

1.4.2运行时获取一个类的成员变量

类的成员变量是java.lang.reflect.Field类的对象,通过Class类的以下方法可以获取某个类的成员变量,值得一提的是变量是包含两部分的,变量类型和变量名

// 获得该类自身声明的所有变量,不包括其父类的变量,但包括私有的
public Field getDeclaredField(String name) 
// 获得该类自所有的public成员变量,包括其父类变量
public Field getField(String name) 

测试代码:(代码清单-javase.reflect.chapter1_4_3)

Class<?> personClass = Class.forName("reflect.Person");
// 获取该类的所有属性
Field[] allFields = personClass.getDeclaredFields();
// 获取该类的所有public属性包括其父类
Field[] publicFields = personClass.getFields();
// 获取该类指定名称的私有属性
Field nameField = personClass.getDeclaredField("name");
// 获取该类指定名称的公共属性
Field descField = personClass.getField("desc");
for (Field field : allFields) {
    System.out.println(field);
}
// 获取属性的值
Constructor<?> constructor = personClass.getConstructor(String.class);
Person instance = (Person) constructor.newInstance("lisi");
// 设置读取私有变量权限
nameField.setAccessible(true);
Object value = nameField.get(instance);
System.out.println(value);

输出结果:

private java.lang.String reflect.Person.name
private int reflect.Person.age
public java.lang.String reflect.Person.desc
lisi

1.4.3运行时获取一个类的成员方法并且调用它

类的成员方法是java.lang.reflect.Method的对象,通过java.lang.Class类的以下方法可以获取到类的成员方法,通过方法类Method提供的一些方法,又可以调用获取到的成员方法。

// 得到该类所有的方法,不包括父类的,但包括私有的
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 
// 得到该类所有的public方法,包括父类的
public Method getMethod(String name, Class<?>... parameterTypes) 

测试代码:(代码清单-javase.reflect.chapter1_4_4)

Class<Person> personClass = Person.class;
// 获取该类的所有方法
Method[] methods = personClass.getDeclaredMethods();
// 获取该类的所有public方法包括其父类
Method[] publicMethods = personClass.getMethods();
// 获取该类指定名称的私有方法
Method sayMethod = personClass.getDeclaredMethod("say");
// 获取该类指定名称的公共方法
Method setNameMethod = personClass.getMethod("setName", String.class);
for (Method method : methods) {
    System.out.println(method);
}
// 方法的调用
Person person = personClass.newInstance();
// 私有方法调用先设置权限
sayMethod.setAccessible(true);
sayMethod.invoke(person);
// 调用方法需要传入反射生成的实例对象,参数(参数可以是多个,按顺序)
setNameMethod.invoke(person, "wangwu");
System.out.println(person);

输出结果:

public java.lang.String reflect.Person.toString()
public java.lang.String reflect.Person.getName()
public void reflect.Person.setName(java.lang.String)
private void reflect.Person.say()
public java.lang.String reflect.Person.getDesc()
public void reflect.Person.setAge(int)
public void reflect.Person.setDesc(java.lang.String)
public int reflect.Person.getAge()
我是私有方法
Person{name=‘wangwu’, age=0, desc=‘null’}

1.4.4运行时获取一个类的注解

注解又称 Java 标注,是 JDK5.0 引入的一种注释机制。通过反射获取到上面元素,然后再通过其获取它们各自的注解。(类、属性、方法和构造函数上都可以获取各自的注解)

// 得到该元素所有注解,如果是类的话不包括父类的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
// 得到该元素所有注解,如果是类的话包括父类的
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) 

测试代码:(代码清单-javase.reflect.chapter1_4_5)

Class<Person> personClass = Person.class;
// 获取该类的所有注解
 Annotation[] annotations = personClass.getDeclaredAnnotations();
Field nameField = personClass.getDeclaredField("name");
// 获取方法上的所有注解
Annotation[] fieldAnnotations = nameField.getAnnotations();
for (Annotation annotation : annotations) {
    // 获取注解上的属性
    if (annotation.annotationType().equals(MyInterface.class)) {
        MyInterface myInterface = (MyInterface) annotation;
        System.out.println(myInterface.name());
    }
    System.out.println(annotation);
}

Class其他的一些方法:

方法名称返回值备注
getGenericSuperclassType获取class对象的直接超类的
getGenericInterfacesType[]获取class对象的所有接口的type集合
isPrimitiveboolean判断是否是基础类型
isArrayboolean判断是否是集合类
isAnnotationboolean判断是否是注解类
isInterfaceboolean判断是否是接口类
isEnumboolean判断是否是枚举类
isAnonymousClassboolean判断是否是匿名内部类
isAnnotationPresent(Deprecated.class)boolean判断是否被某个注解类修饰(参数为注解类型)
getNameString获取class名字 包含包名路径
getPackagePackage获取class的包信息
getSimpleNameString获取class类名
getModifiersint获取class访问权限(如图1.2)
getDeclaredClassesClass<?>[]获取内部类
getDeclaringClassClass<?>获取外部类
getClassLoaderClassLoader获取类加载器
getSuperclassClass<?>[]获取某类所有的父类
getInterfacesClass<?>[]获取某类所有实现的接口
  • Class类实现了Type接口

图1.2:

在这里插入图片描述

二、动态代理

2.1 代理模式

为其他对象提供一个代理对象以控制对该对象的操作。

代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。

实现方式:创建接口 > 实现接口 > 创建代理类

2.2 静态代理

如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理。具有下面几种特性:

  • 由程序员创建或者由第三方工具生成,再进行编译;在程序运行之前,代理类的.class文件已经存在了。
  • 静态代理类通常只代理一个类。
  • 静态代理事先知道要代理的是什么。
2.2.1简单静态代理实现

代码如下:

1、创建一个开车接口:

public interface Drive{
    /**
     * 开车
     */
    void drive();
}

2、实现接口:

public class WhiteCar implements Drive{
    @Override
    public void drive() {
        System.out.println("今天开了一辆白色的车");
    }
}

3、创建代理类:

public class DriveProxy implements Drive{
    private Drive drive;

    public DriveProxy(Drive drive) {
        this.drive = drive;
    }

    @Override
    public void drive() {
        // 执行前
        System.out.println("将喝醉的你扶上车");
        drive.drive();
        // 执行后
        System.out.println("把你送到楼上");
    }
}

测试代码:(代码清单-javase.proxy.chapter2_1)

/**
 * 简单静态代理实现
 */
@Test
public void chapter2_1() {
    Drive proxy1 = new DriveProxy(new WhiteCar());
    Drive proxy2 = new DriveProxy(new RedCar());
    proxy1.drive();
    proxy2.drive();
}

输出结果:

将喝醉的你扶上车
今天开了一辆白色的车
把你送到楼上

将喝醉的你扶上车
今天开了一辆红色的车
把你送到楼上

2.2.2反射实现静态代理

代码如下:

1、创建一个工厂接口:

public interface Factory{
    /**
     * 工厂销售货物
     * 
     * @param num 数量
     *            
     * @return 总金额
     */
    int sale(int num, int price) throws Exception;

    /**
     * 厂家通知
     * @throws Exception
     */
    void active() throws Exception;
}

2、实现接口:

public class AppleFactory implements Factory{

    @Override
    public int sale(int num, int price) {
        System.out.println("苹果厂家卖给了代理商 " + num + " 个苹果,总计" + num * price + "元");
        return num * price;
    }

    @Override
    public void active() {
        System.out.println("厂家通知:近期苹果要降价了");
    }
}

3、创建代理类:

public class FactoryProxy implements Factory{

    private Factory factory;
    // 接口中的方法
    private Method saleMethod;
    private Method activeMethod;

    public FactoryProxy(Factory factory) throws Exception {
        this.factory = factory;
        // 获取Factory的class对象
        Class<Factory> aclass = (Class<Factory>) factory.getClass();
        // 获取类所有的方法
        Method[] methods = aclass.getDeclaredMethods();
        for (Method method : methods) {
            if ("sale".equals(method.getName())) {
                saleMethod = method;
            }
            if ("active".equals(method.getName())) {
                activeMethod = method;
            }
        }
    }

    @Override
    public int sale(int num, int price) throws Exception {
        // 调用前
        System.out.println("顾客给了 " + num * price + "元,想要买" + num + "个苹果");
        // 修改入参调用
        int result = (int) saleMethod.invoke(factory, num, price - 5);
        // 调用后
        System.out.println("代理商调整苹果价格,并通知顾客:");
        // 修改返回值
        return result + 125;
    }

    @Override
    public void active() throws Exception {
        activeMethod.invoke(factory);
    }
}

测试代码:(代码清单-javase.proxy.chapter2_2)

/**
 * 反射静态代理实现
 */
@Test
public void chapter2_2() throws Exception {
    Factory proxy = new FactoryProxy(new AppleFactory());
    int money = proxy.sale(5, 20);
    System.out.println("苹果涨价了,总共需要"+money+"元");
    proxy.active();
}

输出结果:

顾客给了 100元,想要买5个苹果
苹果厂家卖给了代理商 5 个苹果,总计75元
代理商调整苹果价格,并通知顾客:
苹果涨价了,总共需要200元
厂家通知:近期苹果要降价了

2.2.3静态代理优缺点

使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。这个时使用动态代理就是最好的解决方案。

2.3 动态代理

顾名思义,动态代理是利用反射机制在运行时在内存中动态的创建代理类。

这里我们用jdk的动态代理来实现。通常具有一下特性:

  • 在程序运行时,通过反射机制动态生成
  • 动态代理类通常代理接口下的所有类
  • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定
  • 动态代理的调用处理程序必须实现InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类
  • Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库
2.3.1动态代理的优点

Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法也可以)。此外,也可以减少代码量,如果采用静态代理,类的方法比较多的时候,得手写大量代码。

2.3.2动态代理实现

在 java 的java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。

创建jdk代理的处理程序类:

public class JDKProxy implements InvocationHandler{
    /**
     * 服务对象
     */
    private Object target;

    public JDKProxy(Object target) {
        this.target = target;
    }

     /**
     * 增强方法,用来处理需要增强的逻辑
     * 
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是jdk动态代理");
        Object result = null;
        // 反射方法调用之前
        if (null != args && args.length > 0) {
            System.out.println("顾客给了 " + (int) args[0] * (int) args[1] + "元,想要买" + args[0] + "个苹果");
            // 更改参数
            args[1] = (int) args[1] - 5;
        }
        // 执行方法
        result = method.invoke(target, args);
        // 反射方法调用之后
        System.out.println("代理商调整苹果价格,并通知顾客:");
        if (null != result) {
            return (int) result + 125;
        }
        return result;
    }

}

测试代码:(代码清单-javase.proxy.chapter3_1)

@Test
public void chapter3_1() throws Exception {
    Factory apple = new AppleFactory();
    JDKProxy jdkProxy = new JDKProxy(apple);
    Factory appleProxy = (Factory) Proxy.newProxyInstance(apple.getClass().getClassLoader(),
            apple.getClass().getInterfaces(), jdkProxy);
    int money = appleProxy.sale(5, 20);
    System.out.println("苹果涨价了,总共需要" + money + "元");
	appleProxy.active();
}

输出结果:

我是jdk动态代理
顾客给了 100元,想要买5个苹果
苹果厂家卖给了代理商 5 个苹果,总计75元
代理商调整苹果价格,并通知顾客:
苹果涨价了,总共需要200元
我是jdk动态代理
厂家通知:近期苹果要降价了
代理商调整苹果价格,并通知顾客:

2.4 动态代理源码分析

我们带着结果来看源码,我们知道动态代理生成的代理类是由jvm在内存中拼装并生成的,那我们可以拿到这个生成的代理类吗?当然是可以的,看下面的代码我们把jvm生成的代理类保存在D盘中:

/**
 * 将生成的动态代理类保存到磁盘
 * 
 * @throws Exception
 */
@Test
public void generateDynamicClassFileToDisk() throws Exception {
    byte[] bytes = ProxyGenerator.generateProxyClass("appleClass", AppleFactory.class.getInterfaces());
    FileOutputStream out = null;
    try {
        out = new FileOutputStream("D:/appleClass.class");
        out.write(bytes);
        out.flush();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

然后用idea打开,会自动反编译出来,如下:

public final class appleClass extends Proxy implements Factory {
    private static Method m1;
    private static Method m2;
    //接口中的sale方法
    private static Method m3;
    //接口中的active方法
    private static Method m4;
    private static Method m0;

    // 构造方法,参数为InvocationHandler,就是我们写的jdk代理实现的接口
    public appleClass(InvocationHandler var1) throws  {
        super(var1);
    }

    // 接口中的sale方法
    public final int sale(int var1, int var2) throws Exception {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (Exception | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }
    
	// 接口中的active方法
    public final void active() throws Exception {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (Exception | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    
	// 默认会重写equals、hashCode、toString方法,也就是说这三个方法也被代理了
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // 静态代码块,初始化各个Method
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("proxy.staticproxy.Factory").getMethod("sale", Integer.TYPE, Integer.TYPE);
            m4 = Class.forName("proxy.staticproxy.Factory").getMethod("active");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这个类就是我们生成的动态代理类,看起来跟我们静态代理自己写的类是不是很相似。我们点击Proxy.newProxyInstance来查看源码:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    // 校验参数InvocationHandler对象是否为null
    Objects.requireNonNull(h);
    // 克隆被代理对象所有接口
    final Class<?>[] intfs = interfaces.clone();
    // 生成代理类之前检查访问权限,SecurityManager是java安全管理器
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * 先从缓存查找是否存在这个类,如果不存在再生成(最重要的方法)
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            // 检查代理类的访问权限
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        // 通过反射生成构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        // 如果Class作用域为私有,通过 setAccessible 支持访问
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 通过Proxy Class构造函数生成Proxy代理类实例对象
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

利用getProxyClass0(loader, intfs)生成代理类Proxy的Class对象。

/**
 * Generate a proxy class.  Must call the checkProxyAccess method
 * to perform permission checks before calling this.
 */
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //如果接口数量大于65535,抛出非法参数错误
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    //如果指定接口的代理类已经存在与缓存中,则不用新创建,直接从缓存中取即可;
    //如果缓存中没有指定代理对象,则通过ProxyClassFactory来创建一个代理对象。
    return proxyClassCache.get(loader, interfaces);
}

ProxyClassFactory内部类创建、定义代理类,返回给定ClassLoader 和interfaces的代理类。

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 代理类的名字的前缀统一为“$Proxy”
    private static final String proxyClassNamePrefix = "$Proxy";

    // 每个代理类前缀后面都会跟着一个唯一的编号,如$Proxy0、$Proxy1、$Proxy2
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * 验证类加载器加载接口得到对象是否与由apply函数参数传入的对象相同
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            /*
             * 验证这个Class对象是不是接口
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * 校验接口是否重复
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }
 		// 代理类所在的包
        String proxyPkg = null;    
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * 记录非公共代理接口的包,以便代理类将在同一个包中定义。
         * 验证所有非公共代理接口都在同一个包中。
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * 生成代理类的名称
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * 生成指定代理类的字节码文件
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // defineClass0是本地方法
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

到此动态代理源码解析完毕,运行流程如下图:

img

三、注解

3.1 概念

引用java官方文档的一句话:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。

通俗将注解就是为这个元素(类、字段、方法等)增加的说明或功能。例如:@Overvide这个注解就用来说明这个方式重写父类的。

接下我将从注解的定义、元注解、注解属性、自定义注解、注解解析及JDK 提供的注解这几个方面深入了解注解

3.2 注解的定义

日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,他是用的修饰符为 @interface

如图:新建注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C7RGHbcb-1595291796145)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20200714103452997.png)]

注解的格式:public @interface MyTestAnnotation {}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id();
    public String description() default "no description";
}

这样一个注解就定义好了,此时我们还不能使用它,我们还需要给它定义一些元注解。

3.3 元注解

什么是元注解呢?我们可以理解为注解的注解,即修饰注解的注解。用来给予我们自定义的注解一些规则。

Java提供五种元注解,分别是@Target、@Retention、@Document、@Inherited和@Repeatable(jdk1.8加入)。主要作用如下:

名称作用属性值取值
@Target标识注解用于什么地方ElementTypeTYPE:作用接口、类、枚举、注解
FIELD:作用属性字段、枚举的常量
METHOD:作用方法
PARAMETER:作用方法参数
CONSTRUCTOR:作用构造函数
ANNOTATION_TYPE:作用于注解
TYPE_PARAMETER:作用于类型泛型,即泛型方法、泛型类、泛型接口
TYPE_USE:类型使用.可以用于标注任意类型除了class
@Retention表示在什么级别保留注解RetentionPolicySOURCE:注解仅存在于源码中,在class字节码文件中不包含
CLASS:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented将注解中的元素包含到 Javadoc 中去
@Inherited允许子类继承父类注解
@Repeatable注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解

自定义注解的@Retention取值必须是RetentionPolicy.RUNTIME

3.3.1@Target

@Target注解可以包含多个取值,常用的TYPE、FIELD、METHOD、PARAMETER,下面的例子,定义一个注解并指定使用范围:

@Target({ElementType.METHOD,ElementType.TYPE}) // 可以使用在类上、方法上
@Retention(RetentionPolicy.RUNTIME) // 存在于运行时
@Inherited // 允许子类继承
public @interface UseCase {
    public int id();

    public String description() default "no description";
}
3.3.2@Inherited

定义一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InheritanceTest {
}

创建一个接口并使用自定义注解,创建一个继承类

@InheritanceTest
public class Parent {
}
public class Son extends Parent {
}

测试代码:(代码清单-javase.annotation.chapter3_1)

/**
 * 获取父类的注解
 */
@Test
public void chapter3_1() {
    Class<Son> sonClass = Son.class;
    InheritanceTest annotation = sonClass.getAnnotation(InheritanceTest.class);
    System.out.println(annotation);
}

输出结果:成功获取到了父类的注解

@annotation.inheritance.InheritanceTest()

3.3.3@Repeatable

定义@Value注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
public @interface Value {
    String value() default "value";
}

定义@Values注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Values {
    Value[] value();
}

定义一个测试类,在其方法上加上相同的注解@Value

public class AnnotationClass{

    @Value("hello")
    @Value("world")
    public void test() {
        System.out.println("注解测试");
    }
}

测试代码:(代码清单-javase.annotation.chapter3_2)

/**
 * @Repeatabl测试用例
 */
@Test
public void chapter3_2() {
    Class<AnnotationClass> aClass = AnnotationClass.class;
    Method[] methods = aClass.getMethods();
    for (Method method : methods) {
        if ("test".equals(method.getName())) {
            Annotation[] annotations = method.getDeclaredAnnotations();
            System.out.println(annotations.length);
            System.out.println(method.getName() + "的注解为:" + Arrays.toString(annotations));
        }
    }
}

因为test方法上使用了两个@Value注解,所以猜测打印注解长度为2,然后打印详情,可是结果并不同。

输出结果:

1
test的注解为:[@annotation.repeatable.Values(value=[@annotation.repeatable.Value(value=hello), @annotation.repeatable.Value(value=world)])]

结果显示,test方法上的注解长度为 1 , 且打印信息为@Values注解,它的值包含了使用的两个注解。
因此可知在jdk8中,相同注解只是以集合的方式进行了保存,原理并没有变化。

注意事项:

  • @Repeatable所声明的注解,其元注解@Target的使用范围要比@Repeatable的值声明的注解中的@Target的范围要大或相同,否则编译器错误,显示@Repeatable值所声明的注解的元注解@Target不是@Repeatable声明的注解的@Target的子集。
  • @Repeatable注解声明的注解的元注解@Retention的周期要比@Repeatable的值指向的注解的@Retention得周期要小或相同。
3.4 注解属性

注解的属性其实和类中定义的变量有异曲同工之处,只是注解中的变量都是成员变量(属性),并且注解中是没有方法的,只有成员变量。

3.4.1注解的本质

注解的本质就是一个Annotation接口

/**Annotation接口源码*/
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    Class<? extends Annotation> annotationType();
}

通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,其实他就是接口的方法,这就是为什么注解的成员变量会有括号,不同于接口我们可以在注解的括号中给注解的成员变量赋值。

3.4.2注解属性类型

注解属性类型可以是以下面列出的类型:

序号类型
1String
2枚举类型
3注解类型
4Class类型
5基本数据类型
6以上类型的一维数组类型
3.4.3注解属性赋值

如果注解只要一个属性,且属性名是value(),我们直接在注解后面的括号中直接给出该属性的值即可,如果是多个值,使用key = value的格式用逗号隔开即可。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String name() default "mao";
    int age() default 18;
}

@MyTestAnnotation(name = "father",age = 50)
public class Father {
}
3.4.5注解的获取

当然是使用反射来获取了,这里再强调一下,自定义注解的@Retention取值必须是RetentionPolicy.RUNTIME。获取注解的3个主要方法:

 /**是否存在对应 Annotation 对象*/
  public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

 /**获取 Annotation 对象*/
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
 /**获取所有 Annotation 对象数组*/   
 public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }    
3.5 jdk提供的注解
注解作用备注
@Override它是用来描述当前方法是一个重写的方法,在编译阶段对方法进行检查jdk1.5中它只能描述继承中的重写,jdk1.6中它可以描述接口实现的重写,也能描述类的继承的重写
@Deprecated它是用于描述当前方法是一个过时的方法
@SuppressWarnings对程序中的警告去除
3.6 注解的应用

经过我们前面的了解,注解其实是个很方便的东西,它存活的时间,作用的区域都可以由你方便设置,只是你用注解可以做什么?

简单的举个例子,我们使用注解做一个参数校验,定义一个校验对象字段不为null的注解,如果为null就排除异常。

定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
    String value() default "";
}

创建一个house对象:

public class House{

    private String address;
    private Double area;
    @NotNull("拥有者不能为空")
    private String owner;

定义校验器:

/**
 * 功能描述:校验参数类
 *
 * @author baixufeng
 * @create 2020/7/14
 */
public class Validator{

    public static void validate(Object o) {
        Class<?> aClass = o.getClass();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = null;
            try {
                value = field.get(o);
            } catch (Exception e) {
                e.printStackTrace();
            }
            NotNull annotation = field.getAnnotation(NotNull.class);
            if (annotation != null && null == value) {
                throw new RuntimeException(annotation.value());
            }
        }
    }
}

测试代码:(代码清单-javase.annotation.chapter3_3)

/**
 * 参数校验
 */
@Test
public void chapter3_3() {
    House house = new House();
    house.setArea(110.2);
    // house.setAddress("南京市玄武区");
    house.setOwner("正大天晴");
    Validator.validate(house);
}

我们把设置地址注释了,输出结果:

java.lang.RuntimeException: 地址不能为空

四、自己动手实现简单的MVC框架

我们先创建一个普通web项目,而不是spring,也不导入任何spring依赖,并创建项目目录,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5oKvteqC-1595291796146)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20200715111738915.png)]
在这里插入图片描述

pom文件:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
    </dependency>
</dependencies>

工程目录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PxtscnBp-1595291796148)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20200715111934027.png)]

4.1 自定义注解

这里我们创建5个注解,功能跟名称与spring保持一致

// Controller层
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
    String value() ;
}
// Service层
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
    String value() ;
}
// 持久层
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Repostory {
    String value();
}
// 对象根据名称自动注入
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
    String value() ;
}
// 映射路径
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface RequsestMapping {
    String value();
}
4.2 自定义DispatcherServlet

在servlet包下创建一个DispatcherServlet类继承HttpServlet,并重写它的三个方法,init()、doGet()、doPost()。这三个方法的作用分别是,init容器初始化的时候执行一次,doGet浏览器使用get请求的时候运行,doPost浏览器使用post请求的时候运行,doGet和doPost相当于Servlet的service方法,浏览器每次请求都会运行一次。

public class DispatcherServlet extends HttpServlet{
    // 扫描的基包
    private String basePackage;
    // 基包下所有带包路径的全限定类名
    private List<String> packageNames = new ArrayList<String>();
    // 注解实例化,注解上的value,实例化注解对象
    private Map<String, Object> instanceMap = new HashMap<String, Object>();
    // 带包路径的全限定类名,注解上的名称
    private Map<String, String> nameMap = new HashMap<String, String>();
    // url和方法的映射关系
    private Map<String, Method> urlMethodMap = new HashMap<String, Method>();
    // method和全限定名类名映射关系,主要为了通过method找到该方法的对象利用反射执行
    private Map<Method, String> methodPackageMap = new HashMap<Method, String>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        basePackage = config.getInitParameter("base-package");
        try {
            // 1、扫描基包得到全部的带包路径的全限定名
            scanBasePackage(basePackage);
            // 2、把带有注解的类实例化放入map中,key为注解上的value
            instance(packageNames);
            // 3、自动注入标注了@Autowired注解的对象
            springioc();
            // 4、url地址与方法的映射关系
            urlhandler();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String path = uri.replaceAll(contextPath, "");
        // 通过path找到method
        Method method = urlMethodMap.get(path);
        if (null != method) {
            // 通过method拿到controller对象,准备运行反射
            String packageName = methodPackageMap.get(method);
            String controllerName = nameMap.get(packageName);

            Object contoller = instanceMap.get(controllerName);
            method.setAccessible(true);
            PrintWriter writer = null;
            try {
                Object result = method.invoke(contoller);
                // 解析结果,并返回给浏览器
                resp.setContentType("text/html;charset=utf-8");
                writer = resp.getWriter();
                writer.write(result.toString());
                writer.flush();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } finally {
                try {
                    writer.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 方法和url的映射 方法和类名的映射
     * 
     * @throws ClassNotFoundException
     */
    private void urlhandler() throws ClassNotFoundException {
        if (packageNames.size() < 1) {
            return;
        }
        for (String name : packageNames) {
            Class<?> aClass = Class.forName(name);
            if (aClass.isAnnotationPresent(Controller.class)) {
                Method[] methods = aClass.getDeclaredMethods();
                StringBuffer baseUrl = new StringBuffer();
                if (aClass.isAnnotationPresent(RequsestMapping.class)) {
                    RequsestMapping annotation = aClass.getAnnotation(RequsestMapping.class);
                    baseUrl.append(annotation.value());
                }
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequsestMapping.class)) {
                        RequsestMapping annotation = method.getAnnotation(RequsestMapping.class);
                        baseUrl.append(annotation.value());
                        // 保存url与方法的关系
                        urlMethodMap.put(baseUrl.toString(), method);
                        // 保存方法与类名的关系
                        methodPackageMap.put(method, name);
                    }
                }
            }
        }
    }

    /**
     * 自动注入对象
     * 
     * @throws IllegalAccessException
     */
    private void springioc() throws IllegalAccessException {
        // 遍历实例化对象map集合
        for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Qualifier.class)) {
                    String name = field.getAnnotation(Qualifier.class).value();
                    field.setAccessible(true);
                    field.set(entry.getValue(), instanceMap.get(name));
                }
            }
        }
    }

    /**
     * 实例化所有带注解的类,并放入instanceMap集合中
     * 
     * @param packageNames
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void instance(List<String> packageNames)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (packageNames.size() < 0) {
            return;
        }
        for (String packageName : packageNames) {
            Class<?> aClass = Class.forName(packageName);
            if (aClass.isAnnotationPresent(Controller.class)) {
                Controller controller = aClass.getAnnotation(Controller.class);
                String controllerName = controller.value();
                // 保存实例化对象与注解value关系
                instanceMap.put(controllerName, aClass.newInstance());
                // 保存类的全限定名与注解value关系
                nameMap.put(packageName, controllerName);
                System.out.println("controller" + packageName + ",value:" + controller.value());
            } else if (aClass.isAnnotationPresent(Service.class)) {
                Service service = aClass.getAnnotation(Service.class);
                String serviceName = service.value();
                instanceMap.put(serviceName, aClass.newInstance());
                nameMap.put(packageName, serviceName);
                System.out.println("service" + packageName + ",value:" + service.value());
            } else if (aClass.isAnnotationPresent(Repostory.class)) {
                Repostory repostory = aClass.getAnnotation(Repostory.class);
                String repostoryName = repostory.value();
                instanceMap.put(repostoryName, aClass.newInstance());
                nameMap.put(packageName, repostoryName);
                System.out.println("repostory" + packageName + ",value:" + repostory.value());
            }
        }
    }

    /**
     * 扫描基包得到全部的带包路径的全限定名,并加入packageNames列表
     * 
     * @param basePackage
     */
    private void scanBasePackage(String basePackage) {
        // 根据类所在的包获取绝对路径
        URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        System.out.println(url.getPath());
        File basePackageFile = new File(url.getPath());
        System.out.println("sacn" + basePackageFile);
        File[] childFiles = basePackageFile.listFiles();
        // 递归获取类的全限定名
        for (File file : childFiles) {
            if (file.isDirectory()) {
                scanBasePackage(basePackage + "." + file.getName());
            } else if (file.isFile()) {
                packageNames.add(basePackage + "." + file.getName().split("\\.")[0]);
            }
        }
    }
}
4.3 配置web.xml文件
<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>com.smilevers.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>base-package</param-name>
            <param-value>com.smilevers</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
4.4 测试

1、创建dao层的接口和实现类,并添加注解@Repostory("studentDao")

public interface StudentDao {
    void getUser();
}

@Repostory("studentDao")
public class StudentDaoImpl implements StudentDao {
    public void getUser() {
        System.out.println("dao层运行成功");
    }
}

2、创建service层的接口和实现类,添加注解@Service("studentService"),并注入StudentDao

public interface StudentService {
    void getUser();
}

@Service("studentService")
public class StudentServiceImpl implements StudentService {

    @Qualifier("studentDao")
    private StudentDao studentDao;

    public void getUser() {
        System.out.println("service层调用成功");
        studentDao.getUser();
    }
}

3、创建controller层,添加@Controller("userController")@RequsestMapping("/user")注解,并注入StudentService

@Controller("userController")
@RequsestMapping("/user")
public class StudentController {
    @Qualifier("studentService")
    private StudentService studentService;

    @RequsestMapping("/getuser")
    public String getUser() throws IOException {
        System.out.println("controller访问成功");
        studentService.getUser();
        return "welcom myMVC";
    }
}

4、启动项目,查看控制台:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w3Qzp9Hd-1595291796149)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20200715154759938.png)]

controller访问成功
service层调用成功
dao层运行成功

我们自定义的mvc框架运行成功!简单的依赖关系:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值