我们使用的框架底层实现原理都脱离不了java的高级特性,反射、注解,还有一个比较重要的设计模式:动态代理。今天就给大家分享下我自己对这些概念的理解。
反射是指程序可以访问、检测、修改它本身状态或行为的一种能力。
java反射机制是指在程序运行状态中,给定任意一个类,都可以获取到这个类的属性和方法;给定任意一个对象都可以调用这个对象的属性和方法,这种动态获取类的信息和调用对象的方法的功能称之为java的反射。
简言之:反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。
编译期:检查是否有语法错误,如果没有就将其翻译成字节码文件。即.class文件。
运行时:java虚拟机分配内存,解释执行字节码文件。
反射是通过.class文件来获取类的属性及方法的。
想要理解反射需要知道Class这个类,它的全称java.lang.Class
类。
java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,简言之:普通类是Class类的对象,Class类是所有类的类。(代码清单-javase.reflect.chapter1_3_1)
// 普通对象的创建
ReflectService reflectService = new ReflectService();
查看Class类的源码如下图(1.1),可以看到它的构造函数私有化了,所有我们不能用new的方式创建:
测试代码:
// 通过对象的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是同一个类
Class类对象是否是单例的?(代码清单-javase.reflect.chapter1_3_2)
是单列的,在生成Class对象的时候,首先判断内存中是否已经加载。
当我们编写一个新的Java类时,JVM就会帮我们编译成class对象,存放在同名的.class文件中。在运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象
测试代码:
Class<?> aClass1 = Class.forName("reflect.ReflectService");
ReflectService instance = (ReflectService) aClass1.newInstance();
instance.sayHello("zhangsan");
输出结果:
hello zhangsan
类的构造函数是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}
类的成员变量是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
类的成员方法是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’}
注解又称 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其他的一些方法:
方法名称 | 返回值 | 备注 |
---|---|---|
getGenericSuperclass | Type | 获取class对象的直接超类的 |
getGenericInterfaces | Type[] | 获取class对象的所有接口的type集合 |
isPrimitive | boolean | 判断是否是基础类型 |
isArray | boolean | 判断是否是集合类 |
isAnnotation | boolean | 判断是否是注解类 |
isInterface | boolean | 判断是否是接口类 |
isEnum | boolean | 判断是否是枚举类 |
isAnonymousClass | boolean | 判断是否是匿名内部类 |
isAnnotationPresent(Deprecated.class) | boolean | 判断是否被某个注解类修饰(参数为注解类型) |
getName | String | 获取class名字 包含包名路径 |
getPackage | Package | 获取class的包信息 |
getSimpleName | String | 获取class类名 |
getModifiers | int | 获取class访问权限(如图1.2) |
getDeclaredClasses | Class>[] | 获取内部类 |
getDeclaringClass | Class> | 获取外部类 |
getClassLoader | ClassLoader | 获取类加载器 |
getSuperclass | Class>[] | 获取某类所有的父类 |
getInterfaces | Class>[] | 获取某类所有实现的接口 |
图1.2:
为其他对象提供一个代理对象以控制对该对象的操作。
代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。
实现方式:创建接口 > 实现接口 > 创建代理类
如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理。具有下面几种特性:
代码如下:
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();
}
输出结果:
将喝醉的你扶上车
今天开了一辆白色的车
把你送到楼上将喝醉的你扶上车
今天开了一辆红色的车
把你送到楼上
代码如下:
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元
厂家通知:近期苹果要降价了
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。这个时使用动态代理就是最好的解决方案。
顾名思义,动态代理是利用反射机制在运行时在内存中动态的创建代理类。
这里我们用jdk的动态代理来实现。通常具有一下特性:
Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法也可以)。此外,也可以减少代码量,如果采用静态代理,类的方法比较多的时候,得手写大量代码。
在 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动态代理
厂家通知:近期苹果要降价了
代理商调整苹果价格,并通知顾客:
我们带着结果来看源码,我们知道动态代理生成的代理类是由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());
}
}
}
到此动态代理源码解析完毕,运行流程如下图:
引用java官方文档的一句话:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
通俗将注解就是为这个元素(类、字段、方法等)增加的说明或功能。例如:@Overvide这个注解就用来说明这个方式重写父类的。
接下我将从注解的定义、元注解、注解属性、自定义注解、注解解析及JDK 提供的注解这几个方面深入了解注解
日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,他是用的修饰符为 @interface
如图:新建注解
注解的格式:public @interface MyTestAnnotation {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}
这样一个注解就定义好了,此时我们还不能使用它,我们还需要给它定义一些元注解。
什么是元注解呢?我们可以理解为注解的注解,即修饰注解的注解。用来给予我们自定义的注解一些规则。
Java提供五种元注解,分别是@Target、@Retention、@Document、@Inherited和@Repeatable(jdk1.8加入)。主要作用如下:
名称 | 作用 | 属性值 | 取值 |
---|---|---|---|
@Target | 标识注解用于什么地方 | ElementType | TYPE:作用接口、类、枚举、注解 |
FIELD:作用属性字段、枚举的常量 | |||
METHOD:作用方法 | |||
PARAMETER:作用方法参数 | |||
CONSTRUCTOR:作用构造函数 | |||
ANNOTATION_TYPE:作用于注解 | |||
TYPE_PARAMETER:作用于类型泛型,即泛型方法、泛型类、泛型接口 | |||
TYPE_USE:类型使用.可以用于标注任意类型除了class | |||
@Retention | 表示在什么级别保留注解 | RetentionPolicy | SOURCE:注解仅存在于源码中,在class字节码文件中不包含 |
CLASS:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得 | |||
RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到 | |||
@Documented | 将注解中的元素包含到 Javadoc 中去 | ||
@Inherited | 允许子类继承父类注解 | ||
@Repeatable | 注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。 | 另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解 |
自定义注解的@Retention
取值必须是RetentionPolicy.RUNTIME
@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";
}
定义一个注解
@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()
定义@Value
注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
public @interface Value {
String value() default "value";
}
定义@Value
s注解
@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
得周期要小或相同。注解的属性其实和类中定义的变量有异曲同工之处,只是注解中的变量都是成员变量(属性),并且注解中是没有方法的,只有成员变量。
注解的本质就是一个Annotation接口
/**Annotation接口源码*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
Class<? extends Annotation> annotationType();
}
通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,其实他就是接口的方法,这就是为什么注解的成员变量会有括号,不同于接口我们可以在注解的括号中给注解的成员变量赋值。
注解属性类型可以是以下面列出的类型:
序号 | 类型 |
---|---|
1 | String |
2 | 枚举类型 |
3 | 注解类型 |
4 | Class类型 |
5 | 基本数据类型 |
6 | 以上类型的一维数组类型 |
如果注解只要一个属性,且属性名是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 {
}
当然是使用反射来获取了,这里再强调一下,自定义注解的@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);
}
注解 | 作用 | 备注 |
---|---|---|
@Override | 它是用来描述当前方法是一个重写的方法,在编译阶段对方法进行检查 | jdk1.5中它只能描述继承中的重写,jdk1.6中它可以描述接口实现的重写,也能描述类的继承的重写 |
@Deprecated | 它是用于描述当前方法是一个过时的方法 | |
@SuppressWarnings | 对程序中的警告去除 |
经过我们前面的了解,注解其实是个很方便的东西,它存活的时间,作用的区域都可以由你方便设置,只是你用注解可以做什么?
简单的举个例子,我们使用注解做一个参数校验,定义一个校验对象字段不为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: 地址不能为空
我们先创建一个普通web项目,而不是spring,也不导入任何spring依赖,并创建项目目录,如下:
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)]
这里我们创建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();
}
在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]);
}
}
}
}
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>com.smilevers.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>base-packageparam-name>
<param-value>com.smileversparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
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、启动项目,查看控制台:
controller访问成功
service层调用成功
dao层运行成功
我们自定义的mvc框架运行成功!简单的依赖关系: