48、类加载器的一个高级问题的实验分析:
这次我们新建一个web项目然后新建一个servlet,在servet的doGet方法中我们循环的遍历出所有的类加载
器分别为:
WebappClassLoader
StandardClassLoader
AppClassLoader
ExtClassLoader
这样我们可以正常的访问该Servlet, 然后我们把这个servlet打成.Jar包放到jre/lib/ext/下面去让
ExtClassLoader去加载他,然后当我们再次的访问这个程序的时候就报错了:
原因:当ExtClassLoader加载改程序的时候,会首先让他的父亲去加载,由于父亲没有找到,所以就又交给他来加载
当他加载这个servlet的时候,他发现这个类extends HttpServlet所以就又去加载他,因为找不到所以就报错了,因为这个
jar包是由tomcat提供的,把tomcat lib中的servlet-api.jar页拷贝到ext目录下面就可以解决这个问题了。
web程序下类所使用的不同的类加载器
49、分析代理类的作用与原理及AOP概念
(1)AOP的概念:
(2)动态代理:
50、创建动态类及查看其方法列表信息:
示例代码:
public static void main(String[] args) {
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
String clazzName = clazz.getName();
//打印所以的构造方法以及参数
Constructor[] constructs = clazz.getConstructors();
for(Constructor constructor : constructs) {
String constructName = constructor.getName();
StringBuilder sBuilder = new StringBuilder(constructName);
sBuilder.append('(');
Class[] clazzTypes = constructor.getParameterTypes();
for(Class clazzType : clazzTypes) {
sBuilder.append(clazzType.getName()).append(',');
}
if(clazzTypes != null && clazzTypes.length >0) {
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
//打印这个接口所拥有的所有的方法以及他们的参数
Method[] clazzMethods = clazz.getMethods();
for(Method clazzMethod : clazzMethods) {
String methodName = clazzMethod.getName();
StringBuilder sBilder = new StringBuilder(methodName);
Class[] methodTypes = clazzMethod.getParameterTypes();
sBilder.append('(');
for(Class methodType : methodTypes) {
sBilder.append(methodType.getName()).append(',');
}
if(methodTypes != null && methodTypes.length >0) {
sBilder.deleteCharAt(sBilder.length() - 1);
}
sBilder.append(')');
System.out.println(sBilder);
}
}
结果:
$Proxy0(java.lang.reflect.InvocationHandler)
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
1、细节注意:在上述例子中,Proxy的静态方法getProxyClass的第二个参数需要传递一个interfaces的Class的数组。而在上述程序中应该写成Collection.class.getInterfaces(),为什么写成Collection.class也行呢? 那是因为Collection本身就是一个接口,如果Collection不是一个接口而是一个实现了数组的类,则必须写成Collection.class.getInterfaces(),否则程序将报错。
2、生成的代理对象是没有参数为空的构造方法的,只有一个参数为InvocationHandler的构造方法,所以不能直接调用newInstance()方法直接创建一个实例,因为该方法会调用相关类的无参的构造方法。所以只能得到参数为InvocationHandler的构造方法,然后传递一个实现了InvocationHandler接口的类来创建一个对象,从而引出我们下面要讲解的动态代理内容。
51、创建动态类的实例对象及调用其方法
在上面类的基础上添加如下代码:
//无法用下面的方法创建对象,因为newInstance()默认调用的是无参的构造方法,而我们上面获得的是Proxy类的一份字节码,而这个类
//没有无参的构造方法,必须取得他的构造方法来构造对象
//Object object = clazz.newInstance();
//根据你传递的参数类型,返回相应的构造方法
Constructor construct = clazz.getConstructor(InvocationHandler.class);
class myInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
//要返回代理对象,必须要传递一个实现了InvocationHandler接口的类的一个对象
Collection proxy1 = (Collection)construct.newInstance(new myInvocationHandler());
//可以
proxy1.clear();
//报错
proxy1.size();
打印proxy1的结果是null,那并不代表proxy1就是null 是他的toString()方法是null,如果你调用它的clear()方法,不会报
NullPointException,但是返回的这个代理对象为什么只能调用没有返回值的方法,而如果调用有返回值的方法就会报空指针异常呢
因为每当你调用代理类的一个方法的时候,他就会去调用InvocationHandler中的Invoke方法,size的返回值是int,而return的是null
52、完成InvocationHandler对象的内部功能
我们可以把51中创建代理对象的二个步骤合成一个 (用到内部类)
Collection proxy2 = (Collection)construct.newInstance(new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
在51 中我们创建一个代理对象是 得到字节码------>得到构造方法----->创建对象。我们能不能一步到位?这要利用Proxy
类提供的另一个静态方法:newProxyInstance
Collection proxy3 = (Collection)Proxy.newProxyInstance(
//类加载器
Collection.class.getClassLoader(),
//传入接口可能有多个,所以是数组
new Class[]{Collection.class},
new InvocationHandler() {
//target就是我们要创建代理对象的那个真实的对象。
ArrayList target = new ArrayList();
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法运行时间为" + (beginTime -endTime));
return retVal;
}
}
);
proxy3.add("abc");
proxy3.add("cde");
System.out.println(proxy3.size());
总结:当调用代理对象的方法的时候,其实都会去调用InvocationHandler类的method.invoke()方法。invoke方法中有两个参数:(1)要调用的这个方法所属的对象。(2)这个方法所用到的参数。
例子中:传递的对象是target --new的一个真实ArrayLIst的对象,所以在程序后调用proxy2.add方法时,调用的是target的add方法。(每当调用一个代理对象的xx方法的时候,程序会自动将调用过程转交给InvocationHandler的invoke方法)按照这个规律:如果第一个参数传proxy3时,将会出现程序的死循环。
在上面的代码中我把AraryList target = new ArrayList()放到了Invoke方法前面,也就是成员变量,这个时候,
你用代理对象的时候操作的是同一个对象,所以上面的size()方法打印结果是2,但是如果你把target对象放到invoke方法
的内部,也就是局部变量的时候,在操作代理对象的时候,每调用代理对象的一个方法其实操作的是完全不同的对象
所以这个时候的结果是0
53、分析InvocationHandler对象的运行原理:
InvocationHandler原理分析
我们说如果我们调用代理类的一个方法,他会交给InvocationHandler的invoke方法去执行 返回的结果也是目标对象调用方法后的返回结果,那对于代理对象:
System.out.println(proxy3.getClass().getName()); 按照我们上面的理论,调用代理对象方法的时候会返回真实对象的返回结果,那应该是java.util.ArrayList
为什么会是$proxy0呢?
答案:调用代理对象的从Object继承的hashCode() equals() toString()这几个方法的时候才会把调用请求转发给InvocationHandler对象,而对于其他的方法
有自己的实现,所以getClass().getName()返回的是$proxy0
54、总结分析动态代理类的设计原理与结构
动态代理的工作原理图
55、编写可生成代理和插入通告的通用方法
模拟spring,将动态代理中的“切面”问题封装到一个类中,而不要硬编码到动态代理类中,动态代理类中的target
要改成Object,以便让他更有通用性
(1)切面问题接口
public interface Advice {
public void beforeMethod(Method method);
public void afterMethod(Method method);
}
(2)切面问题实现
public class MyAdvice implements Advice {
long beginTime;
public void afterMethod(Method method) {
System.out.println("方法执行之后:");
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法运行时间为" + (beginTime - endTime));
}
public void beforeMethod(Method method) {
System.out.println("方法执行之前:");
beginTime = System.currentTimeMillis();
}
}
(3)动态代理方法的封装
private static Object getProxy(final Object target, final 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 retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy;
}
(4)测试运行
ArrayList target = new ArrayList();
Collection proxy = (Collection)getProxy(target, new MyAdvice());
proxy.add("abc");
56、实现类似Spring可配置的Aop框架:
模拟目标:根据我传递的方法的名称来确认返回的是真实的一个对象,还是一个代理对象,
如果我传递的这个类是个ProxyFactory类型的一个类(instanceof)则返回该类的一个代理对象
否则直接返回所指定类的一个真实的对象。
(1)BeanFactory
public class BeanFactory {
static Properties props = new Properties();
public BeanFactory(InputStream ips) {
try {
props.load(ips);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object getBean(String name) {
Object bean = null;
try {
//得到传递对象的字节码
Class clazz = Class.forName(props.getProperty(name));
//创建一个实例对象
bean = clazz.newInstance();
//如果创建的这个对象是一个ProxyFactoryBean类型的,就返回代理对象,否则返回真实的对象
} catch (Exception e) {
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean) {
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
Object proxy = null;
try {
//得到目标类的对象
Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
//得到解决“切面问题”类的对象
Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
proxy = proxyFactoryBean.getProxy();
} catch (Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}
//将上述两个对象设置到代理对象中
//返回代理对象
return proxy;
}
//返回真实的对象
return bean;
}
}
(2)ProxyFactoryBean
public class ProxyFactoryBean {
private Object target;
private Advice advice;
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getProxy() {
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 retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy;
}
}
(3)所用到的配置文件:config.properties 注意:要与AopTest测试类位于同一个包下面(xxx.target所指定的类要与返回真实类对象所指定的类一样,否则就不一致了)
#xxx=java.util.ArrayList
xxx=cn.itcast.aop.framework.ProxyFactoryBean
xxx.advice=cn.itcast.day2.MyAdvice
xxx.target=java.util.ArrayList
(4)AopTest测试类
public class AopTest {
public static void main(String[] args) {
InputStream ips = AopTest.class.getResourceAsStream("config.properties");
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}