JavaWeb开发(三)3.6——代理模式

文章详细介绍了代理模式,包括静态代理和动态代理的概念、应用场景及优缺点。静态代理通过预先定义的接口实现,但当接口改变时需要同步修改。动态代理分为JDK和CGLIB实现,JDK代理基于接口,CGLIB代理基于子类创建。CGLIB适用于未实现接口的目标类,且执行效率相对较高。
摘要由CSDN通过智能技术生成

一、代理模式概述

1.1、代理模式的理解

参考人家的举例,感觉挺形象,容易理解:
就拿明星与经纪人的关系来说,明星就好比被代理类,明星只需要负责唱歌,表演或给粉丝签名等事务,而类似签合同,面谈,计划日程安排等经纪事务都不需要明星个人去做,可以交给其经纪人来代理完成。

1.2、代理模式的分类:

  • 静态代理模式(静态的定义代理类,编译前定义好):涉及接口的使用。
  • 动态代理模式(动态的生成代理类):涉及接口、反射、Proxy类的相关api。

1.3、代理模式的应用场景

  • 安全代理:对外隐藏真实的调用者
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载

eg:
意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。2、买火车票不一定在火车站买,也可以去代售点。 3、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。4、spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

二、静态代理

2.1、静态代理模式的实现要求

  • 提供两个真实的具体的类,代理类与被代理类;
  • 两个类同时实现同一个接口,接口中定义多个抽象方法(提取代理类,被代理各自的行为任务);
  • 代理类一定要由被代理对象的引用,为了能在代理类中调用被代理类的重写接口中的方法。

2.2、静态代理存在的问题

  • 一旦接口新增或者修改,那么代理对象和被代理对象就得去适配修改。
  • 静态代理是在代码编写时,去生成的,class文件必然会造成类爆炸的风险。

2.3、静态代理示例

(1)被代理接口:

public interface StarInterface {
	//个人签名
	void personSignature();
	//表演
	void show();
	//签合同
	void signContract();
	//日程安排
	void schedule();
}

(2)被代理类(被代理类实现被代理接口):

//明星类
public class Star implements StarInterface{
	@Override
	public void personSignature() {
		System.out.println("明星个人签名");
	}
 
	@Override
	public void show() {
		System.out.println("明星表演");
	}
 
	@Override
	public void signContract() {

	}
 
	@Override
	public void schedule() {

	}
	
}

(3)代理类(实现被代理接口,有被代理对象的引用):

//经纪人
public class Agent implements StarInterface{
	private Star star;
	
	public Agent(Star star) {
		this.star = star;
	}
	
	public Agent() {
		
	}
	
	@Override
	public void personSignature() {
		star.personSignature();
	}
 
	@Override
	public void show() {
		star.show();
	}
 
	@Override
	public void signContract() {
		System.out.println("经纪人谈合同");
	}
 
	@Override
	public void schedule() {
		System.out.println("经纪人安排演唱会");
	}
	
}

(4)测试类:

public class StaticProxyTest {
	public static void main(String[] args) {
		Agent agent = new Agent(new Star());
		agent.personSignature();
		agent.show();
		agent.schedule();
		agent.signContract();
	}
}

三、动态代理

当我们写了一个接口,里面有方法,然后写了实现类实现方法,完成方法逻辑,然后有一天,想对这个方法进行修改,但是不想改源码,有两种方式可以实现。第一种是静态代理,创建一个类继承实现类,然后对方法进行修改,这样太局限,因为只能针对特定的类增强方法,有100个实现类就要创建100个子类去实现。第二种方式是动态代理,可以动态地生成代理类,这是可以接受的。

动态代理,顾名思义,就是在运行时去动态生成代理,我们下面分别说两种实现方式,分别为JDK 动态代理 和 Cglib 动态代理。

3.1、JDK动态代理

JDK提供了一些API,可以实现动态代理,通过实现 InvocationHandler接口和 Proxy.newProxyInstance来实现。

(1)需要被代理的接口(必须要有)和实现类,采用上述静态代理中的例子,被代理接口StarInterface和被代理类Star。

(2)创建动态代理类
需要实现InvocationHandler接口,重写invoke方法,这里可以对方法进行增强。:

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

public class Dynamicproxy implements InvocationHandler {
    private Object targetObject;

    public Dynamicproxy(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("日志开始");
        Object invoke = method.invoke(targetObject, args);
        System.out.println("日志结束");
        return invoke;
    }
}

(3)创建动态代理对象

import com.qizekj.proxy.Star;
import com.qizekj.proxy.StarInterface;

import java.lang.reflect.Proxy;

/**
 * @author chenqun
 * @date 2023/3/10 20:20
 */
public class JDKProxyTest {
    public static void main(String[] args) {
        //生成代理类文件 在根目录的同级目录,com下
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //实现了接口的业务类
        Star iservice = new Star();
        //获取Class对象
        Class<?> iserviceClass = iservice.getClass();
        //代理类 实现需要实现InvocationHandler接口,重写invoke方法 传入业务实现类对象
        Dynamicproxy dynamicproxy = new Dynamicproxy(iservice);

        //创建代理类对象
        StarInterface so = (StarInterface) Proxy.newProxyInstance(iserviceClass.getClassLoader(),
                iserviceClass.getInterfaces(), dynamicproxy);
        so.show();

    }
}

