绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
JAVA代理那些事儿
2019-12-09 19:32:40

JAVA代理那些事儿

1.先看一个房屋租赁例子

问题:此时若有人来整房东,派很多人来找房东假租房,这会导致房东一天到晚都忙且没收获。带来这个问题就是:重复,且责任不分离,其实房东最关系的就是签合同和收房租。

静态代理

1.代理模式

客户端直接使用的都是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。

1.1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;

1.2、代理模式的职责:把不是真实对象该做的事情从真实对象上撇开——职责分离。

2.定义流程

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

3.代码实现

代理接口

package cn.dale.spring.static_proxy.service;

public interface IEmployeeService {

void save(String name);

void update(String name);

}

1

2

3

4

5

6

真实类或委托类

package cn.dale.spring.static_proxy.service.impl;

import cn.dale.spring.static_proxy.service.IEmployeeService;

public class EmployeeServiceImpl implements IEmployeeService {

public void save(String name) {

System.out.println("保存" + name);

}

public void update(String name) {

System.out.println("更新" + name);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

代理类

package cn.dale.spring.static_proxy.service.impl;

import org.springframework.beans.factory.annotation.Autowired;

import cn.dale.spring.static_proxy.service.IEmployeeService;

import cn.dale.spring.static_proxy.tx.MyTx;

public class EmployeeServiceProxy implements IEmployeeService {

@Autowired

private IEmployeeService service;//观察能否调用到另一个实现类EmployeeServiceImpl的save()方法

@Autowired

private MyTx tx;

public void setService(IEmployeeService service) {

this.service = service;

}

// public void setTx(MyTx tx) {

// this.tx = tx;

// }

public void save(String name) {

tx.begin();

try {

service.save(name);

tx.commit();

} catch (Exception e) {

tx.rollback();

} finally {

System.out.println("释放资源");

}

}

public void update(String name) {

tx.begin();

try {

service.update(name);

tx.commit();

} catch (Exception e) {

tx.rollback();

} finally {

System.out.println("释放资源");

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

事务处理类

package cn.dale.spring.static_proxy.tx;

import org.springframework.stereotype.Component;

@Component(value="tx")

public class MyTx {

public void begin(){

System.out.println("开启事务");

}

public void commit(){

System.out.println("提交事务");

}

public void rollback(){

System.out.println("回滚事务");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

配置文件

测试用例:

package cn.dale.spring.static_proxy;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.dale.spring.static_proxy.service.IEmployeeService;

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class static_proxyTest {

@Autowired

private IEmployeeService proxy;

@Test

public void testStatic_Proxy() {

proxy.save("HelloWorld!");

proxy.update("你好世界!");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

控制台输出结果:

4.静态代理优缺点

优点:

业务类只需要关注业务逻辑本身,保证了业务类的重用性。

把真实对象隐藏起来了,保护真实对象。

缺点:

代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他 service 呢。

若需要代理的方法很多,则要为每一种方法都进行代理处理。

若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。

动态代理

1.问题

就是静态代理的缺点:需要为每个真实类创建一个代理类,随着程序规模变大导致代理类急剧膨胀。可以通过动态代理解决。

2.字节码加载

如何动态的创建一份字节码?

由于 JVM 通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循 Java 编译系统组织 .class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此,就完成了在代码中动态创建一个类的能力了。

3.动态代理动态生成字节码

动态代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。

4.实现动态代理方式

4.1、针对有接口:使用 JDK 动态代理;

4.2、针对无接口:使用 CGLIB 或 Javassist 组件。

接下来详细讲解JDK动态代理

1、前提

委托类(真实类),必须实现接口。

2、JDK 动态代理 API

2.1、java.lang.reflect.Proxy 类

Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

主要方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hanlder)

方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例

参数:

loader :类加载器,一般传递真实对象的类加载器

interfaces:代理类需要实现的接口

handler:代理执行处理器,说人话就是生成代理对象要帮你做什么

返回:创建的代理对象

1

2

3

4

5

6

7

2.2、java.lang.reflect.InvocationHandler 接口

主要方法:public Object invoke(Object proxy, Method method, Object[] args)

方法职责:负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法增强。

参数:

proxy :生成的代理对象

method:当前调用的真实方法对象

args :当前调用方法的实参

返回:真实方法的返回结果

1

2

3

4

5

6

7

3、操作步骤

3.1、定义封装事务操作的一个模拟类。

3.2、实现 InvocationHandler 接口,实现 invoke 方法,实现增强操作。

3.3、在 Spring 配置文件中配置 InvocationHandler 实现类、事务操作模拟类、真实对象,让其帮我们创建对象组装依赖。

3.4、在单元测试类中注入 InvocationHandler 的 bean,在测试方法中手动使用 Proxy 创建代理对象,调用代理对象的方法

4、代码实现

事务操作类

package cn.dale.spring.jdk_proxy.tx;

import org.springframework.stereotype.Component;

@Component

public class MyTx {

public void begin(){

System.out.println("开启事务");

}

public void commit(){

System.out.println("提交事务");

}

public void rollback(){

System.out.println("回滚事务");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

真实类或委托类,就是房东

package cn.dale.spring.jdk_proxy.service.impl;

import org.springframework.stereotype.Service;

import cn.dale.spring.jdk_proxy.service.IEmployeeService;

@Service(value="service")

public class EmployeeServiceImpl implements IEmployeeService {

public void save(String name) {

System.out.println(1/0);

System.out.println("保存" + name);

}

public void update(String name) {

System.out.println("更新" + name);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

代理执行处理器类

package cn.dale.spring.jdk_proxy.handler;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import cn.dale.spring.jdk_proxy.tx.MyTx;

@Component

public class TxInvocationHandler implements InvocationHandler{

@Autowired

private Object service;//真实对象的引用,类型是Object

public Object getService() {

return service;

}

@Autowired

private MyTx tx;//事务处理对象的引用

//负责集中处理动态代理类上的所有方法调用,让使用者自定义做什么事情,对原来方法做增强

//proxy代理对象,method调用的方法,args方法调用参数

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object ret = null;

tx.begin();

try {

//调用真实对象的方法

ret = method.invoke(service, args);

tx.commit();

} catch (Throwable e) {

tx.rollback();

}

return ret;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

配置文件,由于事务处理器对象和事务处理器代理类都用注解配置了,故配置文件的相关bean给注释了.

测试用例:

package cn.dale.spring.jdk_proxy;

import java.lang.reflect.Proxy;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.dale.spring.jdk_proxy.handler.TxInvocationHandler;

import cn.dale.spring.jdk_proxy.service.IEmployeeService;

/**

* @author dale

*/

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext-jdk_proxy.xml")

public class jdk_proxyTest {

//注入代理执行处理器对象

@Autowired

private TxInvocationHandler handler;

@Test

public void testSave() {

//根据提供的条件动态生成代理类及创建其对象

IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance

(handler.getService().getClass().getClassLoader(),

handler.getService().getClass().getInterfaces(),

handler);

//调用代理类的方法

service.save("HelloWorld!");

}

@Test

public void testUpdate() {

IEmployeeService service = (IEmployeeService)Proxy.newProxyInstance

(handler.getService().getClass().getClassLoader(),

handler.getService().getClass().getInterfaces(),

handler);

service.update("HelloWorld!");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

控制台输出结果:

5、JDK动态代理原理

1、生成动态代理的字节码

执行main方法生成字节码

import java.io.FileOutputStream;

import sun.misc.ProxyGenerator;

@SuppressWarnings("restriction")

public class DynamicProxyClassGenerator {

public static void main(String[] args) throws Exception {

generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy2");

}

public static void generateClassFile(Class<?> targetClass, String proxyName) throws Exception {

// 根据类信息和提供的代理类名称,生成字节码

byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());

String path = targetClass.getResource(".").getPath();

System.out.println(path);

FileOutputStream out = null;

// 保留到硬盘中

out = new FileOutputStream(path + proxyName + ".class");

out.write(classFile);

out.close();

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

2、 通过反编译工具查看字节码文件

观察:save 方法,发现底层其实依然在执行 InvocationHandler 中的 invoke 方法。

public final void save(String paramString)

throws

{

try

{

//h是增强处理器

this.h.invoke(this, m4, new Object[] { paramString });

return;

}

catch (RuntimeException localRuntimeException)

{

throw localRuntimeException;

}

catch (Throwable localThrowable)

{

throw new UndeclaredThrowableException(localThrowable);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

3、调用流程

4、优缺点

优点:对比静态代理,发现不需手动地提供那么多代理类。

缺点:

1. 真实对象必需实现接口(JDK 动态代理特有)。

2. 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判 断。

3. 对多个真实对象进行代理的话,若使用 Spring 的话配置太多了。

4. 要手动创建代理对象,用起来麻烦。

接下来详细讲解CGLIB动态代理和原理

1、JDK 动态代理的问题

JDK 动态代理要求真实类必须实现接口。而 CGLIB 与 JDK 动态代理不同是,真实类不用实现接口,生成代理类的代码不一样且代理类会继承真实类。

2、CGLIB 动态代理 API

org.springframework.cglib.proxy.Enhancer,类似 JDK 中 Proxy,用来生成代理类创建代理对象的。

org.springframework.cglib.proxy.InvocationHandler,类似 JDK 中 InvocationHandler,让使用者自定义做什么事情,对原来方法增强。

3、操作步骤

3.1、修改 TransactionHandler 实现 org.springframework.cglib.proxy.InvocationHandler 接口,其他不变。

3.2、修改单元测试类中的测试方法,改用 Enhancer 来生成代理类创建代理对象的。

4、代码实现

代理执行处理器类

package cn.dale.spring.cglib_proxy.handler;

import java.lang.reflect.Method;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import cn.dale.spring.cglib_proxy.tx.MyTx;

@Component(value="txx")

public class TxInvocationHandler implements org.springframework.cglib.proxy.InvocationHandler{

@Autowired

private Object service;

public Object getService() {

return service;

}

@Autowired

private MyTx tx;

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object ret = null;

tx.begin();

try {

ret = method.invoke(service, args);

tx.commit();

} catch (Exception e) {

tx.rollback();

}

return ret;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

其他的组件和JDK动态代理一样!

测试用例:

package cn.dale.spring.cglib_proxy;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cglib.proxy.Enhancer;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.dale.spring.cglib_proxy.handler.TxInvocationHandler;

import cn.dale.spring.cglib_proxy.service.impl.EmployeeServiceImpl;

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext-cglib_proxy.xml")

public class Cglib_ProxyTest {

//注入代理执行处理器对象

@Autowired

private TxInvocationHandler handler;

@Test

public void testCglib_Proxy() {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(handler.getService().getClass().getClass());//设置代理类的父类对象为真实对象

enhancer.setCallback(handler);//设置真实对象方法增强

EmployeeServiceImpl proxy = (EmployeeServiceImpl)enhancer.create();//根据提供的条件生成动态代理类及其对象

proxy.save("HelloWorld!");//调用代理对象方法

proxy.update("HelloWorld!");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

5、调用流程

动态代理总结

1、动态代理图示

2、JDK动态代理总结

2.1、Java 动态代理是使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口这两个来完成的。

2.2、要使用 JDK 动态代理,真实类必须实现接口。**

2.3、JDK 动态代理将会拦截所有 pubic 的方法(因为只能调用接口中定义的方法),这样即使在接口中增加了新的方法,不用修改代码也会被拦截。

2.4、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。

3、CGLIB 动态代理总结

3.1、CGLIB 可以生成真实类的子类,并重写父类非 final 修饰符的方法。

3.2、要求类不能是 final 的,要拦截的方法要是非 final、非 static、非 private 的。

3.3、动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断。

4、关于性能

JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承委托类的。

从性能上考虑:Javassit > CGLIB > JDK。

MyBatis 延迟加载对象,采用的是 Javassit 的方式。

对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。

若委托类实现了接口,优先选用 JDK 动态代理。

若委托类没有实现任何接口,使用 Javassit 和 CGLIB 动态代理。

动态代理问题:对多个 service 对象增强配置太多,还有要手动创建代理对象,在使用时不是面向接口,还要编写 InvocationHandler 接口的实现类。

————————————————

版权声明:本文为CSDN博主「漠狐烟」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/IDale/article/details/102869120

分享好友

分享这个小栈给你的朋友们,一起进步吧。

时悦的原创空间
创建时间:2019-08-12 11:14:46
该小栈全部为原创干货,分享数据库,Python等相关内容 另外还有运动健身和英语学习的内容哦 个人微信公众号:宅必备
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • ibsbforever
    栈主

小栈成员

查看更多
  • 栈栈
  • 杨三百
  • ?
  • else
戳我,来吐槽~