JavaWeb21-基础加强(注解;代理;类加载器;泛型反射

时间:2022-05-04
本文章向大家介绍JavaWeb21-基础加强(注解;代理;类加载器;泛型反射,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

基础加强一.注解

1. 注解介绍

注解概述

Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。格式:注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

作用:

通过注解可以描述程序如何运行,以及在什么阶段有作用,替代配置文件

Annotation 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。

jdk中的三个注解

@Override

它是用来描述当前方法是一个重写的方法.

作用:

在编译阶段对方法进行检查

注意事项:

jdk1.5: 它只能描述继承中的重写

jdk1.6: 它可以描述接口实现的重写,也能描述类的继承的重写

@Deprecated

它是用于描述当前方法是一个过时的方法.

过时的情况:

在旧的版本中的方法在新的版本中已经有了更好的实现,

旧的版本中的方法存在安全隐患,不建议使用时,可以标注成过时方法。

@SuppressWarnings

它是用于去除程序中的警告

常见值:

unused 变量未使用

deprecation 使用了不赞成使用的类或方法时的警告

unchecked 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。

fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。

path 在类路径、源文件路径等中有不存在的路径时的警告。

serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。

finally 任何 finally 子句不能正常完成时的警告。

all 关于以上所有情况的警告

2. 自定义注解介绍

注解声明

通过上面的三个注解的源代码可以发现,要声明一个注解通过 @interface

声明一个注解格式:

public @interface 注解名{}

注解本质

找到其.class文件,然后反编译(可以使用javap命令)

@interface MyAnnoation{}

反编译后的结果为

interface MyAnnotation extends Annotation
{
}

结论:

注解本质上就是一个接口。它扩展了java.lang.annotation.Annotation接口;

在java中所有注解都是Annotation接口的子接口。

注解成员

注解本质上就是一个接口,那么它也可以有属性和方法。

但是接口中的属性是 static final的,在注解中注解没有什么意义。

在开发中注解中经常存在的是方法。而在注解中叫做注解的属性.

3. 自定义注解-属性

注解属性类型

基本类型

String

枚举类型 Enum

注解类型 Annotation

Class类型

以上类型的一维数组类型

注解属性的使用

1.如果一个注解有属性,那么在使用注解时,要对属性进行赋值操作

例如:@MyAnnotation3(st = "aaa")

2.如果一个注解的属性有多个,都需要赋值,使用","分开属性

例如:@MyAnnotation3(st = "aaa",i=10)

3.也可以给属性赋默认值(如果属性有默认值,在使用注解时,就可以不用为属性赋值)

例如:double d() default 1.23;

4.如果属性是数组类型

方式1:可以直接使用 属性名={值1,值2,。。。}

例如:@MyAnnotation3(sts={"a","b"})

方式2:若数组的值只有一个也可以写成 属性名=值

例如:@MyAnnotation3(sts="a"})

5.对于属性名称 value的操作

(1).如果属性只有一个且名称叫value,那么在使用时,可以省略属性名称

例如:@MyAnnotation3("hello")

(2).如果有多个属性,都需要赋值,其中一个叫value,这时,必须写属性名称

例如:@MyAnnotation3(value="hello",i=10)

(3).如果属性只有一个且名称叫value,但它的类型是数组类型,可以省略value,

若只有一个值写法两种,格式同数组类型;

例如:

@MyAnnotation3({"abc"})

@MyAnnotation3("abc")

若为多个值必须加上”{}”

例如:@MyAnnotation3({"abc","def"})

4. 自定义注解-元注解

元注解作用

用于修饰注解的注解,可以描述注解在什么范围及在什么阶段使用等(注解上的注解)

元注解介绍

@Retention : 指定注解信息在哪个阶段存在 保留 注解保留在哪个阶段 值为枚举

格式:@Retention(RetentionPolicy.RUNTIME)

Source:在源码保留 用户

Class:在字节码中保留(内存中) 虚拟机

Runtime :在运行时保留

@Target : 指定注解修饰目标对象 此注解可以使用在哪里

格式:@Target(ElementType.METHOD)

TYPE:类上、接口上

FIELD :成员变量(字段)

METHOD:方法上

@Documented :使用该元注解修饰,该注解的信息可以生成到javadoc 文档中

@Inherited :如果一个注解使用该元注解修改,应用注解目标类的子类都会自动继承该注解

@Retention @Target 是自定义注解必须使用两个元注解

反射回顾:

获取Class对象方式

Class 类名.class

Class 对象.getClass();

Class Class.fromName(全限定名);

若有class对象

可以new出来对象

获取所有的方法

获取所有的字段

若获取Method对象

如何让方法执行 Method m=Class,getMethod(...)

Object invoke(Object obj,object... args)

通过m.invoke(...)执行方法

5. 案例-银行的最大转账金额

通过配置文件设定

public void account(String inname, String outname, double money) {
double maxMoney = Double.parseDouble(ResourceBundle.getBundle("bank")
.getString("MaxMoney"));
if (money > maxMoney) {
System.out.println("最大转账金额为" + maxMoney);
} else {
System.out.println(outname + "给" + inname + "转账:" + money);
}
}

通过注解替代配置文件设定

package cn.itcast.annotation.my;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankInfo {
int maxMoney() default 5000;
}
package cn.itcast.annotation;
import java.lang.reflect.Method;
import java.util.ResourceBundle;
import cn.itcast.annotation.my.BankInfo;
public class TestAnno1 {
/**
* @param args
* @throws SecurityException
* @throws NoSuchMethodException
*/
public static void main(String[] args) throws NoSuchMethodException,
SecurityException {
// TODO Auto-generated method stub
account("张三", "世纪", 30000);
}
@BankInfo
public static void account(String fromUser, String toUser, Integer money)
throws NoSuchMethodException, SecurityException {
// 1.获取规定最大金额
// 1.1 获取该方法上的注解
Method m = TestAnno1.class.getDeclaredMethod("account", String.class,
String.class, Integer.class);
// 判断方法上时候有指定的注解
boolean flag = m.isAnnotationPresent(BankInfo.class);
// 1.2获取注解属性的值
if (flag) {
// 若有该属性 才能获取
BankInfo bankInfo = m.getAnnotation(BankInfo.class);
int maxMoney = bankInfo.maxMoney();
// 2.判断
if (money > maxMoney) {
throw new RuntimeException("超过最大金额");
}
System.out.println(fromUser + "给" + toUser + "转账金额为:" + money);
}
}
}

注解使用---让注解具有功能,需要结合反射来完成.

创建一个注解 BankInfo,它具有一个属性 maxMoney类型是double类型.

添加两个元注解来描述注解是在runtime阶段有效果,并且是应用在方法上的.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankInfo {
double maxMoney();
}

u 在银行转账的方法上使用这个注解

@BankInfo(maxMoney=30000)
public void acccount(String name1, String name2, double money)

u 在account方法中,获取注解上的maxMoney属性值.

要结合反射来完成

1.在这个方法中获取本方法的Method对象

2.通过Method类中的getAnnotation(Class c)

参数就是注解类型的Class对象

3.得到注解对象后,可以获取注解属性值

6. 案例-jdbc获取链接

public class JdbcUtils {
@JdbcInfo(driverClassName = "com.mysql.jdbc.Driver", password = "abc", url = "jdbc:mysql:///day19", username = "root")
public static Connection getConnection() throws ClassNotFoundException,
SQLException, SecurityException, NoSuchMethodException {
//1.得到当前类的Class,在获取当前方法Method
Method method=JdbcUtils.class.getDeclaredMethod("getConnection");
//2.得到注解 JdbcInfo
JdbcInfo an = method.getAnnotation(JdbcInfo.class);
// 1.注册驱动
Class.forName(an.driverClassName());
// 2.获取连接
return DriverManager
.getConnection(an.url(),an.username(),an.password());
}
public static void main(String[] args) throws ClassNotFoundException,
SQLException, SecurityException, NoSuchMethodException {
System.out.println(getConnection());
}
}

二.代理模式

1. 代理模式介绍

即Proxy Pattern,23种常用的面向对象软件的设计模式之一

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

优点:

(1).职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的作用。

(3).高扩展性

结构:

一个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理

对象实现同一个接口,先访问代理类再访问真正要访问的对象。

静态代理回顾:

1.被包装类和包装类实现同一个接口或者是继承同一个类

2.包装类中有被包装类的引用

3.包装类对需要增强的方法增强

4.包装类对不需要增强的方法调用原来的方法

2. 动态代理介绍

Ø 动态代理与代理的区别

动态代理与代理模式原理是一样的,只是它没有具体的代理类,直接通过反射生成了一个代理对象。

关于动态代理实现:

jdk提供的Proxy类来实现,它要求,被代理的对象的类必须实现接口。

cglib它可以实现代理,它不要求必须实现接口(spring讲)

动态代理:

对需要的方法增强,控制我们的操作

在程序运行的时候,创建一个类,且创建该类的对象来做些事情

1.jdk中提供一个类可以用来创建动态代理对象

2.spring中 可以通过tglib来做

动态代理类proxy介绍

要求:

1.要有一个被代理类

2.创建一个代理类

java.lang.reflect.Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

Proxy类

newProxyInstance()方法,它是用于方便快捷创建一个代理对象.

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)参数1:ClassLoader 类加载器 将我们的.class文件加载到内存中

