プロキシ モードを使用すると、プロキシ クラスを変更せずにプロキシ クラスを拡張できます。
たとえば、プロキシ クラスには div() メソッドがあります。これは除算を計算するだけですが、例外キャプチャの追加など、それを拡張したいと考えています。
class CalculateImp {
public void div(int x, int y) {
System.out.println("x/y="+(x/y));
}
}
静的プロキシ
通常、静的プロキシ クラスとプロキシ クラスは、統一されたビジネス インターフェイスを実装する必要があります。
public class Application {
public static void main(String[] args) throws InterruptedException {
CalculateImp calculateImp = new CalculateImp();
AgencyCalculate calculate = new AgencyCalculate(calculateImp);
calculate.div(1,0);
}
}
class CalculateImp implements Calculate{
@Override
public void div(int x, int y) {
System.out.println("x/y="+(x/y));
}
}
interface Calculate{
void div(int x,int y);
}
class AgencyCalculate implements Calculate{
private Calculate calculate;
public AgencyCalculate(Calculate calculate) {
this.calculate=calculate;
}
@Override
public void div(int x, int y) {
try{
this.before();
calculate.div(x,y);
this.after();
}
catch (Exception e){
System.out.println("出现错误");
}
}
public void before(){
System.out.println("前置增强");
}
public void after(){
System.out.println("后置增强");
}
}
効果:
この方法は、インターフェース型のオブジェクトを静的プロキシクラスに渡しますが、このインターフェースを実装したオブジェクトであればプロキシクラスのオブジェクトとして利用できるというものです。ただし、このインターフェイスを実装していないオブジェクトは、このプロキシ クラスを通じてプロキシすることはできません。このため、静的プロキシ クラスを作成する必要があります。プロキシの拡張が必要な無関係なクラスが複数ある場合はどうなるでしょうか? また、動的プロキシ クラスを使用すると、クラスごとにプロキシ クラスを自動的に生成し、直接使用するためにメモリにロードできるため、プロキシ クラスを自分で記述する必要がなく、プロキシ オブジェクトが誰であるかを知るだけで済みます。強化されたロジックで何を行うか。
JDK動的プロキシ
動的プロキシの中核は 2 つの部分です:
1. どのクラスをプロキシする必要があるか?
どのクラスを指定するには、Proxy の静的メソッドnewProxyInstance
(ClassLoader ローダー、Class<?>[] インターフェイス、InvocationHandler h) を使用します。このメソッドには 3 つのパラメータがあります。
- ClassLoader: プロキシ クラスのローダー。生成されたプロキシ クラスをメモリにロードできます。クラスローダーは、プロキシされたクラスのリフレクションを通じて取得できます。
- Class<?>[] インターフェイス: プロキシされたクラスのすべての実装インターフェイスは、生成されたプロキシ オブジェクトにもこれらのインターフェイスを実装させることができます。実装されたすべてのインターフェイスは、プロキシ クラスのリフレクションを通じて取得できます。
- InvocationHandler: これは、実装クラスを渡す必要があるインターフェイスです。invoke() メソッドはクラス内で書き直されます。これは、特定の拡張ロジックです。このインターフェースの実装は、以下で考慮する 2 番目の問題です。
2. 具体的な強化ロジックは? インターフェイスを
実装してから、invoke(Object proxy, Method method, Object[] args) メソッドをオーバーライドします。InvocationHandler
生成されたプロキシ クラス オブジェクトがメソッドを呼び出す限り、それは invoke() メソッドによってインターセプトされ、このメソッド内でロジックが強化されてから、プロキシ クラスのメソッドが呼び出されます。このメソッドは 3 つのパラメータを取ります。
- プロキシ: 自動的に生成されたプロキシ オブジェクト
- メソッド: プロキシメソッド
- args: プロキシ クラス メソッドに渡されるパラメータの配列
次に、InvocationHandler
インターフェイスを実装する生成されたオブジェクトをnewProxyInstance()
上記のメソッドの 3 番目のインスタンスに渡します。
注: JDK を通じて動的プロキシ オブジェクトを取得するこの方法では、インターフェイスを実装する必要があります。
次に、JDK 動的プロキシを使用して上記の Calculate クラスをプロキシし、同じ効果を実現します。
public class Application {
public static void main(String[] args) throws InterruptedException {
// 被代理对象
CalculateImp calculateImp = new CalculateImp();
ProxyFactory proxyFactory = new ProxyFactory();
// 通过这个类生成代理对象
proxyFactory.setObject(calculateImp);
// 返回的代理类类型不确定,但可以代理类一定也实现了Calculate接口,利用多态引用
Calculate c1 = (Calculate) proxyFactory.getProxy();
// 调用方法,这是就会被生成的代理类对象的invoke方法拦截,然后实现增强。思考:上面为什么不使用Object或其他类接收
c1.div(1,1);
}
}
class CalculateImp implements Calculate{
@Override
public void div(int x, int y) {
System.out.println("x/y="+(x/y));
}
}
// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
void div(int x,int y);
}
class ProxyFactory{
// 通过set的方式获取被代理对象 ,使用Object可以生成任意对象代理
private Object object;
public void setObject(Object object) {
this.object = object;
}
// 抽象出;两个增强的方法
private void before(){
System.out.println("前置增强");
}
private void after(){
System.out.println("后置增强");
}
Object getProxy(){
InvocationHandler invocationHandler = new InvocationHandler() {
// 使用匿名内部类的方式直接实现接口,也可以使用外部类单独实现
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
try{
// 调用被代理对象的方法 ,代理对象调用哪个方法就会被invoke拦截,然后在这里调用被代理类的该方法
method.invoke(object,args);
}
catch (Exception e){
System.out.println("出现异常");
}
after();
return null; // 若代理类不为 void ,可以在这里返回最终的结果
}
};
// 被代理类的类加载器,被代理类实现的接口,代理逻辑
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
invocationHandler);
}
}
動的プロキシ クラスを使用する利点は、特定のクラスのプロキシ クラスを個別に記述する必要がなく、動的クラスを使用して任意のクラスのプロキシを実装できることです。プロキシ ロジックが異なる場合は、コード内で ProxyFactory クラスの InvocationHandler インターフェイス オブジェクトをプロパティとして公開できます。
上記は、動的プロキシ クラスを使用すると静的プロキシ クラスと同じ効果が得られることを示しましたが、次に、動的プロキシ クラスが異なるクラスのプロキシ オブジェクトを自動的に生成できることを示します。例: Calculate クラスと同じプロキシ ロジックを持つ新しい House クラスを追加します。上記のプロキシの上に次のように追加するだけです。
効果:
JDK ダイナミック プロキシを使用すると、実行時にプロキシ クラスが自動的に生成されます。このプロキシ クラスは実際にはメモリ内に存在しますが、その名前は固定されていません。プロキシ クラスのすべてのインターフェイスが実装されます。JDK の最下位層が作成された後、クラスローダーは自動的にメモリに配置されます。使用するときはメモリから取得するだけでよく、クラス名はわかりませんが、このプロキシクラスはプロキシクラスのインターフェースをすべて実装しており、多態性参照が使用できます。
考えてみます: 動的に生成されたプロキシ クラスのクラス名は何でしょうか?
生成されたプロキシ クラス オブジェクトは、リフレクションを通じてクラスの完全なクラス名を取得します。代理类对象.getClass().getName()
、最終的な形式は包名.$Proxy序号
次のようになります。
Cglib動的プロキシ
Cglib は JDK に付属するプロキシ メソッドではないため、使用するにはまず依存関係を導入する必要があります。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
Cglib ダイナミック プロキシを使用する方法は継承を使用することであるため、プロキシ クラスはインターフェイスを実装する必要はありませんが、プロキシ クラスを に設定する必要はありませんfinal
。最後の最下層が追加されると、プロキシされたクラスを継承できなくなり、プロキシが失敗するためです。
Cblib を使用する場合、考慮すべき問題がまだ 2 つあります:
1. どのクラスをプロキシするか?オブジェクトを作成し、setSuperclass (クラス スーパークラス) メソッドを呼び出す
必要があります。Enhancer
メソッドのパラメータはプロキシ クラスです
2. 具体的な強化ロジックは? インターフェイスを実装し、4 つのパラメーターを持つインターセプト (Object o、Method メソッド、Object[] オブジェクト、MethodProxy methodProxy) メソッドを書き直す必要があります
。MethodInterceptor
- オブジェクト o: 生成されたプロキシ クラス オブジェクト。プロキシ クラスは自動的に生成され、クラス名は固定されません。
- メソッドメソッド:プロキシクラスのメソッド
- Object[]オブジェクト:メソッドのパラメータリスト
上記3つはJDK上のInvokerHander実装クラスのinvoke()メソッドの3つのパラメータに相当します - MethodProxy methodProxy: 最下層はプロキシ クラスのオブジェクトを動的に作成します
次に、Cglib ダイナミック プロキシを使用して上記の Calculate クラスをプロキシし、同じ効果を実現します。
public class Application {
public static void main(String[] args) throws InterruptedException {
//被代理类的对象
CalculateImp calculateImp = new CalculateImp();
// 生成代理类的对象
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setObject(calculateImp);
// 同样生成的类不固定名,但这个类继承了被代理类,所以利用多态即可引用即可
CalculateImp proxy = (CalculateImp) proxyFactory.getProxy();
// 代理类调用此方法,会转到enhancer指定的回调对象的invoke方法,这个方法就是写代理逻辑的方法
proxy.div(1,2);
}
}
// 注意 一定不能为final,否则生成的代理类无法继承。至于接口,与Cglib动态代理无关,这里特意去掉接口
class CalculateImp {
public void div(int x, int y) {
System.out.println("x/y="+(x/y));
}
}
// 要想通过JDK这种方式实现动态代理,就必须有接口
interface Calculate{
public void div(int x,int y);
}
class ProxyFactory implements MethodInterceptor {
private Object object;
public void setObject(Object object) {
this.object = object;
}
// 抽象出;两个增强的方法
private void before(){
System.out.println("前置增强");
}
private void after(){
System.out.println("后置增强");
}
// 具体增强的逻辑
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
this.before();
// 通过反射调用代理类的方法
method.invoke(this.object,objects);
System.out.println(methodProxy.getClass().getName());
this.after();
return null;
}
public Object getProxy(){
//Enhancer :增强者
Enhancer enhancer = new Enhancer();
// 设置要代理哪个类,因为生成的代理类是继承被代理类,所以这个接口参数名为superclass
enhancer.setSuperclass(CalculateImp.class);
//设置回调 就是实现了MethodInterceptor接口的实现类对象,由于这里将enhancer写在了这类内部,所以this就可
enhancer.setCallback(this);
//最后返回生成的代理类对象
return enhancer.create();
}
}
考えてみます: Cglib によって動的に生成されるプロキシ クラスのクラス名は何ですか?
生成されたプロキシ クラス オブジェクトは、リフレクションを通じてクラスの完全なクラス名を取得します。代理类对象.getClass().getName()
、最終的な形式は被代理类的全类名$$EnhancerByCGLIB$$编号
次のようになります。
まとめ
使用の概要
たとえば、クラス内のメソッドのプロキシを強化し、プロキシ クラス内のメソッドの実行時間を補完します。
JDKダイナミックプロキシの使い方
public class Application {
public static void main(String[] args) throws InterruptedException {
ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
业务接口 proxy = (业务接口) proxyFactory.getProxy();
proxy.业务方法;
}
}
class ProxyFactory{
private Object object;
public ProxyFactory(Object object) {
this.object = object;
}
private InvocationHandler getInvocationHandler(){
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long before = System.currentTimeMillis();
method.invoke(object,args);
long after = System.currentTimeMillis();
System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
return null;
}
};
return invocationHandler;
}
public Object getProxy() {
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
getInvocationHandler());
}
}
Cglib 動的プロキシ メソッドを使用する
public class Application {
public static void main(String[] args) throws InterruptedException {
ProxyFactory proxyFactory = new ProxyFactory(被代理对象);
被代理类 proxy = (被代理类) proxyFactory.getProxy();
proxy.业务方法;
}
}
class ProxyFactory implements MethodInterceptor {
private Object object;
public ProxyFactory(Object object) {
this.object = object;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long before = System.currentTimeMillis();
method.invoke(object,objects);
long after = System.currentTimeMillis();
System.out.println("方法执行完毕。共耗时"+(after-before)+"毫秒");
return null;
}
public Object getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Test.class);
enhancer.setCallback(this);
return enhancer.create();
}
}
Cglib ダイナミック プロキシと JDK ダイナミック プロキシの比較优点
:
- Cglib の方がパフォーマンスが高い
- Cglib はプロキシ クラスによって実装される必要がなく、コードの侵入が少なくなります。
Cglib ダイナミック プロキシと JDK ダイナミック プロキシの比較缺点
:
- Cglib ダイナミックは依存関係をインポートする必要がありますが、JDK ダイナミック プロキシはネイティブでサポートしているため、JDK バージョンのアップグレードに伴ってスムーズにアップグレードできます。また、Cglib は Jar パッケージの新しいバージョンを導入する可能性もあります。
- プロキシ クラスは、final によって変更できません。
JDK ダイナミック プロキシで使用される Java リフレクション メカニズムでは、プロキシ クラスはビジネス インターフェイスを実装する必要があります。Cglib は ASM メカニズムに基づいて実装され、プロキシ クラスは Final によって変更できません。動的プロキシを使用すると、プロキシ クラスのプロキシ クラスを自動的に生成し、プロキシ クラス オブジェクトをメモリにロードできます。