一、注解(Annotation)
1.什么是注解:
注解可以说是注释的更高级的一种,相当于标记,注解同样不影响代码的执行,但是注解能够用来创建文档、跟踪代码中的依耐性、执行基本编译时的检查。
同时注解是一个接口,程序可以通过反射来获取指定程序中元素的Annotation对象,然后通过该对象获取注解中元数据信息信息。
即:==Annotation(注解)是Java提供的一种对元程序中元素关联信息和元数据(metadata)的一种途径和方法。==
[引入时间:jdk1.5之后]
标记可以用在包、类、方法、字段、方法参数及局部变量上来关联信息。这些信息被储存在Annotation的“name = value”结构对中。
注意:这些标记和关联的信息并不会影响程序中代码的任何执行,而至于可以用在反射的时候在运行期间被访问,那是只有通过特定的工具才能对Anntation类型中的数据访问和处理。
2.注解分类
按照注解参数个数分类:
- 标记注解:一个没有成员定义的Annotation类型被称为标记注解。
- 单值注解。
- 完整注解
根据注解的使用方法和用途分类:
- JDK内置系统注解。
- 元注解。
- 自定义注解。
系统内置标准注解:
3个标准内置注解,定义在java.lang中:
- @Override:用于修饰此方法重写父类的方法。
- @Deprecated:用于修饰已经过时的方法。
- @SuppressWarning:用于禁止特定的编译警告。
==@Override:限定重写父类方法==
使用这个注解来标记我们重写的方法,能够帮我们判断是否是重写,即方法名写错的时候,编译报错。
==@Deprecated:标记已过时==
使用这个注解标记某个方法,让这个方法称为过时的方法,当使用时候,能够提示使用者这个方法已经过时了,不推荐使用。
==@SuppressWarning:抑制编译器警告==
使用这个注解的时候,可以去除我们不想看到或者出现的警告。(目前不知道这个有什么用。)
抑制编译器警告参数:
- deprecation:使用了不赞成的类或方法时的警告。
- unchecked: 使用了未检查的转换时的警告,例如使用集合的时候没有用泛型(Generics)来指定集合保存的类型。
- fallthrough:当Switch程序块直接通往下一种情况而没有使用break时的警告。
- path:当类路径、源文件中有不存在的路径时的警告。
- serial:在可序列化的类上缺少serialVersionUID定义时的警告。
- finally:任何finally句子不能正常完成时的警告。
- all:以上所有的警告。
3.自定义注解
注解的深入学习,自定义注解。想要自定义注解,首先我们要了解元注解及相关自定义注解的语法。
①元注解(meta-annotation):
元注解的作用是注解其他注解(也就是我们自定义的注解包括系统内置标准注解)。
- @Target
- @Retention
- @Documented
- @Inherited
==@Target==
这个元注解说明了Annotation所修饰的范围:即注解可被用于package、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数、本地变量(循环变量、catch参数等)。
用于描述注解的使用范围,这个范围的取值(ElementType)有:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述属性
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型)和enum申明、
使用实例:
/**
* 注解Table可用于注解类、接口(包括Annotation类型)或enum申明
*/
Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
*/
public abstract String tableName() default "className";
}
/**
* 注解NoDBColumn仅可用于注解类的成员变量
*/
Target(ElementType.FIELD)
public @interface NoDBColumn {
}
==@Retention==
作用:表示在什么级别保存注解信息,用于描述注解的生命周期(即被描述的注解在什么范围内有效),取值(RetentionPoicy):
- SOURCE:在源文件中有效
- CLASS:在class文件中有效
- RUNTIME:在运行时有效
具体实例:
/**
* Column注解的RetentionPolicy属性值是RUNTIME,这样注解处理器就可以通过
* 反射,获取到该注解的属性值,从而做一些运行时的逻辑处理
*/
@(Java高级)[感谢网上各个大神文章, 让我们明悟!!!]
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public abstract String name() default "fileName";
public abstract String setFuncName() default "setField";
public abstract String getFuncName() default "getField";
public abstract boolean defaultDBValue() default false;
}
==@Documented==
是一个标记注解,没有成员。用于描述其他类型的annotation应该作为被标注的程序成员的公共API,即可以被javadoc这样的工具文档化。
具体实例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public abstract String name() default "fileName";
public abstract String setFuncName() default "setField";
public abstract String getFuncName() default "getField";
public abstract boolean defaultDBValue() default false;
}
==@Inherited==
这也是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
具体实例:
@Inherited
public @interface Greeting {
public enum FontColor {
BULE, RED, GREEN
};
String name();
FontColor fontColor() default FontColor.GREEN;
}
②自定义注解
自定义注解格式:
public @interface 注解名{定义体}
注意事项:自定义注解时,自动继承java.lang.Annotation接口 ,由编译程序自动完成其他细节。同时,不能继承其他接口或注解。
定义体中每一个方法实际上都是一个配置参数,方法名=参数名,返回值类型=参数的类型(返回值类型只能是基本类型、Class、Annotation、String、enum和以上所有类型的数组),可以通过default申明参数的默认值。
参数设定:
- 只能用
public
和默认(default)这两个访问权限修饰符修饰。 - 只有一个参数成员时,最好将参数名称设置为"value"。
具体实例:
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.BULE;
}
//被注解的类
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor = FruitColor.Color.RED)
private String appleColor;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void displayName() {
System.out.println("水果的名字是:苹果");
}
}
==注意:==
注解元素必须要有确定的值,即要么在定义注解中设置默认值,要么在使用注解时给定值,一般使用空字符串或0作为默认值,但是很难看出这个元素是存在或缺失的的特征,所以我们在注解定义的时候,给元素赋上特殊的值,空字符串或-1,这个是一种习惯。
4.注解处理器
对于我们写的注解,如果不能去读取它的话,那还不如写注释呢!
所以,接下来我们要做的就是创建使用注解处理器。
①注解处理器类库(Java.lang.reflect.AnnotatedElement)
我们知道Annotation接口是所有Annotation类型的父接口,在java.lang.reflect包下有一个AnnotatedElement接口(它是所有程序元素的父接口),它有一些实现类使我们需要的:
- Class :类的一些定义
- Constructor :类的构造器定义
- Field :类的成员变量定义
- Method :类的方法定义
- Packet :包的一些定义
在java.lang.reflect包下有很多工具,是在我们反射的时候需要用的。
程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的4个方法来访问Annotation信息:
- T getAnnotation(Class annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
- Annotation[] getAnnotations():返回该程序元素上所有存在的注解。
- boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在返回true,不存在返回false。
- **Annotation[] getDeclaredAnnotaions(): **返回直接存在于此元素上的所有注解。(忽略继承过来的注解)如果没有直接存在于次元素上的注解,则返回一个长度为0的数组。
下面是一个简单的例子:
==自定义注解==
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果颜色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color{
BULE , RED , GREEN
};
/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.BULE;
}
/**
* 水果供应者注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1;
/**
*
* 供应商名称
*/
public String name() default "";
/**
*
* 供应商地址
*/
public String address() default "";
}
==自定义注解处理器==
/**
* 注解处理器
*/
public class FruitInfoUtil {
public static void getFruitInfo(Class> clazz){
String strFruitName = "水果名称:";
String strFruitColor = "水果颜色:";
String strFruitProvicer = "供应商信息:";
//返回直接存在于clazz上的所有注释
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.
getAnnotation(FruitName.class);
strFruitName = strFruitName + fruitName.value();
System.out.println(strFruitName);
}else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor = (FruitColor) field.
getAnnotation(FruitColor.class);
strFruitColor = strFruitColor + fruitColor.fruitColor();
System.out.println(strFruitColor);
}else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider = (FruitProvider) field.
getAnnotation(FruitProvider.class);
strFruitProvicer = "供应商编号:" + fruitProvider.id()
+ " 供应商名称:" + fruitProvider.name()
+ " 供应商地址:" + fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
==测试类==
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
运行结果
水果名称:Apple
水果颜色:RED
供应商编号:9527 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
到此,我们注解就讲完了,下面附上一张图,便于我们整理知识点和巩固知识点。
[图片上传失败...(image-e731e1-1540044443552)]
二、反射
反射是Java动态性之一。
何为动态性,即动态性语言:
程序在运行时可以改变其结构,新的函数可以引进,已有的函数可以被删除等结构上的变化。(如JavaScript、Ruby、Python等属于动态语言)
本质上Java和C/C++一样不属于动态语言的,但是因为反射机制,可以在程序运行期间加载、探知和使用编译期间完全未知的类,并可以生成相关类对象的实例,从而调用某个方法和改变某个属性值。因此,Java属于半个动态性语言。
1.反射机制
概念
Java中的反射机制是指在运行状态中,对于任何一个类都能知道这个类的所有属性和方法;并且对于任意一个对象,都能调用它的方法;这种动态获取信息和调用对象方法的功能即为Java语言的反射机制。
使用场合(为什么要使用反射)
我们学习过多态,知道编译类型和运行类型不一致的时候,当我们想调用运行类型特中的特有方法的时候需要强制转换,因为我们可能知道这个对象属于那些类。
但是当我们在实际开发过程中,可能会有某个外部对象传入过来,我们需要使用这个对象独有的方法,那在编译的过程中而我们根本无法知道这个对象属于哪些类,只有运行的时候我们才能知道传入的对象和类的信息,那么这个时候就要用到反射了!
2.反射使用
①反射API
- Class类(java.lang.Class):反射的核心类,可以获取类的属性、方法等信息。
- Field类(java.lang.reflec):表示类中的成员变量,可以获取和设置类中的属性值。
- Method类(Java.lang.reflec):表示类的方法,可以获取方法中的信息或执行方法。
- Constructor类(Java.lang.reflec):表示类的构造方法。
②获取方法和属性等信息
大致步骤:获取想要的类的Class对象—>调用Class类中的方法—>
使用反射API来操作这些信息。
获取Class对象的几个方法
- Class clazz = 对象名(想要的类的对象).getClass();
- Class clazz = 类名.getClass();
- 使用Class类中的forName()静态方法(最安全、性能最好)
==Class clazz = Class.forName("类的安全路径");==(通常使用这个)
简单案例:
package com.xsl.reflectTest;
/**
* 测试类Person,通过反射获取这个类的方法和属性
*/
public class Person {
private String name;
private String gender;
private int age;
public Person() {}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "姓名:"+name+" 性别:"+gender+" 年龄:"+age;
}
}
接下来我们通过反射来获取Person类中的方法、属性等信息
package com.xsl.reflectTest;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) {
try {
//获取Person类的Class对象
Class clazz = Class.forName("com.xsl.reflectTest.Person");
//获取Person类的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);
}
//获取Person类的所有属性
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
//获取Person类所有的构造器
Constructor[] constructors = clazz.getDeclaredConstructors();
for(Constructor cons : constructors){
System.out.println(cons);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
输出结果:
所有方法
public java.lang.String com.xsl.reflectTest.Person.getGender()
public void com.xsl.reflectTest.Person.setGender(java.lang.String)
public int com.xsl.reflectTest.Person.getAge()
public void com.xsl.reflectTest.Person.setAge(int)
public java.lang.String com.xsl.reflectTest.Person.toString()
public java.lang.String com.xsl.reflectTest.Person.getName()
public void com.xsl.reflectTest.Person.setName(java.lang.String)
所有属性
private java.lang.String com.xsl.reflectTest.Person.name
private java.lang.String com.xsl.reflectTest.Person.gender
private int com.xsl.reflectTest.Person.age
所有构造方法
public com.xsl.reflectTest.Person()
public com.xsl.reflectTest.Person(java.lang.String,java.lang.String,int)
③来创建对象为我们使用
创建对象的两种方法:
- 使用Class对象的==newInstance()==方法创建该Class对象对应类的实例。(要求对应的类要有空参构造器)目前提示不推荐使用,即方法过时。【2018年】
- 使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对应类的实例。(这个方法可以指定构造方法创建实例)
简单例子(延续上面的例子):
public class demo01 {
public static void main(String[] args) throws Exception{
//首先获取Class对象
Class clazz = Class.forName("com.xsl.reflectTest.Person");
//采用第一种方法创建对象
Person person = (Person) clazz.newInstance();//提示了不推荐使用,我们知道就好
//设置属性
person.setName("刘备");
person.setAge(20);
person.setGender("男");
System.out.println(person);//会默认调用toString()方法,我们重写了
System.out.println("====================");
Constructor constructor = clazz.getDeclaredConstructor(
String.class,String.class,int.class);
Person person1 = (Person) constructor.newInstance("孙尚香","女",16);
System.out.println(person1);
}
}
运行结果:
姓名:刘备 性别:男 年龄:20
姓名:孙尚香 性别:女 年龄:16
3.通过反射操作泛型、注解
①反射操作泛型
Java采用泛型擦除机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,为确保数据的安全性和免去强制类型转换的麻烦。
为了通过反射操作这些类型以迎合开发的需要,Java新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType这几个类型代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化的类型,比如Collection< String >
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型。
- TypeVariable:是各种类型变量的公共父接口。
- WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integet。
简单实例:
/*
通过反射获取泛型信息
*/
public class Demo {
/*
定义两个带泛型的方法
*/
public static void test01(Map map, List list){
System.out.println("Demo01.test01()");
}
public Map test02(){
System.out.println("Demo01.test02()");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
//获取指定方法参数泛型信息
Method m = Demo.class.getMethod("test01", Map.class, List.class);
Type[] t = m.getGenericParameterTypes();
for(Type paramType : t){
System.out.println("#"+paramType);
if(paramType instanceof ParameterizedType){
//获取泛型中的具体信息
Type[] genericTypes = ((ParameterizedType) paramType).
getActualTypeArguments();
for (Type genericType : genericTypes){
System.out.println("泛型信息:"+genericType);
}
}
}
System.out.println();
//获得指定方法返回值泛型信息
Method m2 = Demo.class.getMethod("test02",null);
Type returnType = m2.getGenericReturnType();
System.out.println("#"+returnType);
if(returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).
getActualTypeArguments();
for(Type genericType : genericTypes){
System.out.println("返回值,泛型类型:"+genericType);
}
}
}
}
②反射操作注解
反射操作注解,即在上面注解中的注解器便是通过反射操作的!
4.反射性能
Method/Constructor/Field/Element都继承了AccessibleObject,其中有一个方法setAccessible(boolean flag)方法,这个方法有两个作用:
- 启动/禁止访问安全检查开关:参数flag为true时,反射的对象在使用时,取消Java语言访问检查。默认有false,实施Java语言访问检查。
- 禁止安全检查,提高反射效率。
简单实例:
public class TestReflect {
public static void testNoReflect(){
Person person = new Person();
long startTimes = System.currentTimeMillis();
for(int i = 0;i < Integer.MAX_VALUE; i++){
person.getName();
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("没有通过反射,调用方法消耗时间:"+times+"毫秒");
}
public static void testNoAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Method method = Class.forName("com.xsl.reflectTest.Person").
getMethod("getName");
long startTimes = System.currentTimeMillis();
for(int i =0;i < Integer.MAX_VALUE;i ++){
method.invoke(person,null);
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("通过反射,没有访问权限,调用方法共消耗时间:"+
times+"毫秒");
}
public static void testUserAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person person = new Person();
Method method = Class.forName("com.xsl.reflectTest.Person").
getMethod("getName");
method.setAccessible(true);
long startTimes = System.currentTimeMillis();
for(int i =0;i < Integer.MAX_VALUE;i ++){
method.invoke(person,null);
}
long times = System.currentTimeMillis() - startTimes;
System.out.println("通过反射,有访问权限,调用方法共消耗时间:"+
times+"毫秒");
}
public static void main(String[] args) throws Exception {
testNoReflect();
testNoAccess();
testUserAccess();
}
}
运行结果:
没有通过反射,调用方法消耗时间:4毫秒
通过反射,没有访问权限,调用方法共消耗时间:5077毫秒
通过反射,有访问权限,调用方法共消耗时间:2611毫秒
虽然直接调用和反射调用消耗时间相差很大,但是这是我们将调用次数放大到了Integer.MAX_VALUE的结果,问题不大。但是禁止访问检查后,反射的效率确实提升了不少。
三、动态代理
在了解动态代理之前,我们先了解一下一种设计模式--代理模式--。
同时对于代理,根据创建代理的时间点,分为静态代理和动态代理。
1.代理模式
代理模式是Java常用的设计模式,特征是代理类和委托类有==同样的接口==,代理类的工作主要是负责为委托类预处理消息、过滤消息、把消息转发给委托类以及事后处理消息等。
代理类和委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定服务。即我们访问实际对象时,是通过代理对象来访问的。代理模式就是在访问实际对象时引入一定程度的间接性,通过这种间接性,可以附加多种用途。
2.静态代理
何为静态代理?
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在运行之前代理类的.class文件就已经生成。
静态代理的简单实现:
首先我们定义好==公共接口==:
/*
学生(接口)-->就是 被代理类(学生) 和代理类(班长)的公共接口
*/
public interface Person {
//教班费的行为
void giveMoney();
}
定义好==被代理类==:
/*
Student类实现Person接口,并实现交班费的功能
*/
public class Student implements Person{
private String name;
public Student(String name){
this.name = name;
}
@Override
public void giveMoney() {
System.out.println(name+"交了班费100块");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后定义==代理类==:
/*
学生代理类,也实现了Person接口,保存一个学生实体,如此就可以代理学生产生行为
*/
public class StudentProxy implements Person{
//被代理的学生
Student stu;
public StudentProxy(Student stu){
//只代理学生
if(stu.getClass() == Student.class){
this.stu = stu;
}
}
//代理交班费,调用被代理学生上交班费行为
@Override
public void giveMoney() {
stu.giveMoney();
}
}
最后我们来测试一下:
public class Test {
public static void main(String[] args) {
//被代理的学生曹操,他的班费上交由代理对象monitor班长完成
Student caocao = new Student("曹操");
//生成代理对象,并将曹操传给代理对象
Person monitor = new StudentProxy(caocao);
//班长代理上交班费
monitor.giveMoney();
}
}
运行结果:
曹操交了班费100块
从测试及结果上看,我们并没有直接通过Student类对象caocao来执行交班费的行为,而是通过StudentProxy类对象monitor来代理执行的,这种模式就是代理模式。
代理模式:公共接口(Person),被代理类(Student),代理类(StudentProxy),而且代理类要有被代理类的具体实例,才能通过这个间接的调用方法
那么就有疑问了?这样做有什么意义呢?
这个意义就在于代理模式的间接性,通过间接性,我们可以在代理过程中加入其它的用途或者是处理。
简单的说,在交班费之前,先来评价一下Student对象caocao,修改一下代码:
/*
学生代理类,也实现了Person接口,保存一个学生实体,如此就可以代理学生产生行为
*/
public class StudentProxy implements Person{
//被代理的学生
Student stu;
public StudentProxy(Student stu){
//只代理学生
if(stu.getClass() == Student.class){
this.stu = stu;
}
}
//代理交班费,调用被代理学生上交班费行为
@Override
public void giveMoney() {
//在这里添加就可以了
System.out.println(stu.getName()+"学习很好,就是疑心太大,老说有人偷他作业");
stu.giveMoney();
}
}
重新执行:
曹操学习很好,就是疑心太大,老说有人偷他作业
曹操交了班费100块
我们可以代理过程中切入一些其他的操作。
这个可以看Spring的面向切面编程(AOP),在一个切点前执行操作,在一个切点后执行操作。
3.动态代理
动态代理就是代理类在程序运行时创建的方式。
对于静态代理,代理类是我们已经定义好的,在程序运行之前就编译完成的。而动态代理中,代理类不是在Java代码中定义的,而是在运行期间根据我们在Java代码中的“提示”动态生成的。
那么相比于静态代理,动态代理有什么好处呢?
动态代理的优势可以很方便的对代理类的函数进行统一处理,而不用修改每个代理类中的方法。
用上面静态代理中代理类StudentProxy中的giveMoney()方法举例子:
@Override
public void giveMoney() {
//调用被代理方法前我们加入个处理方法
beforeMethod();
stu.giveMoney();//被代理方法
}
那好,这里我们只有一个giveMoney()方法,我们也只需要写一次beforeMethod()处理方法,但是如果多了很多其他方法呢,我们是不是也要在每个方法中,都写一次处理方法呢?
所以我们通过动态代理可以解决这个问题:
动态代理的简单实现:
首先,我们需要java.lang.reflect包下的==Proxy类==和一个==InvocationHandler==接口,通过这2个工具我们可以生成JDK动态代理类和动态代理对象。
基本步骤
- 创建一个==InvocationHandler对象==
//MyInvocationHandler(stu)这个是我们实现
//InvocationHandler接口的类
InvocationHandler stuHandler = new MyInvocationHandler(stu);
- 使用Proxy类的==getProxyClass静态方法==生成一个动态代理类对象StuProxyClass
Class> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(),new Class>[]{Person.class});
- 获得stuProxyClass中一个带InvocationHandler参数的constructor
Constructor> constructor = PersonProxy.getConstructor(InvocationHandler.calss);
- 通过构造器constructor来创建动态实例stuProxy
Person stuProxy = (Person)constructor.newInstance(stuHandler);
OK,动态代理对象创建完毕。
上面4个步骤也可以分成2个步骤完成:
//创建一个与动态代理对象相关联的InvocationHandler
InvocationHandle stuHandler = new MyInvocationHandler(stu);
//创建一个代理对象stuProxy,代理对象的每个方法执行
//都会替换执行Invocation中的invoke方法
Person stuProxy = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class>[]{Person.class},stuHandler);
我们基本上以上面的例子来展示:
首先我们创建一个==接口==:
/*
创建接口
*/
public interface Person {
//交班费
void giveMoney();
//学习
void study();
}
然后是需要==被代理的类==:
/*
创建需要被代理的类
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假设数钱用时1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"交了班费50元");
}
@Override
public void study() {
System.out.println(name+"正在学习");
}
}
然后我们创建一个工具类,用来打印方法执行的时间:
/**
* 检测方法执行时间的工具类:
* 即在方法执行前调用start方法,方法结束后调用finsh方法,即可
* 计算出方法执行的时间
*/
public class MonitorUti {
private static ThreadLocal t = new ThreadLocal<>();
public static void start(){
t.set(System.currentTimeMillis());
}
public static void finsh(String methodName){
long endTimes = System.currentTimeMillis();
System.out.println(methodName+"方法一共耗时"+(endTimes - t.get())+"毫秒");
}
}
再实现InvocationHandler接口,并持有代理对象实例
public class stuInvocationHandler implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public stuInvocationHandler(T 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("代理执行"+method.getName()+"方法");
//代理过程中插入检测方法,计算方法耗时
MonitorUti.start();
Object result = method.invoke(target,args);
MonitorUti.finsh(method.getName());
return result;
}
}
最后我们直接测试:
public class ProxyTest {
public static void main(String[] args) throws InterruptedException {
//创建一个实例对象,这个对象是被代理的对象
Person caocao = new Student("曹操");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new stuInvocationHandler(caocao);
/*
创建一个代理对象来代理caocao,
代理对象的每个执行方法都会替换执行Invocation中的invoke方法
*/
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
new Class[]{Person.class},stuHandler);
//执行交班费的方法
stuProxy.giveMoney();
stuProxy.study();
}
}
运行结果:
代理执行giveMoney方法
曹操交了班费50元
giveMoney方法一共耗时1012毫秒
代理执行study方法
曹操正在学习
study方法一共耗时0毫秒
结论:
我们发现,我们调用被代理类方法的时候,我们并没有修改其任何代码,但是执行结果却改变了了,我们只是在StuInvocationHadnler中修改了几行代码而已,我们调用被代理类的任何方法,运行时都被添加了我们自定义的工具类的方法。
这就是动态代理带来的改变,但是具体是为什么了?
4.动态代理原理
我们先来慢慢理清楚:
- 我们有==被代理类==
- 我们有==公共接口(所有的抽象方法被代理类都要实现的)==
- 我们有实现了InvocationHandler接口的类,我们叫它==调用处理器==(这里构造参数T,表示泛型,这样无论被代理类是什么类型,都能传入了)
- 最后我们通过Proxy类的静态方法创建了代理类对象
大致图我们看下:
[图片上传失败...(image-fb521e-1540044443552)]
所以,终归那么多,最主要的核心还是
Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class},stuHandler);
这段代码,这段代码究竟做了什么呢?
经过我们分析源码,一层一层解刨,在Java虚拟机缓存中,找到了生成的代理类:
public final class $Proxy0 extends Proxy implements Person
这个代理类的构造器:
public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler);
}
这个父类构造器有一个参数InvocationHandler paramInvocationHandler
这不就是我们传入的==调用处理器对象==吗?
再来看父类Proxy源码:
//这里正好有个this.h就是InvocationHandler类型!!!
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
因此,子类$Proxy0中不正好也有了这个对象吗?
继续分析代理类源码:
//在静态代码块中有这样的一行代码
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
这不正是我们代理类的吗,通过反射获取方法,然后继续找:
//这里重写了接口Person的方法
public final void giveMoney()
throws
{
try
{
//上面我们知道h是我们传入的参数“调用处理器”对象
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
终于拨开云雾了:
this.h.invoke(this, m3, null);
这行代码不就是直接调用了我们“调用处理器”中实现的invoke方法吗?
到这,终于知道动态反射的一系列过程了!!!!
5.总结
从源码可以看出,生成的代理类是继承了Proxy类,所以,动态代理只能对接口来代理。