Class 对象.getClassLoader()

参数2:Class[] interfaces一组接口

用来规定代理类是那些接口子类,一般这些接口就是被代理的执行接口

参数3:执行处理类

用来处理要加强(控制)的方法的

loader:被代理对象的类加载器 Class.getClassLoader();

interfaces:被代理对象的所在类实现的接口的Class对象 Class.getInterfaces();

h: InvocationHandler 是代理实例的调用处理程序实现的接口。

返回值:

返回的是动态代理对象.

InvocationHandler介绍

代理实例的调用处理程序 实现的接口

每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法.

public Object invoke(Object proxy,Method method,Object[] args);

参数:

proxy就是代理对象,不建议使用,会出现内在溢出

method代表调用的方法

args方法的参数.

返回值:

invoke方法的返回值,就是通过代理对象调用行为后得到的结果

注意

千万不要在invoke中调用proxy

3. 案例-编码处理

三.类加载器

1. 类加载器介绍

Java类加载器(英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统ClassLoader类与类加载器组织结构

作用:

类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象

当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构

2. ClassLoader类介绍与类加载器组织结构

ClassLoader类介绍与常用api

java.lang.ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。

每个 Class 对象都包含一个对定义它的 ClassLoader 的引用。

类加载器组织结构

引导类加载器

bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。

加载rt.jar

可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:

URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}

