代理模式

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()这样来做不。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值