
- 作 用
- 防火墙
- 工作区域
- 开放系统互联模型的会话层
- 中文名
- 代理服务器
- 外文名
- proxy server
-
Java两种动态代理JDK动态代理和CGLIB动态代理
2018-08-07 15:33:35代理模式 JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现...目录
代理模式
代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。
使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。
上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。
JDK动态代理
jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。
首先有个“明星”接口类,有唱、跳两个功能:
package proxy; public interface Star { String sing(String name); String dance(String name); }
再有个明星实现类“刘德华”:
package proxy; public class LiuDeHua implements Star { @Override public String sing(String name) { System.out.println("给我一杯忘情水"); return "唱完" ; } @Override public String dance(String name) { System.out.println("开心的马骝"); return "跳完" ; } }
明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。
package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class StarProxy implements InvocationHandler { // 目标类,也就是被代理对象 private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这里可以做增强 System.out.println("收钱"); Object result = method.invoke(target, args); return result; } // 生成代理类 public Object CreatProxyedObj() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:
1、new一个目标对象
2、new一个InvocationHandler,将目标对象set进去
3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。
Star ldh = new LiuDeHua(); StarProxy proxy = new StarProxy(); proxy.setTarget(ldh); Object obj = proxy.CreatProxyedObj(); Star star = (Star)obj;
Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。
所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。
看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 这里增强 System.out.println("收钱"); Object result = method.invoke(target, args); return result; }
所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。
整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。
下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:
public final class XXX extends Proxy implements XXX
一个方法代码如下:
public final String SayHello(String paramString) { try { return (String)this.h.invoke(this, m4, new Object[] { paramString }); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); }
可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)
C类中有B中所有方法的成员变量
private static Method m1; private static Method m3; private static Method m4; private static Method m2; private static Method m0;
这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]); m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } }
从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。
jdk动态代理类图如下
cglib动态代理
我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。
通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。
先看下代码
package proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中 public Object CreatProxyedObj(Class<?> clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { // 这里增强 System.out.println("收钱"); return arg3.invokeSuper(arg0, arg2); } }
从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。
cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。
这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。
C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。
这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?
@Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { System.out.println("收钱"); return arg3.invokeSuper(arg0, arg2); }
因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。
所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。
fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。
对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。
参考
https://blog.csdn.net/jiankunking/article/details/52143504
http://www.php.cn/java-article-407212.html
https://www.cnblogs.com/chinajava/p/5880887.html
https://rejoy.iteye.com/blog/1627405
测试
public static void main(String[] args) { int times = 1000000; Star ldh = new LiuDeHua(); StarProxy proxy = new StarProxy(); proxy.setTarget(ldh); long time1 = System.currentTimeMillis(); Star star = (Star)proxy.CreatProxyedObj(); long time2 = System.currentTimeMillis(); System.out.println("jdk创建时间:" + (time2 - time1)); CglibProxy proxy2 = new CglibProxy(); long time5 = System.currentTimeMillis(); Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class); long time6 = System.currentTimeMillis(); System.out.println("cglib创建时间:" + (time6 - time5)); long time3 = System.currentTimeMillis(); for (int i = 1; i <= times; i++) { star.sing("ss"); star.dance("ss"); } long time4 = System.currentTimeMillis(); System.out.println("jdk执行时间" + (time4 - time3)); long time7 = System.currentTimeMillis(); for (int i = 1; i <= times; i++) { star2.sing("ss"); star2.dance("ss"); } long time8 = System.currentTimeMillis(); System.out.println("cglib执行时间" + (time8 - time7)); }
经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。
-
免费IP代理网
2019-04-29 10:34:3420、http://www.nimadaili.com/泥马IP代理 19、http://lab.crossincode.com/proxy/Crossin编程教室 18、http://www.xsdaili.com/dayProxy/ip/1415.html小舒代理 17、http://www.xiladaili.com/西拉免费代理IP 16...20、http://www.nimadaili.com/ 泥马IP代理
19、http://lab.crossincode.com/proxy/ Crossin编程教室
18、http://www.xsdaili.com/dayProxy/ip/1415.html 小舒代理
17、http://www.xiladaili.com/ 西拉免费代理IP
16、http://ip.jiangxianli.com/ 免费代理IP库
15、http://www.superfastip.com/ 极速代理
14、http://ip.kxdaili.com/ 开心代理
13、https://proxy.mimvp.com/free.php 米扑代理
12、http://www.shenjidaili.com/open/ 神鸡代理IP
11、http://31f.cn/http-proxy/ 三一代理
10、http://www.feiyiproxy.com/?page_id=1457 飞蚁代理
9、http://ip.zdaye.com/dayProxy/2019/4/1.html 站大爷
8、http://www.66ip.cn 66免费代理网
7、https://www.kuaidaili.com/free/inha 快代理
6、https://www.xicidaili.com 西刺
5、http://www.ip3366.net/free/?stype=1 云代理
4、http://www.iphai.com/free/ng IP海
3、http://www.goubanjia.com/ 全网代理
2、http://www.89ip.cn/index.html 89免费代理
1、http://www.qydaili.com/free/?action=china&page=3 旗云代理
-
nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件,很全
2019-10-09 15:53:47正向代理4. 反向代理5. 动静分离6.动静分离二、Nginx 的安装三、 Nginx 的常用命令和配置文件四、 Nginx 配置实例 1 反向代理五、 Nginx 配置实例 2 负载均衡六、 Nginx 配置实例 3 动静分离七、 Nginx 的高可用...文章目录
- 前言
- 一、nginx简介
- 二、Nginx 的安装(Linux:centos为例)
- 三、 Nginx 的常用命令和配置文件
- 四、 Nginx 反向代理 配置实例 1.1
- 五、 Nginx 反向代理 配置实例 1.2
- 六、 Nginx 负载均衡 配置实例 2
- 七、 Nginx 动静分离 配置实例 3
- 八、 Nginx 的高可用集群
- 九、 Nginx 的原理
前言
一、nginx简介
1. 什么是 nginx 和可以做什么事情
-
Nginx 是高性能的 HTTP 和反向代理的web服务器,处理高并发能力是十分强大的,能经受高负 载的考验,有报告表明能支持高达 50,000 个并发连接数。
-
其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
2.Nginx 作为 web 服务器
- Nginx 可以作为静态页面的 web 服务器,同时还支持 CGI 协议的动态语言,比如 perl、php 等。但是不支持 java。Java 程序只能通过与 tomcat 配合完成。Nginx 专为性能优化而开发, 性能是其最重要的考量,实现上非常注重效率 ,能经受高负载的考验,有报告表明能支持高 达 50,000 个并发连接数。
https://lnmp.org/nginx.html
3. 正向代理
Nginx 不仅可以做反向代理,实现负载均衡。还能用作正向代理来进行上网等功能。 正向代理:如果把局域网外的 Internet 想象成一个巨大的资源库,则局域网中的客户端要访 问 Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理。
- 简单一点:通过代理服务器来访问服务器的过程 就叫 正向代理。
- 需要在客户端配置代理服务器进行指定网站访问
4. 反向代理
- 反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问。
- 我们只 需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返 回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器 地址,隐藏了真实服务器 IP 地址。
5. 负载均衡
-
增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的 情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负 载均衡
-
客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服 务器处理完毕后,再将结果返回给客户端。
这种架构模式对于早期的系统相对单一,并发请求相对较少的情况下是比较适合的,成 本也低。但是随着信息数量的不断增长,访问量和数据量的飞速增长,以及系统业务的复杂 度增加,这种架构会造成服务器相应客户端的请求日益缓慢,并发量特别大的时候,还容易 造成服务器直接崩溃。很明显这是由于服务器性能的瓶颈造成的问题,那么如何解决这种情 况呢?
我们首先想到的可能是升级服务器的配置,比如提高 CPU 执行频率,加大内存等提高机 器的物理性能来解决此问题,但是我们知道摩尔定律的日益失效,硬件的性能提升已经不能 满足日益提升的需求了。最明显的一个例子,天猫双十一当天,某个热销商品的瞬时访问量 是极其庞大的,那么类似上面的系统架构,将机器都增加到现有的顶级物理配置,都是不能 够满足需求的。那么怎么办呢?上面的分析我们去掉了增加服务器物理配置来解决问题的办法,也就是说纵向解决问题 的办法行不通了,那么横向增加服务器的数量呢?这时候集群的概念产生了,单个服务器解 决不了,我们增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们 所说的负载均衡
6.动静分离
为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速 度。降低原来单个服务器的压力。
二、Nginx 的安装(Linux:centos为例)
nginx安装时,用到的包,我都准备好啦,方便使用:
https://download.csdn.net/download/qq_40036754/11891855
本来想放百度云的,但是麻烦,所以我就直接上传到我的资源啦,大家也可以直接联系我,我直接给大家的。1. 准备工作
- 打开虚拟机,使用finallshell链接Linux操作系统
- 到nginx下载软件
http://nginx.org/
- 先安装其依赖软件,最后安装nginx。
- 依赖工具:pcre-8.3.7.tar.gz, openssl-1.0.1t.tar.gz, zlib-1.2.8.tar.gz, nginx-1.11.1.tar.gz。 我这里也提供下。
2. 开始安装
- 都有两种方式,一种直接下载,第二种使用解压包方式。这里大多使用解压包方式。
- 我的安装路径:/usr/feng/
- 安装pcre
方式一、wget http://downloads.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz 。
方拾二、上传源码压缩包,解压、编译、安装 三部曲。
1)、解压文件, 进入pcre目录,
2)、./configure 完成后,
3)、执行命令: make && make install - 安装 openssl
下载OpenSSL的地址:
http://distfiles.macports.org/openssl/
1)、解压文件, 回到 openssl目录下,
2)、./configure 完成后,
3)、执行命令: make && make install - 安装 zlib
1)、解压文件, 回到 zlib 目录下,
2)、./configure 完成后,
3)、执行命令: make && make install - **安装 nginx **
1)、解压文件, 回到 nginx 目录下,
2)、./configure 完成后,
3)、执行命令: make && make install
3. 运行nginx
- 安装完nginx后,会在 路径 /usr/local 下自动生成 nginx 文件夹。这是自动生成的。
- 进入这个目录:
cd /usr/local/nginx
目录内容如下:
- 进入sbin文件夹,里面有两个文件:nginx 和 nginx.old。
- 执行命令:./nginx 即可执行
- 测试启动: ps -ef | grep nginx
已经启动。 - 查看nginx默认端口(默认为80),使用网页的形式测试,(像Tomcat一样。)
- 进入目录查看端口:cd /usr/local/nginx/conf 下的 nginx.conf文件。这个文件也是nginx的配置文件。vim 下:
如下 - 输入IP:80,则显示:
4. 防火墙问题
在 windows 系统中访问 linux 中 nginx,默认不能访问的,因为防火墙问题 (1)关闭防火墙 (2)开放访问的端口号,80 端口
查看开放的端口号
firewall-cmd --list-all
设置开放的端口号
firewall-cmd --add-service=http –permanent firewall-cmd --add-port=80/tcp --permanent
重启防火墙
firewall-cmd –reload
三、 Nginx 的常用命令和配置文件
1. Nginx常用命令
a. 使用nginx操作命令前提
使用nginx操作命令前提:必须进入到nginx的自动生成目录的下/sbin文件夹下。
nginx有两个目录:
第一个:安装目录,我放在:/usr/feng/
第二个:自动生成目录:
/usr/local/nginx/
b. 查看 nginx 的版本号
./nginx -v
c. 启动 nginx
./nginx
d. 关闭nginx
./nginx -s stop
e. 重新加载 nginx
在目录:/usr/local/nginx/sbin 下执行命令,不需要重启服务器,自动编译。
./nginx -s reload
2. Nginx配置文件
a. 配置文件位置
/usr/local/nginx/conf/nginx.conf
b. nginx 的组成部分
配置文件中有很多#, 开头的表示注释内容,我们去掉所有以 # 开头的段落,精简之后的 内容如下:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
- nginx 配置文件有三部分组成
第一部分:全局块
从配置文件开始到 events 块之间的内容,主要会设置一些影响nginx 服务器整体运行的配置指令,主要包括配 置运行 Nginx 服务器的用户(组)、允许生成的 worker process 数,进程 PID 存放路径、日志存放路径和类型以 及配置文件的引入等。
比如上面第一行配置的:worker_processes 1;
这是 Nginx 服务器并发处理服务的关键配置,worker_processes 值越大,可以支持的并发处理量也越多,但是 会受到硬件、软件等设备的制约。
第二部分:events块
比如上面的配置:
events { worker_connections 1024; }
events 块涉及的指令**主要影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 work process 下的网络连接进行序列化,是否 允许同时接收多个网络连接,选取哪种事件驱动模型来处理连接请求,每个 word process 可以同时支持的最大连接数等。**
上述例子就表示每个 work process 支持的最大连接数为 1024.
这部分的配置对 Nginx 的性能影响较大,在实际中应该灵活配置。第三部分:
http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
这算是 Nginx 服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。
需要注意的是:http 块也可以包括 http全局块、server 块。
- http全局块
http全局块配置的指令包括文件引入、MIME-TYPE 定义、日志自定义、连接超时时间、单链接请求数上限等。 - server 块
这块和虚拟主机有密切关系,虚拟主机从用户角度看,和一台独立的硬件主机是完全一样的,该技术的产生是为了 节省互联网服务器硬件成本。
每个 http 块可以包括多个 server 块,而每个 server 块就相当于一个虚拟主机。
而每个 server 块也分为全局 server 块,以及可以同时包含多个 locaton 块。
- 全局 server 块
最常见的配置是本虚拟机主机的监听配置和本虚拟主机的名称或IP配置。 - location 块
一个 server 块可以配置多个 location 块。
这块的主要作用是基于 Nginx 服务器接收到的请求字符串(例如 server_name/uri-string),对虚拟主机名称 (也可以是IP 别名)之外的字符串(例如 前面的 /uri-string)进行匹配,对特定的请求进行处理。 地址定向、数据缓 存和应答控制等功能,还有许多第三方模块的配置也在这里进行。
四、 Nginx 反向代理 配置实例 1.1
1. 实现效果
- 打开浏览器,在浏览器地址栏输入地址 www.123.com,跳转到 liunx 系统 tomcat 主页 面中
2. 准备工作
(1)在 liunx 系统安装 tomcat,使用默认端口 8080,我这里8080被其他应用占用,所以我已修改端口为8081。在conf目录下的server.xml配置文件中,如下,将port改为 8081,其实下面也有类似的Connector 标签,但是要看protocol协议为HTTP/1.1的标签修改即可。
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
- tomcat 安装文件放到 liunx 系统中,解压。
Tomcat的路径:/usr/feng/apach-tomcat/tomcat8081下 - 进入 tomcat 的 bin 目录中,./startup.sh 启动 tomcat 服务器。
(2)对外开放访问的端口 (我这里不需要)
- firewall-cmd --add-port=8080/tcp --permanent
- firewall-cmd –reload
- 查看已经开放的端口号 firewall-cmd --list-all
(3)在 windows 系统中通过浏览器访问 tomcat 服务器
别忘了开启tomcat,在bin目录下,使用 命令:./startup.sh
3. 访问过程的分析
4、具体配置
a. 第一步 在 windows 系统的 host 文件进行域名和 ip 对应关系的配置
添加内容在 host 文件中
2. 第二步 在 nginx 进行请求转发的配置(反向代理配置)
5、最终测试
如上配置,我们监听 80 端口,访问域名为 www.123.com,不加端口号时默认为 80 端口,故 访问该域名时会跳转到 127.0.0.1:8081 路径上。在浏览器端输入 www.123.com 结果如下:
五、 Nginx 反向代理 配置实例 1.2
1. 实现效果
实现效果:使用 nginx 反向代理,根据访问的路径跳转到不同端口的服务中
nginx 监听端口为 9001,
访问 http://127.0.0.1:9001/edu/ 直接跳转到 127.0.0.1:8081
访问 http://127.0.0.1:9001/vod/ 直接跳转到 127.0.0.1:80822. 准备工作
a. 第一步,两个tomcat端口和测试页面
- 准备两个 tomcat,一个 8081 端口,一个 8082 端口。
在**/usr/feng/apach-tomcat/下 新建tomcat8081和tomcat8082两个文件夹,将 Tomcat安装包 分别上传到两个文件夹,进行解压缩安装,8081的Tomcat只改一个http协议默认端口号** 就行,直接启动即可。
这里需要改8082的端口号,需要修改三个端口,只修改一个端口号的话,是启动不了的,我已经测试过了(如果只修改http协议默认端口的话,8081和8082只会启动一个)。因为默认的都是8080(没有的直接创建文件夹,好多都是刚建的,与上面的第一个示例示例有点改动)
-
tomcat8081 解压包,然后进入到 /bin 下 ,使用命令 ./startup 启动
-
tomcat8082
使用命令 编辑 文件 :/conf/server.xml 文件
vim server.xml
修改后如下:
1、修改server 的默认端口,由默认8005->8091
2、修改http协议的默认端口,由默认的8080->8082
3、修改默认ajp协议的默认端口,由默认的8009->9001
- 并准备好测试的页面
写一个a.html页面,
tomcat8081的tomcat,放到目录 /webapp/vod 下,内容:
<h1>fengfanchen-nginx-8081!!!</h1>
tomcat8082的tomcat,放到目录 /webapp/edu 下,内容:
<h1>fengfanchen-nginx-8082!!!</h1>
- 测试页面
b. 第二步,修改 nginx 的配置文件
修改 nginx 的配置文件 在 http 块中添加 server{}
修改其中注释的就行。
修改成功后
- 开发的端口: nginx监听端口:8001,tomcat8081端口:8081,tomcat8082端口:8082。
- 测试结果
- location 指令说明
该指令用于匹配 URL。
语法如下:1、= :用于不含正则表达式的 uri 前,要求请求字符串与 uri 严格匹配,如果匹配 成功,就停止继续向下搜索并立即处理该请求。
2、~:用于表示 uri 包含正则表达式,并且区分大小写。
3、~*:用于表示 uri 包含正则表达式,并且不区分大小写。
4、^~:用于不含正则表达式的 uri 前,要求 Nginx 服务器找到标识 uri 和请求字 符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再使用 location 块中的正则 uri 和请求字符串做匹配。注意:如果 uri 包含正则表达式,则必须要有 ~ 或者 ~*标识。
六、 Nginx 负载均衡 配置实例 2
1. 实现效果
浏览器地址栏输入地址 http://208.208.128.122/edu/a.html,负载均衡效果,平均 8081 和 8082 端口中
2. 准备工作
a.准备两台 tomcat 服务器
- 准备两台 tomcat 服务器,一台 8081,一台 8082
- 上面的反向代理第二个实例中已经配置成功了。但是需要添加点东西,如下哦。
b. 修改一处
- 在两台 tomcat 里面 webapps 目录中,创建名称是 edu 文件夹,在 edu 文件夹中创建 页面 a.html,用于测试。
- 由于第二个实例中,8082中有了 edu 的文件夹,所以只在8081 文件夹下创建即可。
然后使用在vod文件下使用命令:
cp a.html ../edu/
即可完成,
查看命令cd ../edu/ # 进入到 edu 目录下 cat a.html #查看内容
c. 测试页面
测试URL
http://208.208.128.122:8081/edu/a.html
http://208.208.128.122:8082/edu/a.html
3. 在 nginx 的配置文件中进行负载均衡的配置
修改了第一个示例的 配置
upstream myserver { server 208.208.128.122:8081; server 208.208.128.122:8082; } server { listen 80; server_name 208.208.128.122; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; proxy_pass http://myserver; #proxy_pass http://127.0.0.1:8081; index index.html index.htm; }
4. 最终测试
测试url
http://208.208.128.122/edu/a.html
4. nginx 分配服务器策略
随着互联网信息的爆炸性增长,负载均衡(load balance)已经不再是一个很陌生的话题, 顾名思义,负载均衡即是将负载分摊到不同的服务单元,既保证服务的可用性,又保证响应 足够快,给用户很好的体验。快速增长的访问量和数据流量催生了各式各样的负载均衡产品, 很多专业的负载均衡硬件提供了很好的功能,但却价格不菲,这使得负载均衡软件大受欢迎, nginx 就是其中的一个,在 linux 下有 Nginx、LVS、Haproxy 等等服务可以提供负载均衡服 务,而且 Nginx 提供了几种分配方式(策略):
a. 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
配置方式:b. weight
weight 代表权重, 默认为 1,权重越高被分配的客户端越多
upstream myserver { server 208.208.128.122:8081 weight=10; # 在这儿 server 208.208.128.122:8082 weight=10; } server { listen 80; server_name 208.208.128.122; location / { root html; proxy_pass http://myserver; index index.html index.htm; }
c. ip_hash
ip_hash 每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器
upstream myserver { ip_hash; // 在这儿 server 208.208.128.122:8081 ; server 208.208.128.122:8082 ; } server { listen 80; server_name 208.208.128.122; location / { root html; proxy_pass http://myserver; index index.html index.htm; }
d. fair(第三方)
fair(第三方),按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream myserver { server 208.208.128.122:8081 ; server 208.208.128.122:8082 ; fair; # 在这儿 } server { listen 80; server_name 208.208.128.122; location / { root html; proxy_pass http://myserver; index index.html index.htm; }
七、 Nginx 动静分离 配置实例 3
1. 什么是动静分离
Nginx 动静分离简单来说就是把动态跟静态请求分开,不能理解成只是单纯的把动态页面和 静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开,可以理解成使用 Nginx 处理静态页面,Tomcat 处理动态页面。动静分离从目前实现角度来讲大致分为两种:-
一种是纯粹把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案;
-
另外一种方法就是动态跟静态文件混合在一起发布,通过 nginx 来分开。
通过 location 指定不同的后缀名实现不同的请求转发。通过 expires 参数设置,可以使 浏览器缓存过期时间,减少与服务器之前的请求和流量。具体 Expires 定义:是给一个资 源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可, 所以不会产生额外的流量。此种方法非常适合不经常变动的资源。(如果经常更新的文件, 不建议使用 Expires 来缓存),我这里设置 3d,表示在这 3 天之内访问这个 URL,发送 一个请求,比对服务器该文件最后更新时间没有变化,则不会从服务器抓取,返回状态码 304,如果有修改,则直接从服务器重新下载,返回状态码 200。
2. 准备工作
- 在Linux 系统中准备 静态资源,用于进行访问。
- www文件夹中 a.html
<h1>fengfanchen-test-html</h1>
- image 中的 01.jpg
我的照片哈!!!(自动忽略)
3. 具体配置
a. 在 nginx 配置文件中进行配置
4. 最终测试
a. 测试 image
http://208.208.128.122/image/ http://208.208.128.122/image/01.jpg
b. 测试 www
http://208.208.128.122/www/a.html
八、 Nginx 的高可用集群
1. 什么是nginx 高可用
配置示例流程:- 需要两台nginx 服务器
- 需要keepalived
- 需要虚拟IP
2. 配置高可用的准备工作
- 需要两台服务器 208.208.128.122 和 208.208.128.85
- 在两台服务器安装 nginx(流程最上面有)
第二台服务器的默认端口 改为 9001 ,运行并测试,如下:
- 在两台服务器安装 keepalived
2. 在两台服务器安装keepalived
a)安装:
第一种方式:命令安装
yum install keepalived -y # 查看版本: rpm -q -a keepalived
第二种方式:安装包方式(这里我使用这个)
将压缩包上传至:/usr/feng/
命令如下:cd /usr/feng/ tar -zxvf keepalived-2.0.18.tar.gz cd keepalived-2.0.18 ./configure make && make install
b) 配置文件
安装之后,在 etc 里面生成目录 keepalived,有文件 keepalived.conf 。
这个就是主配置文件。
主从模式主要在这个文件里配置。完成高可用配置(主从配置)
a) 修改 keepalived.conf 配置文件
修改/etc/keepalived/keepalivec.conf 配置文件
global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 208.208.128.122 smtp_connect_timeout 30 router_id LVS_DEVEL } vrrp_script chk_http_port { script "/usr/local/src/nginx_check.sh" interval 2 #(检测脚本执行的间隔) weight 2 } vrrp_instance VI_1 { state MASTER # 备份服务器上将 MASTER 改为 BACKUP interface ens192 //网卡 virtual_router_id 51 # 主、备机的 virtual_router_id 必须相同 priority 100 # 主、备机取不同的优先级,主机值较大,备份机值较小 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 208.208.128.50 // VRRP H 虚拟地址 } }
b) 添加检测脚本
在/usr/local/src 添加检测脚本
#!/bin/bash A=`ps -C nginx –no-header |wc -l` if [ $A -eq 0 ];then /usr/local/nginx/sbin/nginx sleep 2 if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then killall keepalived fi fi
c) 开启nginx 和 keepalived
把两台服务器上 nginx 和 keepalived 启动 :
启动 nginx:./nginx
启动 keepalived:systemctl start keepalived.service
85服务一样。4. 最终测试
a)在浏览器地址栏输入 虚拟 ip 地址 192.168.17.50
b)把主服务器(192.168.17.129)nginx 和 keepalived 停止,再输入 192.168.17.50
九、 Nginx 的原理
1. mater 和 worker
-
nginx 启动后,是由两个进程组成的。master(管理者)和worker(工作者)。
-
一个nginx 只有一个master。但可以有多个worker
-
,过来的请求由master管理,worker进行争抢式的方式去获取请求。
2. master-workers 的机制的好处
- 首先,对于每个 worker 进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销, 同时在编程以及问题查找时,也会方便很多。
- 可以使用 nginx –s reload 热部署,利用 nginx 进行热部署操作
- 其次,采用独立的进程,可以让互相之间不会 影响,一个进程退出后,其它进程还在工作,服务不会中断,master 进程则很快启动新的 worker 进程。当然,worker 进程的异常退出,肯定是程序有 bug 了,异常退出,会导致当 前 worker 上的所有请求失败,不过不会影响到所有请求,所以降低了风险。
3. 设置多少个 worker
Nginx 同 redis 类似都采用了 io 多路复用机制,每个 worker 都是一个独立的进程,但每个进 程里只有一个主线程,通过异步非阻塞的方式来处理请求, 即使是千上万个请求也不在话 下。每个 worker 的线程可以把一个 cpu 的性能发挥到极致。所以 worker 数和服务器的 cpu 数相等是最为适宜的。设少了会浪费 cpu,设多了会造成 cpu 频繁切换上下文带来的损耗。
- worker 数和服务器的 cpu 数相等是最为适宜
4. 连接数 worker_connection
第一个:发送请求,占用了 woker 的几个连接数?
- 答案:2 或者 4 个
第二个:nginx 有一个 master,有四个 woker,每个 woker 支持最大的连接数 1024,支持的 最大并发数是多少?
- 普通的静态访问最大并发数是: worker_connections * worker_processes /2,
- 而如果是 HTTP 作 为反向代理来说,最大并发数量应该是 worker_connections * worker_processes/4。
这个值是表示每个 worker 进程所能建立连接的最大值,所以,一个 nginx 能建立的最大连接 数,应该是 worker_connections * worker_processes。当然,这里说的是最大连接数,对于 HTTP 请 求 本 地 资 源 来 说 , 能 够 支 持 的 最 大 并 发 数 量 是 worker_connections * worker_processes,如果是支持 http1.1 的浏览器每次访问要占两个连接,所以普通的静态访 问最大并发数是: worker_connections * worker_processes /2,而如果是 HTTP 作 为反向代 理来说,最大并发数量应该是 worker_connections * worker_processes/4。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服 务的连接,会占用两个连接。
-
轻松学,Java 中的代理模式及动态代理
2017-06-29 22:08:55前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。...前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。
我们先来分析代理这个词。
代理
代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。
她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。
按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。
所以,代理就有一种中间人的味道。
接下来,我们说说软件中的代理模式。
代理模式
代理模式是面向对象编程中比较常见的设计模式。
这是常见代理模式常见的 UML 示意图。
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
- 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
- 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
- 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
如果难于理解的话,我用事例说明好了。值得注意的是,代理可以分为静态代理和动态代理两种。先从静态代理讲起。
静态代理
我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。
package com.frank.test; public interface Movie { void play(); }
然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。
package com.frank.test; public class RealMovie implements Movie { @Override public void play() { // TODO Auto-generated method stub System.out.println("您正在观看电影 《肖申克的救赎》"); } }
这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?
package com.frank.test; public class Cinema implements Movie { RealMovie movie; public Cinema(RealMovie movie) { super(); this.movie = movie; } @Override public void play() { guanggao(true); movie.play(); guanggao(false); } public void guanggao(boolean isStart){ if ( isStart ) { System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!"); } else { System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!"); } } }
Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。
package com.frank.test; public class ProxyTest { public static void main(String[] args) { RealMovie realmovie = new RealMovie(); Movie movie = new Cinema(realmovie); movie.play(); } }
然后观察结果:
电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊! 您正在观看电影 《肖申克的救赎》 电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。
动态代理
既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。
也许概念比较抽象。现在实例说明一下情况。
假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。
package com.frank.test; public interface SellWine { void mainJiu(); }
SellWine 是一个接口,你可以理解它为卖酒的许可证。
package com.frank.test; public class MaotaiJiu implements SellWine { @Override public void mainJiu() { // TODO Auto-generated method stub System.out.println("我卖得是茅台酒。"); } }
然后创建一个类 MaotaiJiu,对的,就是茅台酒的意思。
我们还需要一个柜台来卖酒:
package com.frank.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class GuitaiA implements InvocationHandler { private Object pingpai; public GuitaiA(Object pingpai) { this.pingpai = pingpai; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("销售开始 柜台是: "+this.getClass().getSimpleName()); method.invoke(pingpai, args); System.out.println("销售结束"); return null; } }
GuitaiA 实现了 InvocationHandler 这个类,这个类是什么意思呢?大家不要慌张,待会我会解释。
然后,我们就可以卖酒了。
package com.frank.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub MaotaiJiu maotaijiu = new MaotaiJiu(); InvocationHandler jingxiao1 = new GuitaiA(maotaijiu); SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao1); dynamicProxy.mainJiu(); } }
这里,我们又接触到了一个新的概念,没有关系,先别管,先看结果。
销售开始 柜台是: GuitaiA 我卖得是茅台酒。 销售结束
看到没有,我并没有像静态代理那样为 SellWine 接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。
动态代理语法
放轻松,下面我们开始讲解语法,语法非常简单。
动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。
Proxy
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
下面讲解它的 3 个参数意义。
- loader 自然是类加载器
- interfaces 代码要用来代理的接口
- h 一个 InvocationHandler 对象
初学者应该对于 InvocationHandler 很陌生,我马上就讲到这一块。
InvocationHandler
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
- proxy 代理对象
- method 代理对象调用的方法
- args 调用的方法中的参数
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
public class GuitaiA implements InvocationHandler { private Object pingpai; public GuitaiA(Object pingpai) { this.pingpai = pingpai; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("销售开始 柜台是: "+this.getClass().getSimpleName()); method.invoke(pingpai, args); System.out.println("销售结束"); return null; } }
GuitaiA 就是实际上卖酒的地方。
现在,我们加大难度,我们不仅要卖茅台酒,还想卖五粮液。
package com.frank.test; public class Wuliangye implements SellWine { @Override public void mainJiu() { // TODO Auto-generated method stub System.out.println("我卖得是五粮液。"); } }
Wuliangye 这个类也实现了 SellWine 这个接口,说明它也拥有卖酒的许可证,同样把它放到 GuitaiA 上售卖。
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub MaotaiJiu maotaijiu = new MaotaiJiu(); Wuliangye wu = new Wuliangye(); InvocationHandler jingxiao1 = new GuitaiA(maotaijiu); InvocationHandler jingxiao2 = new GuitaiA(wu); SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao1); SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao2); dynamicProxy.mainJiu(); dynamicProxy1.mainJiu(); } }
我们来看结果:
销售开始 柜台是: GuitaiA 我卖得是茅台酒。 销售结束 销售开始 柜台是: GuitaiA 我卖得是五粮液。 销售结束
有人会问,dynamicProxy 和 dynamicProxy1 什么区别没有?他们都是动态产生的代理,都是售货员,都拥有卖酒的技术证书。
我现在扩大商场的经营,除了卖酒之外,还要卖烟。
首先,同样要创建一个接口,作为卖烟的许可证。
package com.frank.test; public interface SellCigarette { void sell(); }
然后,卖什么烟呢?我是湖南人,那就芙蓉王好了。
public class Furongwang implements SellCigarette { @Override public void sell() { // TODO Auto-generated method stub System.out.println("售卖的是正宗的芙蓉王,可以扫描条形码查证。"); } }
然后再次测试验证:
package com.frank.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub MaotaiJiu maotaijiu = new MaotaiJiu(); Wuliangye wu = new Wuliangye(); Furongwang fu = new Furongwang(); InvocationHandler jingxiao1 = new GuitaiA(maotaijiu); InvocationHandler jingxiao2 = new GuitaiA(wu); InvocationHandler jingxiao3 = new GuitaiA(fu); SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao1); SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao2); dynamicProxy.mainJiu(); dynamicProxy1.mainJiu(); SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(), Furongwang.class.getInterfaces(), jingxiao3); dynamicProxy3.sell(); } }
然后,查看结果:
销售开始 柜台是: GuitaiA 我卖得是茅台酒。 销售结束 销售开始 柜台是: GuitaiA 我卖得是五粮液。 销售结束 销售开始 柜台是: GuitaiA 售卖的是正宗的芙蓉王,可以扫描条形码查证。 销售结束
结果符合预期。大家仔细观察一下代码,同样是通过 Proxy.newProxyInstance() 方法,却产生了 SellWine 和 SellCigarette 两种接口的实现类代理,这就是动态代理的魔力。
动态代理的秘密
一定有同学对于为什么 Proxy 能够动态产生不同接口类型的代理感兴趣,我的猜测是肯定通过传入进去的接口然后通过反射动态生成了一个接口实例。
比如 SellWine 是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有new SellWine();
这样相同作用的代码,不过它是通过反射机制创建的。那么事实是不是这样子呢?直接查看它们的源码好了。需要说明的是,我当前查看的源码是 1.8 版本。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
newProxyInstance 的确创建了一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 由 getProxyClass0() 方法获取。
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
直接通过缓存获取,如果获取不到,注释说会通过 ProxyClassFactory 生成。
/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // Proxy class 的前缀是 “$Proxy”, private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
这个类的注释说,通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:
// Proxy class 的前缀是 “$Proxy”, private static final String proxyClassNamePrefix = "$Proxy"; long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num;
所以,动态生成的代理类名称是包名+$Proxy+id序号。
生成的过程,核心代码如下:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
这两个方法,我没有继续追踪下去,defineClass0() 甚至是一个 native 方法。我们只要知道,动态创建代理这回事就好了。
现在我们还需要做一些验证,我要检测一下动态生成的代理类的名字是不是包名+$Proxy+id序号。
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub MaotaiJiu maotaijiu = new MaotaiJiu(); Wuliangye wu = new Wuliangye(); Furongwang fu = new Furongwang(); InvocationHandler jingxiao1 = new GuitaiA(maotaijiu); InvocationHandler jingxiao2 = new GuitaiA(wu); InvocationHandler jingxiao3 = new GuitaiA(fu); SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao1); SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(), MaotaiJiu.class.getInterfaces(), jingxiao2); dynamicProxy.mainJiu(); dynamicProxy1.mainJiu(); SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(), Furongwang.class.getInterfaces(), jingxiao3); dynamicProxy3.sell(); System.out.println("dynamicProxy class name:"+dynamicProxy.getClass().getName()); System.out.println("dynamicProxy1 class name:"+dynamicProxy1.getClass().getName()); System.out.println("dynamicProxy3 class name:"+dynamicProxy3.getClass().getName()); } }
结果如下:
销售开始 柜台是: GuitaiA 我卖得是茅台酒。 销售结束 销售开始 柜台是: GuitaiA 我卖得是五粮液。 销售结束 销售开始 柜台是: GuitaiA 售卖的是正宗的芙蓉王,可以扫描条形码查证。 销售结束 dynamicProxy class name:com.sun.proxy.$Proxy0 dynamicProxy1 class name:com.sun.proxy.$Proxy0 dynamicProxy3 class name:com.sun.proxy.$Proxy1
SellWine 接口的代理类名是:
com.sun.proxy.$Proxy0
SellCigarette 接口的代理类名是:com.sun.proxy.$Proxy1
这说明动态生成的 proxy class 与 Proxy 这个类同一个包。
下面用一张图让大家记住动态代理涉及到的角色。
红框中$Proxy0
就是通过 Proxy 动态生成的。$Proxy0
实现了要代理的接口。$Proxy0
通过调用InvocationHandler
来执行任务。代理的作用
可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?
主要作用,还是在不修改被代理对象的源码上,进行功能的增强。
这在 AOP 面向切面编程领域经常见。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。
同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?
这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。
至此,静态代理和动态代理者讲完了。
总结
- 代理分为静态代理和动态代理两种。
- 静态代理,代理类需要自己编写代码写成。
- 动态代理,代理类通过 Proxy.newInstance() 方法生成。
- 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
- 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
- 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
- 代理模式本质上的目的是为了增强现有代码的功能。
读者们都在下面的二维码所示的免费的知识星球问我问题:
-
nginx反向代理配置去除前缀
2019-01-07 00:13:28使用nginx做反向代理的时候,可以简单的直接把请求原封不动的转发给下一个服务。设置proxy_pass请求只会替换域名,如果要根据不同的url后缀来访问不同的服务,则需要通过如下方法: 方法一:加"/" server ... -
-
nginx反向代理--负载均衡
2018-10-19 17:39:37同时也是一个IMAP、POP3、SMTP代理服务器;nginx可以作为一个HTTP服务器进行网站的发布处理,另外nginx可以作为反向代理进行负载均衡的实现。 这里主要通过三个方面简单介绍nginx 反向代理 负载均衡 nginx特点 1... -
Java中动态代理的两种方式JDK动态代理和cglib动态代理以及区别
2019-05-31 13:59:19上篇介绍了一下静态代理:Java中的代理模式——静态代理以及分析静态代理的缺点 也分析了一下静态代理的缺点: 1、由于静态代理中的代理类是针对某一个类去做代理的,那么假设一个系统中有100个Service,则需要... -
java动态代理
2020-12-14 17:59:02最近在看一些技术源码的时候,发现很多地方都是动态代理, 真可谓是一切皆代理啊,所以我们需要弄明白代理模式这样在看源码的时候会好受很多。 2、基本概念 代理(Proxy)模式提供了间接访问目标对象的方式,即通过... -
【代理模式】Java的静态代理与动态代理
2020-12-21 15:42:21代理模式 代理模式给某一个对象提供一个代理对象,并由代理...创建一个接口,然后创建一个被代理的类实现接口中的抽象方法,再创建一个代理类,让代理类也实现这个接口,在代理类里创建一个被代理的类,在调用被代理 -
Nginx配置反向代理
2019-07-14 14:05:31Nginx配置反向代理,什么是反向代理 反向代理服务器决定哪台服务器提供服务。返回代理服务器不提供服务器。只是请求的转发。 -
Nginx(一款高性能的HTTP和反向代理web服务器)_01
2020-10-09 16:17:39正向代理和反向代理: 正向代理: 为了从目标服务器(Google)中取得内容,客户端向代理发送一个请求并指定目标(Google 服务器),然后代理向 Google 转交请求并将获得的内容返回给客户端。 反向代理: 对于用户... -
静态代理和动态代理
2019-10-08 11:53:04静态代理和动态代理的实现和讲解 -
nginx反向代理——将80端口请求转发到8080
2018-03-25 11:09:46先来理解一波概念,什么是nginx反向代理? 反向代理的意思是以代理服务器(这里也就是nginx)来接收网络上的请求,也就是url(默认是80端口), 1,nginx通过对url里面的一些判断(转达规则配置在nginx配置文件中)... -
Spring的静态代理与动态代理
2018-06-26 23:34:00代理即是将被代理对象进一步封装后,隐藏被代理对象,在不修改被代理对象代码的前提下完成一些额外的处理。 2.场景描述 有一个BookService类,其中有一个add方法,现在想在执行hello方法之前打印一句话,例如是... -
在线代理 网页代理 ip代理 在线代理ip 代理ip 网页代理ip ip在线代理
2019-10-05 23:45:03在线代理 网页代理 ip代理 在线代理ip 代理ip 网页代理ip ip在线代理 在线代理 网页代理 ip代理 在线代理ip 代理ip 网页代理ip ip在线代理 ... -
Java多线程02_Thread与静态代理
2020-08-27 12:22:16Java多线程02_Thread与静态代理 静态代理示例: 真实对象和代理对象都要 实现同一个接口 代理是对方法的 增强 public class StaticProxy { public static void main(String[] args) { WeddingCompany ... -
IP代理池检测代理可用性
2019-05-23 19:01:44在《基于Scrapy的IP代理池搭建》一文中,我们将从网页爬取到的免费代理IP按照如下格式保存到了Redis的 proxies:unchecked:list 队列中。 同时,为了避免同一代理IP被重复存储,在将代理保存到 proxies:unch... -
Python 快速验证代理IP是否有效
2020-04-12 20:16:47得到了一些代理IP但是不清楚到底是否可用,这个时候可以用Python来快速验证,携带该IP模拟访问某个网站,如果多次未请求成功则说明该IP是不可用的。 -
Java JDK 动态代理(AOP)使用及实现原理分析
2019-05-08 21:28:06代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。 代理模式UML图: 简单结构... -
Nginx反向代理——简单体验Nginx反向代理功能
2018-07-29 12:05:19相信大家在学习Nginx之前对反向代理和负载均衡就有所闻知,那么今天小编带领大家先来体验一下使用这个Nginx反向代理的感觉。 二、反向代理流程 话说这个Nginx反向代理+负载均衡难吗?实话告诉你们 ...
-
各种显示器色域测试软件和计算显示器色域值软件和表格
-
各种格式测试视频(.avi.wmv.mkv.mp4.mov.rm)
-
opencv3.3.1要的文件.rar
-
徒手写卷积神经网络
-
MySQL NDB Cluster 负载均衡和高可用集群
-
时间序列分析06
-
moveit-next:应用程序可构建NLW04 da ROCKETSEAT-源码
-
【硬核】一线Python程序员实战经验分享(1)
-
大数据分析关键技术.pptx
-
朱老师C++课程第3部分-3.6智能指针与STL查漏补缺
-
LeetCode-682. 棒球比赛
-
C++代码规范和Doxygen根据注释自动生成手册
-
MySQL 主从复制 Replication 详解(Linux 和 W
-
用微服务spring cloud架构打造物联网云平台
-
Neo4j安装和基础使用
-
2010-2011年品牌微博营销执行方案.ppt
-
Matlab强化学习机器人仿真
-
GoLand 快捷键
-
视频转换器WonderFox便携注册版 UI界面好看.rar
-
Amoeba 实现 MySQL 高可用、负载均衡和读写分离