- 浏览: 118481 次
- 性别:
- 来自: 湖南
文章分类
最新评论
-
一步一个天涯:
good.
tomcat配置热启动,我试过有用的方式 -
yuechen323:
哥们!!看了你博客的大部分文章,写的都非常的通俗易懂,谢谢~ ...
我在java中碰到的设计模式 -
悲剧了:
...
jstl
15. 现在开始来讲解代理模式 ,这个比较重要,因为对后期框架的理解比较重要。比如AOP
16. 代理起到一个控制的作用,我可以控制你,不允许你访问,属于结构型模式,就像我们卖产品不是直接卖给客户,而是卖给代理商,代理商跟客户之间出现了什么问题,这不是我们也不知道,他可以控制这个客户,我们关注的是代理商,到底代理商是怎么把产品运到客户那去的,是开飞机过去的,还是开汽车过去的,我们不管,所以代理商起到隐藏了一些细节,代理就起到这个作用,可以控制客户。
17. 在程序中也可以用类似的功能来做这个代理。
18. 这个代理从分类上有一个静态的和动态的概念
19. 现在写一个UserManager接口
public interface UserManager{
public void addUser(String userId,String username);
public void modifyUser(String userId,String username);
public void findById(Stirng userId)
}
20. 现在有一个实现类
public class UserManagerImpl implements UserManager{
接下来就是实现 方法了,就在方法里面写一些打印语句
}
21. 现在写完了一接口,一实现,现在来调用就写一个客户端呗,一个Client类
public class Client{
main
UserManager userManager = new UserManager();
userManager.addUser("0001","张三");
}
运行 打印 UserManagerImpl.addUser().这就调过去了。
22. 假设现在出现了这种情况,就是每个方法 在调用 的时候要打印一下,在调用 这个业务逻辑之前要打印一下参数之类 的,调用完后,也要打印一下,假设需求方这么一改变,你现在所有的方法都要改了
,我们目前的做法是所有的方法都要加。从开闭原则上来讲是不支持的。
23. 因为需求变了你得改啊,这方法我是copy过来的,他把方法给注释了。
public void addUser(String userId, String username) {
//在调用之前打印
//System.out.println("start-->>addUser() - " + userId);
try {
System.out.println("UserManagerImpl.addUser()");
//调用 这个方法成功了,你也得给打印一下,
//System.out.println("success-->>addUser()");
}catch(Exception e) {
e.printStackTrace();
//调用 出错了你也得打印一下,
//System.out.println("error-->>addUser()");
throw new RuntimeException(e);
}
24. 你想所有 的方法你都得像上面 一样修改,像我们 现在三个方法都要加,这样波及太广了,假设现在有几百个方法呢?而且都是重复的,还是我们以前那个原则,重复的东西不要出现多次,
25. 假设你现在每个方法都给加上了这个需求,一个星期之后,说不要了,那你怎么办,难道一个一个改回来啊,你的做法还是找到所有的类一个一个给注释掉,
26. 那接下我们怎么办,我们还不想改以前的东西,我们得支持ocp原则,我们可以扩展,我们可以加一个代理类,客户端直接访问代理类。
27. 现在建 一个代理类,注意这是代理类,代理类
的一个重要的原则就是,他要跟真实的目标类,对于我们这里来说就是用户ManageImpl实现类,他的接口应该是一样的,你不应该改变接口,也就是说,你让这个代理商去卖衣服,这个代理商就是卖衣服,给你卖衣服,卖给客户,不应该卖别的东西。这就是一个接口的事,
28. 你看实现的是哪个接口啊,
public class UserManagerImplProxy implements UserManager {
这个代理类要控制原来的目标,怎么控制,要持有原来目标的引用才能控制啊,所以在这里要持有目标对象的引用,
private UserManager userManager;
不写
private UserManagerImpl userManagerImpl;这样就写死了,面向接口编程就是这样的。
到时你把目标对象的引用给传过来就行了。
29. 怎么传,我们 可以采用这种方式,来一个构造方法,构造方法
public UserManagerImplProxy(UserManager userManager) {
//把你new 好的真实实现放到我们的成员变量里面。
this.userManager = userManager;
}
构造方法里面是什么呢?是我们的接口,这个接口,到时你new 我们的代理的时候,你new 一个什么,new 一个真正实现的目标给我们就可以了。
30. 这样就跟目标有了一个关联,你拿到了userManager你当然可以调真正的实现。
public void addUser(String userId, String username) {
System.out.println("start-->>addUser() - " + userId);
try {
//调用真实对象的方法
userManager.addUser(userId, username);
System.out.println("success-->>addUser()");
}catch(RuntimeException e) {
System.out.println("error-->>addUser()");
throw e;
}
}
31. 现在你的调用 将发生改变,
UserManager userManager = new UserManagerImplProxy(new UserManagerImpl());
//其实上面一句的创建可以采用一个工厂隐藏掉
userManager.addUser("0001", "张三");
你交给客户,让客户来调什么呢?调代理。
32. 说白了就多写了一个类,去完成一些功能。
33. 现在一运行,效果是不是一样的啊,原来的代码我没有改,我只是加 了一个类,把这个类交给客户端。
34. 这就是我们刚才说的ocp原则,我满足了,
这就是代理,跟我们现实中的代理是一样的。
35. 但是这种方式确确实实解决了我们的需求,但是也存在一些问题。假设我们有很多方法,你怎么办这些方法,你还是都要写
System.out.println("start-->>addUser() - " + userId);
try {
//调用真实对象的方法
userManager.addUser(userId, username);
System.out.println("success-->>addUser()");
}catch(RuntimeException e) {
System.out.println("error-->>addUser()");
throw e;
}
这些东西,仍然避免不了一个问题,重复的东西不要出现多次。方法就在这个代理类里面,你要用,你就得写,如果有很多类,你就得写很多代理类,
36. 重复量大工作量也大啊,现在我想到了以前讲的Filter,咱们在没有用Filter的时候,每次请求都得设置一下字符集,你没有办法不设,你要用你就得设。
37. filter是一种什么技术,是一种横切性的技术。你看上面的东西是不是类似啊,调用方法之前打印一下,调用之后打印一下。
38. 上面说的那个打印我只要写一份,就用动态代理来实现,我只写一份就可以完成了。动态代理功能是非常强大的,其实有很多框架的实现包括服务器的一些实现,像jboss,他最先引入了的动态代理,他的ejb容器的实现。
40. 静态代理你可以这样理解,这个代理类是确确实实存在的,而动态代理类,那个代理类没有,是在运行期在内存里生成出来的,你看我现在是一个UserManagerImplProxy 假设我还有一个UserItem你还得写一个UserItemProxy,用动态代理就不用写了。
41. 动态代理就像filter似的,记录日志的代码放一份就可以了。不用出现那么多次。我们把单独的记录日志的代码放到一个类里,
写一个LogHandler类,这个类也一样,要实现一个接口,那这个接口在哪里,在jdk里面 是
InvocationHandler接口,相当于filter接口,你就这么理解吧,容易,在这个接口里就一个方法
Object invoke(Object proxy,Method method,Object[] args) 你就把这个方法理解为filter里面的doFilter方法吧
42. 那我们在这个方法里做什么事啊,以前做字符集的设置,现在要做的事就是记录日志的操作啊,
Object invoke(Object proxy,Method method,Object[] args) {
System.out.println("start-->>addUser() - " + userId);
}
我们写到这里面不能把这个方法写死了,因为执行任何方法之前,他都先调他,所以我们可以自动拿到这个方法,怎么拿,你看这个方法的参数,不就知道了,第一个参数,说你要传入代理的对象,第二个参数,代理的方法为什么,
43. 这个方法的解析,自己到帮助文档里面去看吧,好长,但是得注意了这个接口位于java.lang.reflect包下面。
44. 所以那个方法你得这样写
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("start-->>" + method.getName()); //注意这里啊method.getName()
45. 以前还这样写
System.out.println("start-->>addUser() - " + userId);这个参数不就是写死了吗?现在采用那个方法中的第三个参数Object[] args这个参数把你里面的参数全拿出来。
46. //第二个参数 args表示,你调用userManager方法的时候,你传过来的参数
Object ret = null;
ret = method.invoke(this.targetObject, args);
你看你这样去调用方法,有些方法有返回值,有些没有,所以jdk 中是这么处理的,都有返回值,返回一个object
47.
public Object newProxyObject(Object targetObject) {
this.targetObject = targetObject;
//对这个对象生成一个代理类。第一个参数classLoader因为要加载类嘛
//第二个参数,把目标对象的接口传过去,这个代理是这样的,只能对实现了接口的类生成代理
//所以把你这个类的所有的接口传过去,你看是一个getInterfaces()加s
//第三个,你要传过去一个对象,这个对象是什么,实现了InvocationHandler的对象,我把这个newProxyObject写在这里面了,那LogHandler就实现了InvocationHandler,所以可以传一个this
//如果你把这个方法写在别的类里面了,那么 你第三个参数就得new LogHandler
return Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), this);
}
48. 在Client类里怎么调用这个动态代理呢?
LogHandler handler = new LogHandler();
UserManager userManager = (UserManager)handler.newProxyObject(new UserManagerImpl());
userManager.addUser("0001", "张三");
一运行,效果一模一样。假设现在再改一下
String name = userManager.findUserById("0001");
System.out.println(name);对这个里面的所有方法都起作用。
49. 因为这个代理呢?很重要的一点就是要跟源对象的接口是一样的。
50. 尽量能遵循ocp原则那是最好了。
51. 静态代理的缺点:在创建代理的时候,要建大量的代理类出来。2.重复的代码会出现在各个角落里,这就违背了一个原则重复的代码最好不要出现多次。但是你用静态代理确实没有办法,你确实要去调用啊。
52. 使用动态代理把重复的代码放到一个地方,那么你得放到 一个类里啊,你不放到类里你就地方放。
public void invoke(Object proxy)第一个参数我们一般不会去用。指的就是他的代理对象,就好像我们以前写的UserManagerImplProxy一样。
53. method指的是这次方法调用对应的对象,假设你现在调用add方法,这add方法会对应一个对象method
54. method.invoke(this.targetObject,args);
第一个参数是目标,他会自动地来调用
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
第三个参数是实现了InvocationHandler的一个对象。我放了this就是当前的对象
55. 其实Jdk的这个代理,是根据接口创建的,你代理的这个类一定要实现一个接口。如果不实现接口他就会报错。创建不出来的。
56. 因为代理对象和目标对象的接口一模一样,虽然他自己给你创建一个,但是和目标的一模一样,
//String name = userManager.findUserById("0001");
//System.out.println(name);
你现在调用 的是代理的。不是真正的User 他设了一个断点,来说明问题了。
57. 你一调用代理对象上的方法,他就去调实现了InvocationHandler的对象的invoke()方法,也就是调用真正的目标之前,先调用实现了InvocationHandler的对象的invoke()方法
58. 使用debug模式运行的时候,你把鼠标放上去,直接可以看到那个变量有没有值。
59. 重点说明第三个参数,你看啊,他先是产生一个代理对象出来,在你调用代理对象上的方法的时候,比如add 他就先调用 invoke()方法,给你做一些事情,你调用add的时候,不是指定说调用add方法,然后你又传一些参数进去不。
60. 其实你调用 userManager.addUser("0001", "张三");其实是调到了代理上,这也就实现了在调用方法之前打印日志,调用完成后再打印日志。
在这个invoke方法里面,还有一句
ret = method.invoke(this.targetObject,args);在这里真正去调用我们的方法,代理执行完了。真正去调那就目标
61. 我们这个添加没有返回值,所以这个ret里面就是一个null在里面。
62. 这个动态代理他也有他的缺点,在性能上肯定不如静态代理,因为这个代理类是在运行时自动生成出来的。
63. 现在采用动态代理来封装一下我们那个项目的事务。
64. 现在再来看一个在FlowCardServiceImpl类里面public void addFlowCard(FlowCard flowCard){
Connection conn = null;
conn = ConnectionManager.getConnection();
ConnectionManager.setAutoCommit(conn,false);
其实你看一下,这个取得conn 和开启事务,应该
不太属于这个添加流向单的方法。添加流向单你还管什么开启事务之类的。而且我如果这样写的话,假设
我还涉及到删除修改之类的,你都得需要开启事务都得需要conn,最后还得提交,还得回滚,
}
65. 也确实啊,在service里面写了这么多其它的代码,这个service里面应该就是方法的调用,那么同样是重复,我们完全可以把这些东西单独拿出来放到一个类里,让他运行的时候,一调用这个方法就开启事务,得到 conn开启事务,等这个方法执行成功就commit,这样的话,就可以简化我们的代码,我们采用动态代理来做。
66. 之前是不是我们这个开户事务的代码就在各个角落,采用动态代理好处多多啊。
67. 建 一个TransactionHandler类,同样继承InvocationHandler接口
68. 每一个类在堆区中都有一个class与之对应,所以通过这个class可以拿到这个类的相关信息,第二个参数是要把我们传过来目标的所有接口给拿过来,因为他只能对接口进行代理,
69. 咱们要保存代理完成的这个目标,那你得定义一个成员变量,来保存public Object newProxyObject(Object targetObject){}方法生成的对象,因为后面还要用啊,你这个类实现了InvocationHandler接口,后面还有一个public Object invoke()方法里面要用这个代理产生的对象,
70. 你想想,当我们要调用service里面的add方法,就调到了代理类上,你就可以在代理类里面写拿到连接,开启事务,就是这样实现的。
71. 我们的可以进一步地处理,哪些方法需要手动设置事务,add 是需要手动设置的,查询方法可以不用,用默认的就可以了,那我们怎么区分呢?
因为我们的方法命名不一样,findFlowCardList find开头的,那么我就不用手动设置事务,add开头的,我让他手动设置事务,一进到这个方法之前就让他的事务不自动提交了,等他执行完,没有出异常就自动提交。
72. 在TransactionHandler类中不是继承了那个接口不,在invoke方法中
conn = ConnectionManager.getConnection();
if(method.getName().startsWith("add")||
method.getName().startsWith("del")||
method.getName().startsWith("modify")
){
//如果满足 我就开启事务
ConnectionManager.setAutoCommit(conn,false);
}
他不是可以拿到方法名吗?再拿他是add开头的。del,modify开关的这些方法
不能什么情况都提交啊,只能执行了上面
ConnectionManager.setAutoCommit(conn,false);这一句才提交,下面的语句才能执行。下面的意思就是,你只有设置了自动提交了我才给你提交事务
if(!conn.getAutoCommit()){
ConnectionManager.commit(conn);
}
73. 其实上面的都写死了,我还在里面写了add modify del你可以把这些放到一个配置文件中去。
74. 也就是说遇到上面三个声明的add modify del开头的方法就开启事务。这样就是一个事务的一个框架,这就是我们以后要讲的spring的aop.他的底层实现也是这么做的,在默认的情况下也是采用jdk的动态代理。
75. 你看好处是,你的ConnectionManager.close();方法不用到处写了,就写一份。
76. 现在他把service层里面的取得连接的代码全删了。
77. 我们这个FlowCardService在哪创建出来的,在servlet里面,那我们再对这个service生成一个代理不就完了吗?就在FlowcardServlet的init()方法里面去写,他说不那样搞了,太麻烦了,以后 有现在的这种事务框架,
TransactionHandler transaction = new TransactionHandler();
flowCardService = (FlowCardService)transaction.newProxyObject(flowCardService);
78. 这个动态代理控制事务还存在一个问题,现在故意把sql语句写错如:insert1 into 看看会出什么问题。
79. 一点debug模式运行了,你走了几个方法你觉得过去了,想重走,你点好个绿色的像三角一样的符号再走一遍,不就是了吗?你想指定的看多个方法,你就加多个断点去调试啊。
80. 现在动态代理抛异常,抛的时候会有问题,我们在service层抛异常是AppException,动态代理把异常给转了,转成InvocationTargetException调用目标异常,那我们以前的异常信息就没有了,本来在service层写的是添加流向单失败,现在到动态代理去找到了我写的调用失败,这肯定不对啊
81. 运行的时候会报InvocationTargetException异常,你去看一下这个异常的源代码,我们的AppException为什么出不来了呢?给抛到他源代码里面的一个
private Throwable target;属性上去了,通过这个属性我们可以拿到AppException
有一个getTargetException
82. //永远不会直到AppException那个catch里面去了。把所有的都拦了,我在下面再判断一下。
//如果是他,我们就可以把我们最原始的异常拿出来
ConnectionManager.rollback(conn);
if (e instanceof InvocationTargetException) {
InvocationTargetException te = (InvocationTargetException)e;
throw te.getTargetException();
}
throw new AppException("调用失败!");
83. 一直往上抛,tomcat到web.xml文件中一看,你配了,说这个AppException 要转到哪个页面去。
84. 留一个作业 ,计算一下ItemService每个方法执行了多长时间,单位精确到秒,用动态代理计算itemService中的方法执行。
85. 他说他以前做项目都有这个要求,每一个service中的方法都得打印开始时间,执行时间,结束时间,到时测试人员会看的,这个方法执行多长时间。
86. 另一个作业,把创建代理的与实现分开,他的目的不是已经很明确了吗,就是为了让我们的第三个参数不用this,而用new XXX()这样来做不。
相关推荐
代理模式是一种设计模式,它在软件工程中扮演着重要的角色,允许我们为其他对象提供一个替代接口,以控制对原始对象的访问。这种模式的主要目的是为了增加灵活性、安全性或者在不修改原有对象的情况下,增强或扩展其...
代理模式是设计模式的一种,它提供了一种对目标对象进行增强或者控制访问的方式。在本实例中,我们将深入探讨Java中的代理模式及其应用。 代理模式的核心思想是为一个对象创建一个代理对象,这个代理对象在客户端和...
代理模式是设计模式中的一种结构型模式,它在对象交互中起到了中介的作用,允许通过代理对象来控制对原对象的访问。代理模式的核心思想是为一个对象提供一个替身,以便增加新的功能或者控制对原对象的访问。这种模式...
代理模式是设计模式的一种,它的主要目的是在不改变原有对象的基础上,为一个对象提供额外的功能或者控制对这个对象的访问。在Android开发中,代理模式的应用尤为常见,尤其在处理复杂的业务逻辑、网络请求、界面...
SignalR提供了两种主要的工作模式:代理模式和非代理模式。这两种模式在实现上有所不同,各自具有优缺点,适用于不同的场景。 **1. 代理模式(Proxy Mode)** 在代理模式下,SignalR为每个Hub(服务端的业务逻辑...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要的角色,特别是在iOS平台的应用程序设计中。代理模式的核心思想是为一个对象提供一个替身或代理,以控制对这个对象的访问。这种模式允许我们通过代理来间接...
在Java编程中,代理模式是一种常用的面向对象设计模式,它允许我们为一个对象提供一个代理以控制对该对象的访问。代理模式通常用于增加额外的功能,如日志、权限检查等,或者为了创建虚拟代理以提高性能。以下是Java...
**Java设计模式——代理模式详解** 代理模式是软件设计模式中的一个重要组成部分,它在Java编程中扮演着举足轻重的角色。代理模式的核心思想是为一个对象提供一个替身,这个替身即代理对象,代理对象可以控制对原...
代理模式是一种设计模式,它在软件工程中扮演着重要的角色,允许我们为其他对象提供一个替代接口,以控制对原对象的访问。这种模式的主要目的是为了增加灵活性、安全性或者为对象提供额外的功能,同时保持客户端代码...
**设计模式之代理模式(Proxy Pattern)** 设计模式是软件工程中的一种最佳实践,它是在特定情境下解决常见问题的模板。代理模式是其中一种行为设计模式,它的核心思想是为一个对象提供一个替身或者代理,以控制对...
**设计模式实现——代理模式** 在软件工程中,设计模式是一种通用可重用的解决方案,它描述了在特定上下文中经常出现的问题以及该问题的解决方案。代理模式是设计模式的一种,它提供了一种对目标对象的间接访问方式...
代理模式是一种设计模式,属于结构型模式之一,其主要目的是为其他对象提供一个代理,以控制对该对象的访问。在实际应用中,代理模式能够帮助我们实现如下的功能: 1. 远程代理:代理对象可以代表一个位于远程系统...
在这个“Java设计模式-代理模式例子”中,我们将深入探讨代理模式的概念、实现方式以及它在实际开发中的应用。 代理模式的核心思想是为一个对象提供一个替身,这个替身即代理对象,代理对象控制对原对象的访问。在...
代理模式在软件设计中是一种常用的设计模式,尤其在Android开发中,它可以帮助我们实现复杂的控制逻辑,隔离复杂性,以及提供额外的功能。在Android上下文中,代理模式常常用于数据加载、权限控制、事件处理等方面。...
代理模式是一种设计模式,它是结构型模式之一,主要用于在客户端和目标对象之间建立一个代理对象,以便控制对目标对象的访问。在C++中,代理模式可以用来为其他对象提供一种代理以控制对这个对象的访问,或者增加...
### Java代理模式与Java动态代理详解 #### 一、代理模式概述 代理模式是一种软件设计模式,它在客户端和目标对象之间提供了一种间接层。这种模式的主要目的是控制客户端对目标对象的访问,并且可以在不修改原有...
代理模式是一种常用的设计模式,它在软件开发中扮演着重要角色,允许我们通过一个代理类来控制对原对象的访问。在《设计模式:可复用面向对象软件的基础》(通常称为GoF设计模式)中,代理模式被定义为“为其他对象...
代理模式(Proxy) 定义: 为其他对象提供一种代理以控制对这个对象的访问 结构: 由三部分组成 1.RealSubject(真实对象): 真正会调用到的对象 2.Proxy(代理对象): 代理真实对象的地方 3.Subject(共同点): 代理对象...
在IT行业中,代理模式是一种常见的设计模式,它允许我们在不修改原有对象的基础上,为对象添加新的功能或控制访问。在本示例中,我们将重点讨论如何在Java环境下使用代理模式来实现代理逻辑,特别是在CAS(Central ...