因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库

扩展类加载器

extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。

加载ext.jar

应用类加载器

system classloader (App Classloader) - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。

加载classpath下的文件自己写的文件

3. 全盘负责委托机制

classloader 加载类用的是全盘负责委托机制。

全盘负责

即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。

委托机制

先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。

类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。

4. 自定义类加载器

自定义类加载器的作用

通过自定义类加载器,我们可以指定一个类的加载过程。

自定义类加载器的定义步骤

1.做一个类,继承ClassLoader ,就声明了一个类加载器.

2.重写findClass方法,这个方法就是得到我们指定的.class文件生成的Class对象.

3.在findClass方法中使用 defineClass

自定义类加载器的基本原理

就是将.class文件通过流读取到,得到一个byte[]数组。通过defineClass方法直接生成Class对象.

自定义类加载器案例

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
//参数是类的全名
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
String extname = name.replace(".", "\");
String filename = rootDir + "\" + extname + ".class";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
InputStream is = new FileInputStream(filename);
int len = -1;
byte[] b = new byte[1024];
while ((len = is.read(b)) != -1) {
baos.write(b, 0, len);
}
baos.flush();
baos.close();
is.close();
byte[] data = baos.toByteArray();
return defineClass(name, data, 0, data.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

四.泛型反射

1. 泛型反射介绍

泛型是应用于设计阶段。泛型在类,接口,方法上都可以使用.

2. 泛型反射常用api介绍

通过泛型得到Class对象.

Type type = this.getClass().getGenericSuperclass(); // 得到当前类上的泛型--父类型
Type[] params = ((ParameterizedType) type).getActualTypeArguments();
// 得到当前类上所有的泛型类型Class
clazz = (Class) params[0];