运行结果:
在这里插入图片描述

如果这时接口新增或者修改,不再需要去修改代理类。因为其本质是,通过反射+classloader去实现的,所以不依赖于对象。
但是依然有一个问题,就是Proxy.newProxyInstance使用上有一个弊端,就是必须面向接口,如果源对象也就是被代理对象,如果没有实现某个接口,这时就不能用JDK动态代理了,此时 Cglib 可以帮我们解决这个问题。

3.2、Cglib 代理

我们知道一个对象在内存中存储,一般就是在栈或者堆上,那是否有一种方法或者组件,可以直接从内存copy这个对象,实现深克隆,也就是在内存中在copy一个出来。

(1)Cglib代理模式的基本介绍

  • JDK动态代理模式要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是Cglib代理。
  • Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展。
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
  • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
  • Cglib由于是基于字节码的,显然这时android就不能使用了,因为android是dex文件,这怎么办?没关系,有dexmaker 和 cglib-for-android 库。

(2)Cglib代理的实现

  • 第一步:引入cglib的文件,目标类使用上述静态方法中的Star。
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.7</version>
</dependency>
  • 第二步:创建方法拦截器
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author chenqun
 * @date 2023/3/10 20:33
 */
public class MyInterceptor implements MethodInterceptor {

    private Object target;

    public MyInterceptor(Object target) {
        this.target = target;
    }

    /**
     *
     * @param o 代理对象
     * @param method 被代理对象的方法
     * @param objects 方法入参
     * @param methodProxy 代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("-----cglib before-----");
        // 调用代理类FastClass对象
        Object result =  methodProxy.invokeSuper(o, objects);
//        Object result = methodProxy.invoke(target, objects);
        System.out.println("-----cglib after-----");
        return result;
    }
}
  • 第三步,调用测试。
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

/**
 * @author chenqun
 * @date 2023/3/10 20:37
 */
public class CglibTest {
    public static void main(String[] args) {
        Star star = new Star();

        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(Star.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyInterceptor(star));
        // 创建代理对象
        Star starService = (Star)enhancer.create();
        // 通过代理对象调用目标方法
        starService.personSignature();
        starService.show();
    }
}

运行结果:
在这里插入图片描述

3.3、JDK 和 CGLIB动态代理的区别

JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB代理使用字节码处理框架asm,对代理对象类的class文件加载进来,通过修改字节码生成子类。

JDK创建代理对象效率较高,执行效率较低;
CGLIB创建代理对象效率较低,执行效率高。

JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。

JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
 1.实现InvocationHandler接口,重写invoke()
 2.使用Proxy.newProxyInstance()产生代理对象
 3.被代理的对象必须要实现接口
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
 1.实现MethodInterceptor接口,重写intercept()
 2.使用Enhancer对象.create()产生代理对象

总结:

(1)jdk代理只能对实现了接口的类进行代理,而cglib代理可以对普通类进行代理;
(2)jdk代理是通过反射的方式来实现动态代理,而cglib则是通过为目标类生成一个子类的方式来实现动态代理;
(3)由于cglib代理是为目标类生成了一个子类,并对父类方法进行增强,所以目标类不能用final修饰;

3.4、什么情况下使用JDK或CGLIB代理

(1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP。
(2)如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。

3.5、强制使用CGLIB实现AOP的方法

(1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
(2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JavaWeb中,实现网站用户登录功能通常涉及到以下几个关键步骤: 1. **前端页面设计**:创建登录界面,包括用户名输入框(username)、密码输入框(password),以及登录按钮。可以使用HTML、CSS和JavaScript进行前端开发。 2. **后端服务器处理**:后端使用Java和Servlet或Spring MVC框架接收用户的登录请求。通常会涉及以下步骤: - 用户名和密码的验证:检查输入的用户名和密码是否匹配数据库中的记录。你可以使用JDBC连接数据库查询用户信息,也可以使用ORM框架如Hibernate或MyBatis。 - 使用Session或Cookie管理用户状态:如果验证通过,为用户创建一个Session,存储用户标识(通常是登录凭据的哈希值)或其他重要信息,这样后续请求可以识别用户。 3. **安全性考虑**:确保密码安全,一般会采用哈希+盐的方式加密存储,登录时对比哈希值。同时,防止SQL注入和XSS攻击。 4. **错误处理和反馈**:对输入错误或验证失败的情况,返回合适的错误消息给前端,并可能显示错误提示。 5. **登录/登出功能**:除了登录,还需要提供登出功能,清除Session或Cookie,结束用户会话。 6. **登录日志记录**:为了审计和安全,应该记录用户的登录尝试和结果。 相关问题: 1. 如何在JavaWeb中防止跨站脚本攻击(XSS)? 2. 什么是Session和Cookie的区别,它们在用户登录中的作用是什么? 3. 如何在Java中使用Spring Security来增强登录系统的安全性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吟诗作对歌一曲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值