:具有一个或多个类型变量的类,称之为泛型类!
class A<T> {
}
A<String> a = new A<String>();
* 如果创建实例时,不给类型变量赋值,那么会有一个警告!
:具有一个或多个类型变量的方法,称之为泛型方法!
class A<T> {
public T fun(T t1) {}
}
fun()方法不是泛型方法!它是泛型类中的一个方法!
public <T> T fun(T t1) {} --> 它是泛型方法
* 泛型方法与泛型类没什么关系,泛型方法不一定非要在泛型类中!
* 泛型类中使用泛型:
> 成员类型
> 返回值和参数类型
> 局部变量的引用上(左边)
class A<T> {
private T bean;//泛型可在成员变量上使用
public T fun(T t) {}//泛型可以在类中的方法上(返回值和参数类型)使用!
public void fun2() {//泛型还可以在局部变量的引用类型上使用
Tb = ...
new T();//不行的!
}
}
=========================================
*继承泛型类需要为父类的泛型变量赋值!就好比创建泛型类的对象时需要给泛型变量赋值一样。
class A<T> {}
class AA extends A<String> {} //AA子类不是泛型类,只是它爸爸是泛型类!
* 子类不是泛型类:需要给父类传递类型常量
> 当给父类传递的类型常量为String时,那么在父类中所有T都会被String替换!
* 子类是泛型类:可以给父类传递类型常量,也可以传递类型变量
class AA1 extends A<Integer> {}
class AA3<E> extends A<E> {}
=========================================
=========================================
=========================================
方法的形参!
通配符只能出现在引用的定义中,而不能出现在创建对象中。
例:new ArrayList<?>()//不行
ArrayList<?> list = null//这是可以的
使方法更加通用!
无界通配:?
子类限定:? extends Object
父类限定:? super Integer
使变量使用上不再方便
无界:参数和返回值为泛型的方法,不能使用!
子类(下边界):参数为泛型的方法不能使用,可以使用返回值为泛型变量的方法
父类(上边界):返回值为泛型的方法不能使用.可以使用参数为泛型变量的方法
boolean addAll(Collection<E> c) List<Number> numList = newArrayList<Number>(); List<Integer> intList = newArrayList<Integer>(); numList.addAll(intList);//addAll(Collection<Number>c), //传递的是List<Integer> boolean addAll(Collection<? extends E> c) List<Number> numList = newArrayList<Number>(); List<Integer> intList = newArrayList<Integer>(); numList.addAll(intList);//addAll(Collection<?extends Number> c), //传递的是List<Integer>
语法:@注解名称
注解的作用:替代xml配置文件!
servlet3.0中,就可以不再使用web.xml文件,而是所有配置都使用注解!
注解是由框架来读取使用的!
* 定义注解类:框架的工作
* 使用注解:我们的工作
* 读取注解(反射):框架的工作
*java中的注解
@Overrid:作用在方法上的注解。当方法不是重写父类的方法时会报错
@Deprecated:作用在方法上。标记该方法为作废方法(已过时)
@SuppressWarnings:作用在方法上,压制警告。
class A {}
interface A{}
enum A{}
@interface A{}//天下所有的注解都是Annotation的子类!
注解的作用目标:
*类
*方法
*构造器
*参数
*局部变量
*包
> 格式:
@interface MyAnno1 {
int age();
String name();
}
> @MyAnno1(age=100, name="zhangSan")
在定义注解时,可以给注解指定默认值!
> int age() default 100;
> 在使用注解时,可以不给带有默认值的属性赋值!
> 当使用注解时,如果只给名为value的属性赋值时,可以省略“value=”,
例如:@MyAnno1(value="hello"),可以书写成 @MyAnno1("hello")
> 8种基本类型
> String
> Enum
> Class
> 注解类型
> 以上类型的一维数组类型
当给数组类型的属性赋值时,若数组元素的个数为1时,可以省略大括号
@MyAnno1( a=100, b="hello", c=MyEnum1.A, d=String.class, e=@MyAnno2(aa=200,bb="world"), f=100 //多个数组元素{100,11,99} 另外new int[]{100}这种不行 ) public class Demo3 { } @interface MyAnno1 { int a(); String b(); MyEnum1 c(); Class d(); MyAnno2 e(); int[] f(); }
* 在定义注解时,给注解添加注解,这个注解是@Target
*value属性类型为 ElementType[]数组 此数组类型为枚举
@Target(value={ElementType.TYPE,ElementType.METHOD, ElementType.FIELD})//MyAnno1只能定义在类/接口,成员方法和成员变量上 @interface MyAnno1 { }
* 源代码文件(SOURCE):注解只在源代码中存在,当编译时就被忽略了
* 字节码文件(CLASS):注解在源代码中存在,然后编译时会把注解信息放到了class文件,但JVM在加载类时,会忽略注解!
*JVM中(RUNTIME):注解在源代码、字节码文件中存在,并且在JVM加载类时,会把注解加载到JVM内存中(它是唯一可反射注解!)
*如果希望注解被反射,那么注解就要保留到运行时(RUNTIME)
例: 限定注解的保留策略到运行时
@Retention(RetentionPolicy.RUNTIME) @interface MyAnno1 { }
* 注解的保留策略必须是RUNTIME
java.lang.reflect.AccessibleObject的子类 Field/ Method/ Construcator/
* 类上的注解,需要使用Class来获取
* 方法上的注解,需要Method来获取
* 构造器上的注解,需要Construcator来获取
* 成员上的,需要使用Field来获取
Class :java.lang.Class 对象建模的类的类型
Method、Constructor、Field :AccessibleObject(M,C,F的基类)
java.lang.reflect.AccessibleObject的子类 Field/ Method/ Construcator/
它们都有一个方法:
*Annotation getAnnotation(Class),返回目标上指定类型的注解!
*Annotation[] getAnnotations(),返回目标上所有注解!
@Retention(RetentionPolicy.RUNTIME)//保留到运行时,如果不设置为保留到运行时,那么就无法反射出来 @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnn { String value() default "hello"; int value1() default 100; } @MyAnn(value="hello world", value1=200) public class MyClass { private int a; @MyAnn("myMethod")//给value属性赋值 的省略形式 public void fun() {} } public class Demo1 { public static void main(String[] args) throws Exception { Class clazz = MyClass.class;//获取MyClass的Class MyAnn myAnn = (MyAnn) clazz.getAnnotation(MyAnn.class);//获取类上的MyAnn类型的注解 System.out.println(myAnn.value());//获取value()属性值 结果为hello word System.out.println(myAnn.value1());//获取value1属性值 结果为200 Method method = clazz.getMethod("fun");//获取MyClass类中fun方法的Method MyAnn myAnn1 = method.getAnnotation(MyAnn.class);//获取方法上的MyAnn类型的注解 System.out.println(myAnn1.value());//结果为 myMethod System.out.println(myAnn1.value1());//结果为 value1默认(缺省default)值 100 } }
========================
java.lang.reflect接口Type
Type的实现类 :Class 对象建模的类的类型
Type的子接口之一: ParameterizedType 表示参数化类型 如 Collection<String>, Map<integer,String>
Class --> Type getGenericSupperclass()
Type --> ParameterizedType,把Type强转成ParameterizedType类型!!!
ParameterizedType --> 参数化类型 = A<String>
ParameterizedType:Type[] getActualTypeArguments(),A<String>中的String
Type[]就是Class[],我们就得到了类型参数了!
abstract class A<T> { public A() { /* * 在这里获取子类传递的泛型信息,要得到一个Class! */ // Class clazz = this.getClass();//得到子类的类型 // Type type = clazz.getGenericSuperclass();//获取传递给父类参数化类型 // ParameterizedType pType = (ParameterizedType) type;//它就是A<String> // Type[] types = pType.getActualTypeArguments();//它就是一个Class数组 // Class c = (Class)types[0];//它就是String // Class c = (Class)((ParameterizedType)this.getClass() .getGenericSuperclass()).getActualTypeArguments()[0]; System.out.println(c.geSimpleName());//打印T 既继承A的子类传递的泛型名称 } } class B extends A<String> { } class C extends A<Integer> { }
封装dbutils写一个BaseDao<T>()
SQL语句的表名通过反射泛型获取T的Class类型及反射注解获取
?及参数通过反射获取
class BaseDAO<T> { private QueryRunner qr = new TxQueryRunner(); private Class<T> beanClass; public BaseDAO() { beanClass = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } public void add(T bean) throws SQLException { Field[] fs = beanClass.getDeclaredFields(); //获取泛型名,如果表名=beanClass类型名,即可直接获取,如果不相同添加注解通过beanClass及注解获取 String sql = "insert into " + beanClass.getSimpleName() + " values("; //通过反射获取?个数 for(int i = 0; i < fs.length; i++) { sql += "?"; if(i < fs.length-1) { sql += ","; } } sql += ")"; Object[] params = {/*参数值是什么*/}; qr.update(sql, params); }
把dao,service,servlet中依赖的实现类的创建转移到工厂类和配置xml文件(可以随意切换实现类)
以来达到解耦的作用
xml示例
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="stu1" className="cn.itcast.domain.Student"> <property name="number" value="ITCAST_1001"/> <property name="name" value="zhangSan"/> <property name="age" value="29"/> <property name="sex" value="male"/> <property name="teacher" ref="t1"/><!-- ref的值必须是另一个been的id --> </bean> <bean id="stu2" className="cn.itcast.domain.Student"> <property name="number" value="ITCAST_1002"/> <property name="name" value="wangWu"/> <property name="age" value="94"/> <property name="sex" value="female"/> <property name="teacher" ref="t1"/><!-- ref的值必须是另一个been的id --> </bean> <bean id="t1" className="cn.itcast.domain.Teacher"> <property name="tid" value="TEACHER_2001" /> <property name="name" value="liSi" /> <property name="salary" value="123.456" /> </bean> <bean id="stuDao" className="cn.itcast.dao.impl.StudentImpl2"> </bean> <bean id="stuService" className="cn.itcast.service.impl.StudentServiceImpl"> <property name="studentDao" ref="stuDao"/> </bean> </beans>
Beanfactory测试
import cn.itcast.beanfactory.BeanFactory; import cn.itcast.dao.StudentDao; import cn.itcast.domain.Student; import cn.itcast.service.StudentService; /* * 面向接口编程 * dao * * daoImpl * service * * serviceImpl */ public class Demo1 { @Test public void fun1() { /* * 1. 创建Bean工厂,创建时需要给工厂指定配置文件 * 2. 从工厂中获取bean对象 */ BeanFactory bf = new BeanFactory("beans.xml"); Student s1 = (Student)bf.getBean("stu1"); Student s2 = (Student)bf.getBean("stu1"); System.out.println(s1 == s2); } @Test public void fun2() { /* * 1. 创建Bean工厂,创建时需要给工厂指定配置文件 * 2. 从工厂中获取bean对象 */ BeanFactory bf = new BeanFactory("beans.xml"); Student s1 = (Student)bf.getBean("stu1"); Student s2= (Student)bf.getBean("stu2"); System.out.println(s1.getTeacher() == s2.getTeacher()); } @Test public void fun3() { BeanFactory bf = new BeanFactory("beans.xml"); StudentDao stuDao = (StudentDao)bf.getBean("stuDao"); stuDao.add(null); stuDao.update(null); } @Test public void fun4() { BeanFactory bf = new BeanFactory("beans.xml"); StudentService service = (StudentService) bf.getBean("stuService"); service.login(); } }
================
1. MyEclipse10.0或以上版本!
2. 发布到Tomcat7.0或以上版本!
1. 创建JavaEE6.0应用!
-----------------------------
* 注解代替web.xml配置文件
* 异步处理
* 对上传的支持
Servlet3.0在市场上没有应用!
-----------------------------
1. 删除web.xml
/* <servlet> <servlet-name>AServlet</servlet-name> <servlet-class>cn.itcast.web.servlet.AServlet</servlet-class> <init-param> <param-name>p1</param-name> <param-value>v1</param-value> </init-param> <init-param> <param-name>p2</param-name> <param-value>v2</param-value> </init-param> <load-on-startup>1<load-on-startup> </servlet> <servlet-mapping> <servlet-name>AServlet</servlet-name> <url-pattern>/AServlet</url-pattern> <url-pattern>/AAServlet</url-pattern> </servlet-mapping> */ @WebServlet(urlPatterns="/AServlet", initParams={ @WebInitParam(name="p1", value="v1"), @WebInitParam(name="p2", value="v2") }, loadOnStartup=1 ) public class AServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("hello servlet3.0!"); resp.getWriter().print("hello servlet3.0!!"); } }
总结:
* 注解好处:配置信息少!
* 注解缺点:不方便修改!
@WebFilter(urlPatterns="/*") public class AFilter implements Filter { @Override public void destroy() { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse repsonse, FilterChain chain) throws IOException, ServletException { System.out.println("哈哈~,你看到我没有!"); chain.doFilter(request, repsonse); } @Override public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
@WebListener public class AListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent arg0) { System.out.println("死掉了"); } @Override public void contextInitialized(ServletContextEvent arg0) { System.out.println("出生了"); } }
-----------------------------
原来,在服务器没有结束响应之前,浏览器是看不到响应内容的!只有响应结束时,浏览器才能显示结果!
现在异步处理的作用:在服务器开始响应后,浏览器就可以看到响应内容,不用等待服务器响应结束!
* 得到AsyncContext,它是异步上下文对象
AsyncContext ac = request.startAsync(request,response);
* 给上下文一个Runnable对象,启动它!(给上下文一个任务,让它完成!)
ac.start(new Runnable() {
public void run() {
...
}
});
*@WebServlet(urlPatterns="/AServlet", asyncSupported=true)
*resp.setContentType("text/html;charset=utf-8");
*IE如果不能正常输出,这说明响应体大小不足512B,那你需要多输出点废话!
*AsyncContext#complete():通知Tomcat我们异步线程已经执行结束了!这让Tomcat才会及时的断开与浏览器的连接!
/** * 添加WebServlet注解 * @author cxf * */ @WebServlet(urlPatterns="/AServlet", asyncSupported=true)//<span style="font-size: 13.3333px; font-family: Arial, Helvetica, sans-serif;">asyncSupported=trues 为开启异步处理</span> public class AServlet extends HttpServlet { // public static void main(String[] args) { // System.out.println("hello"); // new Thread() { // public void run() { // // } // }.start(); // // System.out.println("不知道上面的线程是否结束!"); // } public void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); // 异步支持IE!但如果输出不足512B,没有异步效果! for(int i = 0; i <= 512; i++) { resp.getWriter().print("a"); } resp.getWriter().flush(); /* * 1. 得到异步上下文对象 */ final AsyncContext ac = req.startAsync(req, resp); /* * 2. 给上下文对象一个Runnable对象,让它执行这个任务,既启动异步处理线程 */ ac.start(new Runnable() { public void run() { println("现在马上开始<br/>", resp); sleep(2000); for(char c = 'A'; c <= 'Z'; c++) { println(c+"", resp); sleep(250); } // 通知Tomcat我们已经执行结束了!如果不反馈结束服务器会等到响应超时为止 ac.complete(); } }); } public void println(String text, HttpServletResponse resp) { try { resp.getWriter().print(text); resp.getWriter().flush(); //每次响应后都要刷新一次流,这样浏览器会看到一个字母一个字母出现 } catch (IOException e) { } } public void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { } } }
-----------------------------
>method="post"
>enctype="multipart/form-data",它的默认值是:application/x-www-form-urlencoded
><input type="file" name="必须给"/>
>request.getParameter()不能再用
>request.getInputStream()使用它来获取整个表单的数据!
> 创建工厂
> 解析器
> 使用解析器来解析request对象,得到List<FileItem>
==============
* 表单不变
* 在Servlet中不需要再使用commons-fileupload,而是使用Servlet3.0提供的上传组件接口!
==============
* 使用request.getPart("字段名"),得到Part实例,
* Part:
> StringgetContentType():获取上传文件的MIME类型
> StringgetName():获取表单项的名称,不是文件名称
> StringgetHeader(String header):获取指定头的值
> longgetSize():获取上传文件的大小
>InputStream getInputStream():获取上传文件的内容
> voidwrite(String fileName):把上传文件保存到指定路径下
*默认Servlet是不支持使用上传组件:需要给Servlet添加一个注解: @MultipartConfig
它没有提供获取上传文件名称的方法:
*这需要我们自己从Content-Disposition头中截取!
@WebServlet(urlPatterns="/AServlet") @MultipartConfig public class AServlet extends HttpServlet { @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); /* * 1. getParameter()方法可以使用了!!! */ String username = req.getParameter("username");//可以使用了!!! /* * 2. 获取文件表单字段,对应的Part对象 */ Part part = req.getPart("resume"); /* * 3. 从Part中获取需要的数据 */ // 获取上传文件的MIME类型 System.out.println(part.getContentType()); // 获取上传文件的字节数 System.out.println(part.getSize()); // 获取文件字段名称 System.out.println(part.getName()); // 获取头,这个头中包含了上传文件的名称 System.out.println(part.getHeader("Content-Disposition")); // 保存上传文件 part.write("C:/xxx.jpg"); // 截取上传文件名称 String filename = part.getHeader("Content-Disposition"); int start = filename.lastIndexOf("filename=\"") + 10; int end = filename.length() - 1; filename = filename.substring(start, end); System.out.println(filename); } }
-----------------------------
方法的作用:在运行时,动态创建一组指定的接口的实现类对象!(在运行时,创建实现了指定的一组接口的对象)
public class Demo1 { @Test public void fun1() { /* * 三大参数 * 1. ClassLoader * 方法需要动态生成一个类,这个类实现了A、B接口,然后创建这个类的对象! * 需要生成一个类,这个类也需要加载到方法区中,谁来加载,当然是ClassLoader!!! * * 2. Class[] interfaces * 它是要实现的接口们 * * 3. InvocationHandler * 它是调用处理器 * 先敷衍它! * * 代理对象的实现的所有接口中的方法,内容都是调用InvocationHandler的invoke()方法。 */ ClassLoader loader = this.getClass().getClassLoader(); InvocationHandler h = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("你好,动态代理!"); return "xxx"; } }; // 使用三大参数创建代理对象!!! Object o = Proxy.newProxyInstance(loader, new Class[]{A.class, B.class}, h); // 强转成A和B类型,成功了! A a = (A) o; B b = (B) o; // a.a(); // a.aa(); // b.b(); // b.bb(); // System.out.println(o.getClass().getName()); Object result = a.aaa("hello", 100); System.out.println(result); } } interface A { public void a(); public void aa(); public Object aaa(String s, int i); } interface B { public void b(); public void bb(); }
-------------
Object proxyObject =Proxy.newProxyInstance(ClassLoader classLoader, Class[] interfaces,InvocationHandler h);
1. ClassLoader:类加载器!
* 它是用来加载器的,把.class文件加载到内存,形成Class对象!
2. Class[] interfaces:指定要实现的接口们
3. InvocationHandler:代理对象的所有方法(个别不执行,getClass())都会调用InvocationHandler的invoke()方法。
---------------------------------------------------------
最终是学习AOP(面向切面编程),它与装饰者模式有点相似,它比装饰者模式还要灵活!
----------------------------------------------------------
public Object invoke(Object proxy, Methodmethod, Object[] args);
这个invoke()方法在什么时候被调用!
*是在调用代理对象所实现接口中的方法时
* Object proxy:当前对象,即代理对象!在调用谁的方法!
* Method method:当前被调用的方法(目标方法)
* Object[] args:实参!
----------------------------
目标对象:被增强的对象
代理对象:需要目标对象,然后在目标对象上添加了增强后的对象!
目标方法:增强的内容
代理对象 = 目标对象 + 增强
代码
接口及实现类
// 服务员 public interface Waiter { // 服务 public void serve(); } ------------------ public class ManWaiter implements Waiter { public void serve() { System.out.println("服务中..."); } }
/** *必须要掌握的是当前这个案例! * @author cxf * */ public class Demo2 { @Test public void fun1() { Waiter manWaiter = new ManWaiter();//目标对象 /* * 给出三个参数,来创建方法,得到代理对象 */ ClassLoader loader = this.getClass().getClassLoader(); Class[] interfaces = {Waiter.class}; InvocationHandler h = new WaiterInvocationHandler(manWaiter);//参数manWaiter表示目标对象 // 得到代理对象,代理对象就是在目标对象的基础上进行了增强的对象! Waiter waiterProxy = (Waiter)Proxy.newProxyInstance(loader, interfaces, h); waiterProxy.serve();//前面添加“您好”, 后面添加“再见” } } class WaiterInvocationHandler implements InvocationHandler { private Waiter waiter;//目标对象 public WaiterInvocationHandler(Waiter waiter) { this.waiter = waiter; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("您好!"); this.waiter.serve();//调用目标对象的目标方法 System.out.println("再见!"); return null; } }
目标是让目标对象和增强都可以切换!
服务员 接口及实现类
// 服务员 public interface Waiter { // 服务 public void serve(); public void shouQian(); } ---------------------- public class ManWaiter implements Waiter { public void serve() { System.out.println("服务中..."); } public void shouQian() { System.out.println("混蛋,给我钱!"); } }
/** * 前置增强 * @author cxf * */ public interface BeforeAdvice { public void before(); } ------------------------------------------- public interface AfterAdvice { public void after(); }
/** * 它用来生成代理对象 * 它需要所有的参数 * * 目标对象 * * 增强 * @author cxf */ /** * 1. 创建代理工厂 * 2. 给工厂设置三样东西: * * 目标对象:setTargetObject(xxx); * * 前置增强:setBeforeAdvice(该接口的实现) * * 后置增强:setAfterAdvice(该接口的实现) * 3. 调用createProxy()得到代理对象 * * 执行代理对象方法时: * > 执行BeforeAdvice的before() * > 目标对象的目标方法 * > 执行AfterAdvice的after() * @author cxf * */ public class ProxyFactory { private Object targetObject;//目标对象 private BeforeAdvice beforeAdvice;//前置增强 private AfterAdvice afterAdvice;//后置增强 /** * 用来生成代理对象 * @return */ public Object createProxy() { /* * 1. 给出三大参数 */ ClassLoader loader = this.getClass().getClassLoader(); Class[] interfaces = targetObject.getClass().getInterfaces(); InvocationHandler h = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* * 在调用代理对象的方法时会执行这里的内容 */ // 执行前置增强 if(beforeAdvice != null) { beforeAdvice.before(); } Object result = method.invoke(targetObject, args);//执行目标对象的目标方法 // 执行后置增强 if(afterAdvice != null) { afterAdvice.after(); } // 返回目标对象的返回值 return result; } }; /* * 2. 得到代理对象 */ Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h); return proxyObject; } public Object getTargetObject() { return targetObject; } public void setTargetObject(Object targetObject) { this.targetObject = targetObject; } public BeforeAdvice getBeforeAdvice() { return beforeAdvice; } public void setBeforeAdvice(BeforeAdvice beforeAdvice) { this.beforeAdvice = beforeAdvice; } public AfterAdvice getAfterAdvice() { return afterAdvice; } public void setAfterAdvice(AfterAdvice afterAdvice) { this.afterAdvice = afterAdvice; } }
/* * 目标是让目标对象和增强都可以切换! */ public class Demo3 { @Test public void fun1() { ProxyFactory factory = new ProxyFactory();//创建工厂 factory.setTargetObject(new ManWaiter());//设置目标对象 factory.setBeforeAdvice(new BeforeAdvice() {//设置前置增强 public void before() { System.out.println("您好不好!"); } }); factory.setAfterAdvice(new AfterAdvice() {//设置后置增强 public void after() { System.out.println("再见不见!"); } }); Waiter waiter = (Waiter)factory.createProxy(); waiter.shouQian(); }}
* 把.class文件加载到JVM的方法区中,变成一个Class对象!
*Class#getClassLoader()
理解:把它们比作片警!分"片"管理
*引导bootstrap classloader:引导加载器 类库!加载rt.jar中的类
* 扩展sun.misc.Launcher$ExtClassLoader:扩展jar包 扩展类加载器,加载lib/ext目录下的类
*sun.misc.Launcher$AppClassLoader系统:系统加载器 应用下的class,包含开发人员写的类,和第三方的jar包!classpath下的类!
系统类加载器的上级:扩展加载器
扩展类加载器的上级:引导加载器
引导没上层 :导类加载器是用C语言写的,是JVM的一部分,它是最上层的类加载器
jvm中相同的类
在JVM中,不可能存在一个类被加载两次的事情!
一个类如果已经被加载了,当再次试图加载这个类时,类加载器会先去查找这个类是否已经被加载过了,
如果已经被加载过了,就不会再去加载了。
但是,如果一个类使用不同的类加载器去加载是可以出现多次加载的情况的!
也就是说,在JVM眼中,相同的类需要有相同的class文件,以及相同的类加载器。
当一个class文件,被不同的类加载器加载了,JVM会认识这是两个不同的类,这会在JVM中出现两个相同的Class对象!甚至会出现类型转换异常!
======================================
代理模式保证了JDK中的类一定是由引导类加载加载的!这就不会出现多个版本的类,这也是代理模式的好处。
* 代码中出现了这么一行:new A();
> 系统发现了自己加载的类,其中包含了new A(),这说明需要系统去加载A类
> 系统会给自己的领导打电话:让扩展去自己的地盘去加载A类
> 扩展会给自己的领导打电话:让引导去自己的地盘去加载A类
> 引导自己真的去rt.jar中寻找A类
* 如果找到了,那么加载之,然后返回A对应的Class对象给扩展,扩展也会它这个Class返回给系统,结束了!
* 如果没找到:
> 引导给扩展返回了一个null,扩展会自己去自己的地盘,去寻找A类
* 如果找到了,那么加载之,然后返回A对应的Class对象给系统,结束了!
* 如果没找到
> 扩展返回一个null给系统了,系统去自己的地盘(应用程序下)加载A类
* 如果找到了,那么加载之,然后返回这个Class,结束了!
* 如果没找到,抛出异常ClassNotFoundException
因为"谁"导致的加载,"谁"就得继续加载相关类
每一个线程都有一个当前的类加载器
Thread类的 getContextClassLoader()方法
例:
class MyApp {//被系统加载
main() {
A a= new A();//也由系统加载
String s = new String();//也由系统加载!(先有系统加载开始,然后执行委托机制,最终还是由引导加载)
}
}
----------------------------------------
class String {//引导
private Integer i;//直接引导加载
}
=====================
目的一般是为了加载网络上的类,因为这会让class在网络中传输,为了安全,那么class一定是需要加密的,
所以需要自定义的类加载器来加载(自定义的类加载器需要做解密工作)。
ClassLoader加载类都是通过loadClass()方法来完成的,loadClass()方法的工作流程如下:
1. 调用 findLoadedClass () 方法查看该类是否已经被加载过了,如果该没有加载过,那么这个方法返回 null*在JVM的方法区中查看已加载过的类!即判断当前类是否已经被加载过
2.判断findLoadedClass()方法返回的是否为null,如果不是null那么直接返回,这可以避免同一个类被加载两次
3.如果findLoadedClass()返回的是null,那么就启动代理模式(委托机制),即调用上级的loadClass()方法,获取上级的方法是getParent(),当然上级可能还有上级,这个动作就一直向上走;
4.如果getParent().loadClass()返回的不是null,这说明上级加载成功了,那么就加载结果
5.如果上级返回的是null,这说明需要自己出手了,这时loadClass()方法会调用本类的findClass()方法来加载类
6.这说明我们只需要重写ClassLoader的findClass()方法,这就可以了!如果重写了loadClass()方法覆盖了代理模式
通过分析要自定义一个类加载器,
只需要* 继承ClassLoader
然后重写它的* 重写findClass()方法即可
public class FileSystemClassLoader extends ClassLoader { private String classpath;//[这是它的地盘!因为类加载器都是片警,它需要有自己的地盘!] public FileSystemClassLoader() {} public FileSystemClassLoader(String classpath) {//[创建类加载器时,就要为其指定负责的地盘!它只会到这里去查找类!] this.classpath = classpath; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] datas = getClassData(name);//[通过类名称找到.class文件,把文件加载到一个字节数组中。] if(datas == null) {//[如果返回的字节数组为null,说明没有找到这个类,抛出异常] throw new ClassNotFoundException("类没有找到:" + name); } //[它可以把字节数组变成Class对象!defineClass()是ClassLoader的方法,它的作用是把字节数组变成Class对象!] return this.defineClass(name, datas, 0, datas.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException("类找不到:" + name); } } private byte[] getClassData(String name) throws IOException { name = name.replace(".", "\\") + ".class"; File classFile = new File(classpath, name); return FileUtils.readFileToByteArray(classFile);//[commons-io.jar中的类] } }
ClassLoader loader = new FileSystemClassLoader("F:\\classpath"); Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils"); Method method = clazz.getMethod("md5", String.class); String result = (String) method.invoke(null, "qdmmy6"); System.out.println(result);
=====================
* 服务器类加载器:${CATALINA_HOME}\lib,服务器类加载器,它负责加载这个下面的类!
* 应用类加载器:${CONTEXT_HOME}\WEB-INF\lib、${CONTEXT_HOME}\WEB-INF\classes,应用类加载器,它负责加载这两个路径下的类!
引导
扩展
系统
优先级
服务器类加载器:先自己动手,然后再去委托
应用类加载器::先自己动手,然后再去委托