您的位置:首页 > 编程语言 > Java开发

Java反射机制, 动态代理, AOP

2020-03-20 12:14 711 查看

Java反射机制, 动态代理, AOP

Java反射机制

普通业务代码:

  1. 需要调用一个类的方法时, 使用Person p = new Person(); 直接new一个被调用类的对象, 在编译时就会检测对象是否存在或者可以使用(编译检测,加载)
  2. 如果Person类的名字改变了或者不可用了,或者不需要调用Person类而是调用Man类, 需要改动自己的代码
  3. 事先需要知道Person类中的属性和方法, 然后写调用代码,如果Person类中的属性或者方法改动了, 需要相对应改动自己的代码,重新编译

使用反射机制的代码:

  1. 不需要事先在代码中写new, 用反射机制代码代替(3种方式), 将需要实例化的对象类名以参数的方式给到(运行时加载对象类,如果不存在就抛异常)
  2. 可以在运行时访问 Java 对象的属性,方法,构造方法等
  3. 可通过反射机制的API实时获取最新的属性和方法, Person类中的属性或方法变动后, 代码不需要变动

反射的主要应用场景有:

(普通业务逻辑代码中不常用)

  1. 开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
  2. 动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。
  3. 注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。
  4. 可扩展性功能 - 应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类。

反射的缺点

  1. 性能开销 - 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
  2. 破坏封装性 - 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
  3. 内部曝光 - 由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

java.lang.reflect 包的核心接口和类如下:

Member 接口 - 反映关于单个成员(字段或方法)或构造函数的标识信息。
Field 类 - 提供一个类的属性的信息以及访问类的属性的接口。
Method 类 - 提供一个类的方法的信息以及访问类的方法的接口。
Constructor 类 - 提供一个类的构造函数的信息以及访问类的构造函数的接口。
Array 类 - 该类提供动态地生成和访问 JAVA 数组的方法。
Modifier 类 - 提供了 static 方法和常量,对类和成员访问修饰符进行解码。
Proxy 类 - 提供动态地生成代理类和类实例的静态方法。

使用反射的4种方法:

推荐第三种使用Class类的forName静态方法

package javatest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Fanshe {

public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
// TODO Auto-generated method stub

//1.调用运行时类本身的.class属性
Class clz1 = Person.class;
System.out.println(clz1.getName());
System.out.println();

//2.通过运行时类的对象获取
Person p = new Person();
Class clz2 = p.getClass();
System.out.println(clz2.getName());
System.out.println();

//3.通过class的静态方法获取
String className = "javatest.Person";     //需要运行时加载的类
Class clz3 = Class.forName(className);	  //使用forName静态方法加载类
Object obj = clz3.newInstance();		  //实例化类,建立对象

//使用Person后产生硬编码
Person p1 =(Person)obj;					  //将加载类对象转化为这个类类型的对象
p1.setName("kevin zhu");				  //由于p1已经自Person的对象,可以直接调用Person的方法
System.out.println(p1.getName());

//使用反射机制的API,获得Person中的指定方法,不使用Person
Method nameMethod1 = clz3.getMethod("setName",String.class);  //第二个值是该方法型参的类型,有多个型参用逗号隔开;方法没有型参,忽略第二个参数
//调用此方法
nameMethod1.invoke(obj, "shelley yang");
//使用反射机制的API,获得Person中所有的方法,通过循环找到需要调用的方法,不适用Person(属性同理)
Method[] nameMethod2 = clz3.getMethods();
for(int i = 0; i<nameMethod2.length;i++){
if (nameMethod2[i].getName() == "getName") {
System.out.println(nameMethod2[i].invoke(obj));
}
}

//调用运行时类的构造器
Constructor con = clz3.getConstructor(String.class,int.class);
Person p2 = (Person)con.newInstance("shelley",100);     //用构造器建立对象,不用clz3,用con
Method nameMe3 = clz3.getMethod("getName");
System.out.println("*** " + nameMe3.invoke(p2));
System.out.println("*** " + p2.getAge());

//对private的属性和方法进行调用时,需要用Declared API获取,并且设定Accessible为true
Field age = clz3.getDeclaredField("age");
age.setAccessible(true);
age.set(obj, 20);

System.out.println(clz3.getName());

//4.通过类的加载器(了解)
//ClassLoader classLoader = this.getClass().getClassLoader();
//Class clz4 = classLoader.loadClass(className);
//System.out.println(clz4.getName());

}

}

