类加载器和动态代理


类加载器:把class文件加载到内存形成字节码,其本身也是类
系统默认三个类加载器: BootStrap(最高层由c++实现,不需要类加载器,JRE/lib/rt.jar)
     ExtClassLoader(JRE/lib/ext/*.jar)
    AppClassLoader(classpath指定所有jar或目录)
    MyClassLoader(自己定义类加载器)
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载
<span style="white-space:pre">	</span>//打印AppClassLoader
	System.out.println(Demo.class.getClassLoader().getClass().getName());
	//打印ExtClassLoader
	System.out.println(Demo.class.getClassLoader().getParent().getClass().getName());
	//打印null,实际就是BootStrap,第一个类加载器不是java类,由c++完成
	System.out.println(Demo.class.getClassLoader().getParent().getClass().getParent());
委托机制
类加载器采用委托机制,即先委托自己的上一级加载,上一级也委托他的上一级,第一层加载失败再交给下一级,直至返回自身,如果自身加载失败则停止加载并抛出ClassNotFoundException
<span style="white-space:pre">		</span>在eclipse中的某个Java文件上右键-->export-->打包为xxx.jar文件放到jre/lib/ext目录下
		Demo.class.getClassLoader().getClass().getName()//打印ExtClassLoader
		//证明了委托机制是先由父类加载器加载
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1、首先当前线程的类加载器去加载线程中的第一个类
2、如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B
3、还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
编写自己的类加载器(模板设计方法)
1、自定义的类加载器的必须继承ClassLoader
2、loadClass方法与findClass方法:loadClass即确定的功能,先调用父类的loadclass方法,在使用子类复写的的findclass方法
3、defineClass方法:将一个 byte 数组转换为 Class 类的实例
	import java.io.*;
	import java.util.Date;
	public class ClassLoaderDemo{
	public static void main(String[] args)throws Exception{
		Class clazz = new MyClassLoader().loadClass("Demo");//通过自定义的类加载器返回class对象		
		//这种方法是错误的,因为这里使用到Demo,类加载器AppClassLoader就会去加载这个类
		//而我们是通过自己的方式来加载这个类的
		//Demo demo = (Demo)clazz.newInstance();
		Date date = (Date)clazz.newInstance();
		System.out.println(date);
		System.out.println(new Demo());
	}

        class MyClassLoader extends ClassLoader{ //自定义类加载器
	FileInputStream  fis ;
	ByteArrayOutputStream bos ;
	byte[] outbyte;
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException{//只需实现findClass方法
		
		String filename = "E:\\new\\"+name+".class";//实际就是MyclassLoader管理的目录
		try{
			fis = new FileInputStream(filename);
			bos = new ByteArrayOutputStream();//字节数组输出流
			int readbyte = 0;
			while((readbyte=fis.read())!=-1){
				readbyte = readbyte^0xff//对读取的数据操作(解密),自定义类加载器的目的
				bos.write(readbyte);
			}
			outbyte = bos.toByteArray() ;
			fis.close();
			bos.close();
		}catch(Exception e){}
		System.out.println("MyClassLoader");
		return defineClass(name,outbyte,0,outbyte.length);//调用父类的defineClass方法,返回一个class
	}
}

class Demo extends Date{//定义用于被记载的类,继承Date
	public String toString(){
		return "Demo";
	}
}
重要总结:因为类加载器的委托机制,父加载器会先搜索自己管辖目录下的.class文件,同时子类加载器并没有覆盖loadClass方法,所以想使用自己编写的类加载器,就必须把父类加载器目录下的class文件删除(AppClassLoader管理的classpath和ExtClassLoader管理的(JRE/lib/ext)
tomcat使用自己的类加载器加载class
1、编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
2、把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。ExtclassLoader加载MyServlet.class,同时MyServlet.class中用到了HttpServlet.class。虽然tomcat自定义的类加载器可以加载HttpServlet.class,由于委托机制,父类级加载器找不到并不交给子类,也不知道该交给哪个子类(前提为不是有子类级加载器发起的),直接报错
3、把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader
第一种情况:WebAppClassLoader加载MyServlet.class,MyServlet.class使用了HttpServlet.class,
同样由WebAppClassLoader加载HttpServlet.class
第二种情况:ExtclassLoader加载了Servlet.class,同样也由ExtclassLoader加载HttpServlet.class
================================ 代理=======================================
代理架构与AOP
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码
 交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,不同对象的不同方法中需要添加相同的功能,对这个相同功能的实现就是切面编程
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术

动态代理技术
(1)JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
(2)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
(3)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析JVM动态生成的类
	//动态代理类,继承Collection接口,类加载器使用和接口一样的
	Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
	System.out.println(clazzProxy.getName());//打印proxy$0
	//打印构造方法列表
	Constructor[] constructors = clazzProxy.getConstructors();//得到所有的构造方法
	for(Constructor constructor:constructors){
		String name = constructor.getName();
		StringBuilder sb = new StringBuilder(name);
		sb.append('(');
		Class[] clazzParams = constructor.getParameterTypes();//得到方法中的参数
		for(Class clazzParam:clazzParams){
			sb.append(clazzParam.getName());
			sb.append(',');
		}
		if(clazzParams!=null&&clazzParam.length==0){
			sb.deleteCharAt(sb.length()-1);
		sb.append(')');
	}
		System.out.println(sb);
	·	//打印方法列表
		Method[] methods = clazzProxy.getMethods();
		for(Method method:methods){
		String name = method.getName();
		StringBuilder sb = new StringBuilder(name);
		sb.append('(');
		Class[] clazzParams = method.getParameterTypes();
		for(Class clazzParam:clazzParams){
			sb.append(clazzParam.getName());
			sb.append(',');
		}
		if(clazzParams!=null&&clazzParam.length==0){
			sb.deleteCharAt(sb.length()-1);
		sb.append(')');
		System.out.println(sb);
	}
<span style="white-space: pre; ">	</span>结果:动态类只有一个构造方法:$Proxy0(java.lang.reflect.InvocationHandler)
	      方法列表为:从接口继承的方法和从Object类继承的方法<strong>
</strong>
创建动态类的实例对象
三个条件:(1)该动态类实现的接口(2)传递类加载器(3)InvocationHandler对象
1、通过反射得到构造函数,创建对象
	Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
	class myInvocationHandler1 implements InvocationHandler{//实现InvocationHandler接口的类
		public Object invoke(Object proxy,Method method,Object[] args){//实现接口的方法
			return null;
		}
	}
	//参数为自定义类的对象,创建的对象为其继承接口的类型
	Collection proxy1 = (Collection)constructor.newInstance(new myInvocationHandler1());
	proxy.clear();//Collection的方法
2、通过匿名内部类创建对象
	Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
		public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
			return null;
		}
	});
3、通过 Proxy的静态方法newProxyInstance
	Collection proxy3 = (Collection) Proxy.newProxyInstance(
		Collection.class.getClassLoader(),
		new Class[]{Collection.class},//实现接口的数组(可变参数必须是最后一个)
		new InvocationHandler(){
			public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
				return null;
			}
		});
分析动态生成的类的内部代码
动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和接受InvocationHandler参数的构造方法
	1.构造方法接受一个InvocationHandler对象:
		class proxy$0{
			InvocationHandler handler;
			proxy$0(InvocationHandler handler){
				this.handler = handler;	   //接受此对象,内部使用handler.invoke()方法
			}
		}
	2.实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?
	Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、 add方法、   “abc”参数
			Class Proxy$ {				|		|	      |				
				add(Object object) {		|		|	      |
					return handler.invoke(Object proxy, Method method, Object[] args);
				}
			}
	3.动态代理类中size方法的实现原理:
		int size(){
			return handler.invoke(this,this.getClass.getMethod("size"),null)
		}
为什么动态类的实例对象的getClass()方法返回了正确结果(Proxy$0)而没有调用invoke()返回ArrayList呢?
调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
		Collection proxy3 = (Collection) Proxy.newProxyInstance(
			Collection.class.getClassLoader(),
			new Class[]{Collection.class},
			new InvocationHandler(){
			ArrayList target = new ArrayList();//外部定义被代理的目标类
				public Object invoke(Object proxy,Method method, Object[] args)throws Throwable{
					//ArrayList target = new ArrayList();//在这定义,每次定义都产生一个target
					long btime=System.curentTimeMillis();
					//实际调用目标类的方法,知道目标类有此方法是因为他们实现了相同的接口
					Object value = method.invoke(target,args);
					long etime=System.curentTimeMillis();
					System.out.println(""+(etime-btime))
					return value;
				}
			});
		proxy3.add("xxx");//new InvocationHandler().invoke(proxy3,add,"xxx");
		proxy3.add("yyy");
		proxy3.size();
让动态生成的类成为目标类的代理
1、将目标类作为参数传递:
为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量
2、将系统功能代码模块化,即将切面代码也改为通过参数形式提供:
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码
3、eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象
<span style="white-space:pre">	</span>class ProxyTest{
		public Object getProxy(final Object target,final Advice advice){//Advice advice参数调用即实现功能接口
			Object proxy = Proxy.newProxyInstance(
			target.getClass().getClassLoader(),//目标类的类加载器
			target.getClass().getInterfaces(),//目标类实现的接口
			new InvocationHandler(){
				public Object invoke(Object proxy,Method method, Object[] args)throws Throwable{
					advice.beforeMethod(method);//调用功能
					Object value = method.invoke(target,args);//调用目标类的方法
					advice.afterMethod(method);
					return value;
				}
			});
			return proxy;
		}
	}
	public interface Advice{ //定义一个通行契约,想实现系统的功能,必须实现此接口
		void beforeMethod(Method method);
		void afterMethod(Method method);
	}
	class MyAdvice implements Advice{//此类实现了Advice接口,实现了具体的方法
		long btime,etime;
		public void beforeMethod(Method method){//实现方法
			btime = System.curentTimeMillis();
			System.out.println(method.getName+btime);
		}
		public void afterMethod(Method method){
			etime = System.curentTimeMillis();
			System.out.println(method.getName+etime);
		}
	}
	proxy = getProxy(new ArrayList(),new MyAdvice());//生成动态代理类,参数:目标类,功能类
	proxy.add("xxx");


基于SSM框架的智能家政保洁预约系统,是一个旨在提高家政保洁服务预约效率和管理水平的平台。该系统通过集成现代信息技术,为家政公司、家政服务人员和消费者提供了一个便捷的在线预约和管理系统。 系统的主要功能包括: 1. **用户管理**:允许消费者注册、登录,并管理他们的个人资料和预约历史。 2. **家政人员管理**:家政服务人员可以注册并更新自己的个人信息、服务类别和服务时间。 3. **服务预约**:消费者可以浏览不同的家政服务选项,选择合适的服务人员,并在线预约服务。 4. **订单管理**:系统支持订单的创建、跟踪和管理,包括订单的确认、完成和评价。 5. **评价系统**:消费者可以在家政服务完成后对服务进行评价,帮助提高服务质量和透明度。 6. **后台管理**:管理员可以管理用户、家政人员信息、服务类别、预约订单以及处理用户反馈。 系统采用Java语言开发,使用MySQL数据库进行数据存储,通过B/S架构实现用户与服务的在线交互。系统设计考虑了不同用户角色的需求,包括管理员、家政服务人员和普通用户,每个角色都有相应的权限和功能。此外,系统还采用了软件组件化、精化体系结构、分离逻辑和数据等方法,以便于未来的系统升级和维护。 智能家政保洁预约系统通过提供一个集中的平台,不仅方便了消费者的预约和管理,也为家政服务人员提供了一个展示和推广自己服务的机会。同时,系统的后台管理功能为家政公司提供了强大的数据支持和决策辅助,有助于提高服务质量和管理效率。该系统的设计与实现,标志着家政保洁服务向现代化和网络化的转型,为管理决策和控制提供保障,是行业发展中的重要里程碑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值