java使用动态代理Proxy.newProxyInstance,模拟谷歌汽车的场景:(装饰着模式),(装饰着模式)弊端,类加载器的介绍,使用动态代理解决网站的字符集编码问题
设计模式:
什么是设计模式,简单的来说,就是在软件开发当中遇到的一些相似问题,将问题的解决方式抽取出的模型(开发套路 ),来解决软件开发当中遇到的问题。
单列,工厂,装饰着,适配器,动态代理
模拟谷歌汽车的场景:(装饰着模式)
1、java设计了汽车开发的约定
interface ICar{start run stop}
class GoogleCar implements ICar{}
2、希望在谷歌Car接入到生态圈的平台时,做一些增强汽车启动的功能。
3、用装饰着模式
(1)二次开发的时候,无法获取到原码,无法使用继承前提下,要对已经存在一些对象上的功能进行增强。
(2)前提:可以获取到被装饰的对象GoogleCar实现的所有接口。
(3)实现的思路:自己定义一个装饰类来实现ICar接口,为自定义的一个装饰类传递被装饰类的对象
假设是Google公司定义的接口
public interface ICar {
public void start();
public void run();
public void stop();
}
假设这是谷歌公司定义的类实现了上方的接口,并实现对应的方法
//相当于mysql的驱动包,是由谷歌汽车的开发人员来实现类
public final class GoogleCar implements ICar {
@Override
public void start() {
System.out.println("判断天气是否良好");
System.out.println("路况是否良好");
System.out.println("控制谷歌的汽车启动");
//调用谷歌汽车提供的C语言函数
}
@Override
public void run() {
System.out.println("控制谷歌的汽车运行");
}
@Override
public void stop() {
System.out.println("控制谷歌汽车停止");
}
}
一般上方的代码是不对外公开的所以无法直接通过new的方式来创建对象并调用其中的方法,并该类是通过final 修饰的所以也无法继承。
这个时候可以通过装着模式来对其类进行功能的增加,并丰富其中的方法
public class MyCar implements ICar{//实现其GoogleCar 类电实现的接口对应的方法
ICar car;
//虽然无法调用Google当中的方法也无法继承GoogleCar类,将GoogleCar通过参数的形式传递到对应的方法当中,将其GoogleCar的对象传递给GoogleCar的接口,通过接口来调用实现类当中的方法
public MyCar(ICar car) {
this.car=car;
}
@Override
public void start() {
// TODO Auto-generated method stub
System.out.println("检查天气是否良好");
System.out.println("检查路况是否拥堵");
car.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
car.run();
}
@Override
public void stop() {
// TODO Auto-generated method stub
car.start();
}
}
public class TestCar {
public static void main(String[] args) {
ICar car = new MyCar(new GoogleCar());
car.start();
car.run();
car.stop();
}
}
输出结果为:
检查天气是否良好
检查路况是否拥堵
判断天气是否良好
路况是否良好
控制谷歌的汽车启动
控制谷歌的汽车运行
判断天气是否良好
路况是否良好
控制谷歌的汽车启动
(装饰着模式)弊端:
如果被实现的方法过多,装饰类当中的方法过多,需要动态代理的方式解决装饰着模式的弊端
动态代理模式
原理:通过虚拟机在内存当中创建类似与MyCar.class文件。
要创建这个文件MyCar.class文件要告诉虚拟机:
1、被创建的字节码文件上应该有多少方法。
2、
利用Java的反射技术(JavaReflection),在运行时创建一个实现某些给定接口的新类(也称“动态代理类”)及其实例(对象),代理的是接口(Interfaces),不是类(Class),也不是抽象类。在运行时才知道具体的实现,spring aop就是此原理。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException;
newProxyInstance,方法有三个参数:
loader: 用哪个类加载器去加载代理对象:孤单值,告诉虚拟机字节码加载器用哪个字节码加载器加载内存当中创建出的字节码文件
interfaces:动态代理类需要实现的接口:告诉虚拟机内存当中正在被创建的字节码文件当中应该有哪些
h:获取接口当中的方法,并且动态代理方法在执行时,会调用h里面的invoke方法去
TestCar.class.getClassLoader()将下面获取到的字节码对应的接口加载到本类的字节码当中,
GoogleCar.class.getInterfaces(),告诉虚拟机创建并获取GoogleCar字节码文件当中的所有接口 ,并不能获取其特有的方法
InvocationHandler()从接口当中获取那个方法,如何获取方法,复制那个方法,并执行新增的方法和特性
最后
通过上方一系列操作创建了一个对象ICar car = (ICar)需要强转
测试类方便理解GoogleCar.class.getInterfaces();
获取到的是结构是什么
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
//获取GoogleCar googleCar.class字节码文件上所有的接口吗,googleCar上可能实现了多个接口
Class[] clazz = GoogleCar.class.getInterfaces();
for (Class cla : clazz) {
//获取到ICar.class对应的接口当中所有的方法
Method[] mds = cla.getMethods();
for (Method method : mds) {
System.out.println(method.getName());
}
}
}
}
运行结果
动态代理模式的实现
Proxy.newProxyInstance在内存当中创建字节码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TestCar {
public static void main(String[] args) {
//TestCar.class.getClassLoader()将下面获取到的字节码对应的接口加载到本类的字节码当中
//GoogleCar.class.getInterfaces(),告诉虚拟机创建并获取GoogleCar字节码文件当中的所有接口
//new InvocationHandler()从接口当中获取那个方法,如何获取接口对应方法,复制那个接口对应方法,并执行新增的方法和特性
//最后通过上方一系列操作创建了一个对象ICar car = (ICar)需要强转
ICar car = (ICar)Proxy.newProxyInstance(TestCar.class.getClassLoader(),
GoogleCar.class.getInterfaces(),
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//经过测试得知:method代表当前正在执行每个方法
//System.out.println(method.getName());//获取到是是字节码文件GoogleCar当中的所有方法名称
//执行当前的方法
//method.invoke(new GoogleCar(), args);
if(method.getName().equalsIgnoreCase("start")) {//equalsIgnoreCase忽略大小写比较
//执行当前method对应的方法
System.out.println("判断天气是否良好");
System.out.println("路况是否良好");
method.invoke(new GoogleCar(), args);
}else {
//获取接口当中的方法,放到正在执行的方法当中
method.invoke(new GoogleCar(), args);
}
return null;
}
});
car.start();
car.run();
car.stop();
}
}
//也可以实现该接口然后将MyCC传入到第三个参数当中
class MyCC implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
执行结果:
判断天气是否良好
路况是否良好
控制谷歌的汽车启动
控制谷歌的汽车运行
控制谷歌汽车停止
解析动态代理
TestCar.class.getClassLoader(),
GoogleCar.class.getInterfaces(),
new InvocationHandler()
上方代码生成TestCar$1.class字节码文件
TestCar$1.class生成上方代码当中的ICar对象的对象car
GoogleCar.class.getInterfaces():TestCar$1.class内放了GoogleCar的一堆方法
动态代理内的参数:
TestCar.class.getClassLoader():
字节码加载器:
jdk有一些程序,专业将各种字节码文件加载到内存当中,这里程序简称为字节码加载器。
如何将字节码文件class文件加载到内存当中?
底层实现的过程,利用IO流这种技术,获取到文件当中的数据,加载到内存当中。
字节码加载器:
TestCar.class.getClassLoader()将下面获取到的字节码对应的接口加载到本类的字节码当中。
GoogleCar.class.getInterfaces(),告诉虚拟机创建并获取GoogleCar字节码文件当中的所有接口 ,并不能获取其特有的方法。
InvocationHandler()从接口当中获取那个方法,如何获取方法,复制那个方法,并执行新增的方法和特性
Method:正在执行的方法
args:参数
改进上方代码验证各种参数的作用:
public interface ICar {
public String start(int a,int b);
public void run();
public void stop();
}
//相当于mysql的驱动包,是由谷歌汽车的开发人员来实现类
public class GoogleCar implements ICar {
@Override
public String start(int a,int b) {
System.out.println("控制谷歌的汽车启动");
//调用谷歌汽车提供的C语言函数
return "start...."+a+" "+b;
}
@Override
public void run() {
System.out.println("控制谷歌的汽车运行");
}
@Override
public void stop() {
System.out.println("控制谷歌汽车停止");
}
}
package com.itzheng.test06;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class TestCar {
public static void main(String[] args) {
// TestCar.class.getClassLoader()将下面获取到的字节码对应的接口加载到本类的字节码当中
// GoogleCar.class.getInterfaces(),告诉虚拟机创建并获取GoogleCar字节码文件当中的所有接口
// new InvocationHandler()从接口当中获取那个方法,如何获取方法,复制那个方法,并执行新增的方法和特性
// 最后通过上方一系列操作创建了一个对象ICar car = (ICar)需要强转
ICar car = (ICar) Proxy.newProxyInstance(TestCar.class.getClassLoader(), GoogleCar.class.getInterfaces(),
new InvocationHandler() {
//method代表正在执行的方法
//args:代表正在执行的方法的参数
//Object:代表方法执行完毕之后的返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 经过测试得知:method代表当前正在执行每个方法
// System.out.println(method.getName());//获取到是是字节码文件GoogleCar当中的所有方法名称
// 执行当前的方法
// method.invoke(new GoogleCar(), args);
// 代表每个方法执行完毕之后返回的对象
Object obj = null;
if (method.getName().equalsIgnoreCase("start")) {// equalsIgnoreCase忽略大小写比较
// 执行当前method对应的方法
System.out.println("判断天气是否良好");
// 打印args里面的内容
System.out.println(Arrays.toString(args));
System.out.println("路况是否良好");
obj = method.invoke(new GoogleCar(), args);
} else {
// 获取接口当中的方法
obj = method.invoke(new GoogleCar(), args);
}
return obj;
}
});
String cc = car.start(1, 4);
System.out.println(cc);
car.run();
car.stop();
}
}
//也可以实现该接口然后将MyCC传入到第三个参数当中
class MyCC implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
执行结果
判断天气是否良好
[1, 4]
路况是否良好
控制谷歌的汽车启动
start…1 4
控制谷歌的汽车运行
控制谷歌汽车停止
字节码加载器:3种
1、系统引导加载器:.class.getClassLoader() java当中不存在由其他语言来实现的
public class TestClassLoader {
public static void main(String[] args) {
//系统引导的类加载器
//获取String类的加载器
ClassLoader classLoader = String.class.getClassLoader();//String类的字节码加载器
System.out.println(classLoader);
//由于String.class,int.class等字节码文件需要频繁的被加载到内存速度必须块,底层用其他语言来实现
}
}
2、ExtClassLoader扩展类加载器
public class TestClassLoader {
public static void main(String[] args) {
//获取ext(extendtion)包下的某个类下的字节码加载器
//ExtClassLoader扩展类加载器
ClassLoader classLoader2 = sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader();
System.out.println(classLoader2);
}
}
输出结果:
3、应用类加载器 AppClassLoader应用类内存当中实际存在的类
应用类:一般实现的所有类都是属于应用类(自己创建的类)
public class TestClassLoader {
public static void main(String[] args) {
//应用类:一般实现的所有类都是属于应用类(自己创建的类)
//获取应用类加载器 AppClassLoader
ClassLoader classLoader3 = TestClassLoader.class.getClassLoader();
System.out.println(classLoader3);
}
}
解决网站动态代理编码的问题
案例:动态代理解决全站乱码问题
步骤:
1、new DynamicWeb Progect > index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>post方式提交的中文</h1>
<form action="/day18_v3/ServletDemo" method="post">
User:<input type="" name="username"><br> <input
type="submit" />
</form>
<h1>get方式提交的中文</h1>
<form action="/day18_v3/ServletDemo" method="get">
User:<input type="" name="username"><br> <input
type="submit" />
</form>
</body>
</html>
2、建立ServletDemo
public class ServletDemo extends HttpServlet {
private static final long serialVersionUID = 1L;
public ServletDemo() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String um = request.getParameter("username");
System.out.println(request.hashCode()+" :"+um);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String um = request.getParameter("username");
System.out.println(request.hashCode()+" :"+um);
}
}
无论是在post/get方法,执行以下语句不存在中文乱码问题
String um = request.getParameter("username");
System.out.println(um);
3、在过滤器当中为request上的getParameter();进行增强
思路:
判断当前的请求是get/post request.getMethod();
request.getMethod();返回的如果是post,设置一句话:request.setCharacterEncoding("utf-8");
//放行
如果是get,调用原先的String v = request.getParameter(name);
将v进行转码,放行
案例的实现
next > /EncodingFilter > /*
package cn.itcast.test00;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class EncodingFilter implements Filter {
public EncodingFilter() {
}
public void destroy() {
}
public void init(FilterConfig fConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//将request转换为HttpServletRequest
final HttpServletRequest req = (HttpServletRequest)request;
//让jdk在内存当中生成一个代理对象:增强了req对象上的getParmeter(name);API
HttpServletRequest myReq = (HttpServletRequest)Proxy.newProxyInstance(EncodingFilter.class.getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
Object obj = null;
if(method.getName().equalsIgnoreCase("getParameter")) {
//获取本次请求的方式,并对其进行转码为utf-8
String md = req.getMethod();
if("get".equalsIgnoreCase(md)) {
//get方式的请求
//调用req对象上的getParameter方法
String v = (String)method.invoke(req, args);
//转码
String vv = new String(v.getBytes("iso-8859-1"),"utf-8");
return vv;
}else {
//POST方式的请求
req.setCharacterEncoding("UTF-8");
obj = method.invoke(req, args);
}
}else {
obj = method.invoke(req, args);
}
return obj;
}
});
//打印代理对象的Hash码
System.out.println(myReq.hashCode());
//将代理的对象放行
chain.doFilter(myReq, response);//放行
}
}