基础加强一.注解
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];