class Person {
public String name;
private int age;
public Person(String name,int age){
super();
this.name = name;
this.age = age;
System.out.println("with param");
}
public Person(){
super();
System.out.println("without param");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
private void setAge(int age) {
this.age = age;
}

}

代理

为什么要用到代理:(代理是一种设计模式, AOP就是基于代理的设计模式)
在平时我们写java代码的时候,一个类都是只处理自己相关的业务,不会去处理多余的代码。每个类都尽量做到独立,这样才能做到高内聚低耦合,为了以后的代码维护和扩展。
那么问题来了,如果哪天我们需要在类里加上权限,日志等功能时,我们是不是需要修改每个类。这样不仅工作量巨大,严重影响代码质量。这时我们需要一个代理,让我们可以在不改动原有代码的前提下,实现一些其他功能,即增强。我们在进入目标类之前,先进入代理类,在代理类中写我们需要的额外功能,这样原有类不动,不影响原有功能。
例如:
代理类for IT工程师
1.校验权限
2.打印日志
3.核心业务类: IT工程师写代码
4.打印日志
5.代码结果发送到消息队列

这5步可以封装成一个代理类. 如果需要对1,2,4,5步进行改动时,可以不变动第3步核心业务类的代码.

静态代理:
静态代理其实就是指设计模式中的代理模式。
代理模式为其他对象提供一种代理以控制对这个对象的访问。
没有用到反射机制

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

例如:
如果核心类是: 医生医治病人
那就需要写一个新的代理类:
代理类for 医生
1.校验权限
2.打印日志
3.核心业务类: 医生医治病人
4.打印日志
5.代码结果发送到消息队列

每个核心业务类都有自己的代理类,哪怕其他额外步骤一样, 代码冗余

package javatest;
//静态代理模式
//接口
interface ClothFactory {
void produceCloth();
}

//被代理类
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth(){
System.out.println("Nike工厂生产一批衣服");
}

}

//代理类
class ProxyFactory implements ClothFactory{
NikeClothFactory cf;

//创建代理类的对象时,实际传入一个被代理类的对象
public ProxyFactory(NikeClothFactory cf){
this.cf = cf;
}

@Override
public void produceCloth(){
System.out.println("代理类开始执行,收代理费$1000元");
cf.produceCloth();
System.out.println("代理类结束执行,衣服发送快递");
}

}

public class TestClothProduct {

public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ProxyFactory proxy = new ProxyFactory(nike);
proxy.produceCloth();
}

}

动态代理: (反射机制最常用到的场景)
动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基于 Java 动态代理实现的。

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

JDK动态代理-> 只能代理实现了接口的类
CGLIB动态代理 -> 可以代理继承了类的类

例如:
核心类是一个可以输入的参数
动态代理类for XXX
1.校验权限
2.打印日志
3.核心业务类: class(XXX)
4.打印日志
5.代码结果发送到消息队列

package javatest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//接口
interface CarFactory {
void produceCar();
}

//被代理类-1
class BenzCarFactory implements CarFactory{
@Override
public void produceCar(){
System.out.println("benz工厂生产一辆车");
}

}

//被代理类-2(cloth类)

//动态代理类
class DynamicInvocationHandler implements InvocationHandler{
private Object obj;

public Object realize(Object obj){
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.print("代理类开始执行,收代理费$1000元\n");
Object returnVal = method.invoke(obj,args);
System.out.print("代理类结束执行,物品发送快递\n");
return returnVal;
}

}

public class TestCarProduct {

public static void main(String[] args) {

//动态代理-car
//1.被代理类的对象
BenzCarFactory bcar = new BenzCarFactory();

//2.创建一个实现了InvocationHandler接口的类的对象
DynamicInvocationHandler handler = new DynamicInvocationHandler();

//3.调用realize()方法,动态的返回了一个同样实现了BenzCarFactory所在类实现的接口CarFactory的被代理类的对象
Object obj = handler.realize(bcar);
CarFactory carf = (CarFactory)obj;//此时carf就是被代理类的对象

carf.produceCar();

//动态代理-cloth
NikeClothFactory ncloth = new NikeClothFactory();
ClothFactory cfact = (ClothFactory)handler.realize(ncloth);
cfact.produceCloth();

}

}

AOP(Aspect Orient Program, 面向切面编程)

AOP(Aspect Oriented Programing),即面向切面编程,它主要用于日志记录、性能统计、安全控制、事务处理、异常处理等方面。它的主要意图就要将日志记录,性能统计,安全控制、事务处理、异常处理等等代码从业务逻辑代码中清楚地划分出来。通过对这些行为的分离,我们希望可以将它们独立地配置到业务逻辑方法中,而要改变这些行为的时候也不需要影响到业务逻辑方法代码。

非面向切片变成会导致的问题:
代码混乱: 每个方法在处理核心逻辑时还必须兼顾其他多个关注点.
代码分散: 以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块里多次重复相同的日志代码. 如果日志需求发生变化, 必须修改所有模块

面向切面的简图

  • 点赞
  • 收藏
  • 分享
  • 文章举报
whatkevin1984 发布了17 篇原创文章 · 获赞 0 · 访问量 957 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: