1,javaBean的主要应用
:如果要在两个模块之间传递多个信息,可以将这些信息封装到一个
JavaBean
中,这种
JavaBean
的实例对象通常称之为值对象(
Value Object
,简称:VO)。
将高新技术1中的ReflectPoint.java类作为javaBean,其中的属性x,y需要相应的set,get方法。
快捷键Ait+Shift+S-->r,自动生成get和set方法
。
//用内省的方式操作javaBean中的属性。
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class IntroSpectorTest {
public static void main(String[] args) throws Exception {
ReflectPoint pt1 = new ReflectPoint(3, 5);
String propertyName = "x";
//原来的方法"x"-->"X"-->"getX"-->MethodGetX这种方式比较复杂,java已经帮我们封装好了类,直接拿过来用就O啦,IntroSpector和PropertyDescriptor。
//调用自定义的方法,读取出x属性的值。
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
//调用自定义的方法,写入x属性的值。
Object value = 7;
setProperty(pt1, propertyName, value);
System.out.println(pt1.getX());
// 内省—beanutils工具包, Sun公司的内省API过于繁琐,所以Apache组织结合很多实际开发中的应用场景开发了一套简单、易用的API操作Bean的属性——Beanutils 。
Beanutils工具包的常用类: BeanUtils
PropertyUtils
ConvertUtils.regsiter(Converter convert, Class clazz)
下载beanutils的jar包,复制到工程下Lib文件夹内,右键-->Build Path-->Add to Build Path即可。
BeanUtils用到了logging日志jar包,同样的方式添加到工程中来。
BeanUtils.setProperty(pt1, propertyName, "90");
//注意:此处设置的新值为字符串。此方法也 返回String类型。
好处:1)因为在web开发中,我们通过浏览器填写的数字,传给服务器的是一个字符串。自己写程序的话就需要把字符串转换为整数,再把整数设置进去。BeanUtils替我们完成了这个动作。返回同理。
System.out.println(BeanUtils.getProperty(pt1, propertyName).getClass().getName());
好处:2) 支持属性的级联操作。如果自己做那就比较复杂了。
BeanUtils.setProperty(pt1, " birthday.time", "111");
System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));
好处:3)BeanUtils中提供了 Map与javaBean可以相互转化的方法。
Map map = {name : "zhangsan",age :35}; //JDK1.7新特性,对Map直接复制。不用一堆put方法了。爽!
BeanUtils.setProperty(map, "name", "lisi");
//注意:PropertyUtils以属性本身的类型进行操作, BeanUtils是以String的形式对javaBean进行操作。如果不想进行类型转化或者转的过程中出错,我们就使用PropertyUtils。
PropertyUtils.setProperty(pt1, propertyName, 8);
System.out.println(PropertyUtils.getProperty(pt1, propertyName).getClass().getName()); //返回Integer类型
}
//自定义方法,写入x属性的值。
private static void setProperty(Object obj, String propertyName,
Object value) throws IntrospectionException, IllegalAccessException, InvocationTargetException {
// 获取PropertyDescriptor对象。
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
//反射SetX方法。
Method methodSetX = pd.getWriteMethod();
methodSetX.invoke(obj,value);
}
//自定义方法,获取x属性的值。
private static Object getProperty(Object obj, String propertyName)
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
//第一种方式: PropertyDescriptor 。
//获取 PropertyDescriptor 对象。
PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
//反射GetX方法。
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(obj);
//第二种方式:IntroSpector。
//调用IntroSpector的静态方法得到BeanInfo子类对象
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
//通过BeanInfo得到 PropertyDescriptor 对象数组。
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
//定义一个外部变量用来接收返回值。
Object retVal = null;
//遍历对象数组
for (PropertyDescriptor pd : pds) {
//判断所需要的属性。
if(pd.getName().equals(propertyName)){
//利用属性得到对应方法的反射,再利用得到的 方法反射 得到该对象的属性值。
retVal=pd.getReadMethod().invoke(obj);
break;
}
}
return retVal;
}
}
2, 注解
Annotation
。JDK1.5新特性。
EJB (EnterpriseJavaBean), JPA(Java Persistence API),Spring,Hibernate,Struts2。都是基于注解的。
JDK为我们提供了三个基本注解:Deprecated,Override,SupressWarnings。
包,类,字段,方法,方法的参数以及局部变量上。
1)先自定义一个注解类。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.itheima.day1.EnumTest;
一个注解的生命周期有三个阶段:
RetentionPolicy.SOURCE---> RetentionPolicy.CLASS(默认)---> RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME) //元注解
@Target({ElementType.METHOD,ElementType.TYPE}) //元注解,TYPE接口JDK1.5新特性。
public @interface ItheimaAnnotation {
String color() default "blue"; //为color属性指定默认值。
String value(); //此方法非常特殊,当调用时只须给此属性赋值时,可省略value=。
// 数组类型的属性。
int[] arrayAttr()default {3,4,4};
//枚举类型的属性。
EnumTest.TrafficLamp lamp()default EnumTest.TrafficLamp.RED;
//注解类型的属性。
MetaAnnotation annotationAttr()default @MetaAnnotation("hi,I am annotationAttr");
//Class类型的属性
Class clazz();
}
2)应用了注解类的类。
import java.util.Arrays;
//注解加在类上。
@ItheimaAnnotation(clazz=String.class,annotationAttr=@MetaAnnotation("Hi,I am new annotationAttr"),color="red",value="abc",arrayAttr=/*{*/1/*,2,3}*/) //数组属性是有一个值,不用写大括号。
public class AnnotationTest {
// 注解,向编译器传递信息
@SuppressWarnings("deprecation") //禁止警告,JDK为我们提供的。 一个注解就是一个类。用到注解就相当于一个实例对象。
@ItheimaAnnotation("efg")
3)对应用了“注解类的类”进行反射操作的类,主函数写在哪个类中从思想上来说无所谓。
public static void main(String[] args) {
System.runFinalizersOnExit(true);
//判断指定类型的注解是否存于该元素上。
if(AnnotationTest.class.isAnnotationPresent(ItheimaAnnotation.class)){
//利用反射得到注解。
ItheimaAnnotation annotation = (ItheimaAnnotation)AnnotationTest.class.getAnnotation(ItheimaAnnotation.class);
//测试打印各注解
System.out.println(annotation.color());
System.out.println(annotation.value());
System.out.println(annotation.arrayAttr().length);
System.out.println(annotation.lamp().nextLamp());
System.out.println(annotation.annotationAttr().value());
}
}
@Deprecated //标识该方法为已过时,不建议使用。
public static void hiHeima() {
System.out.println("嗨,黑马");
}
}
//定义一个注解类,在上述定义的注解类中把此注解类作为它的一个注解类型的属性。
public @interface MetaAnnotation {
String value();
}
查阅langspec,注解类属性的类型有:原子类型,String,Class,any invocation of Class,枚举,注解,以上类型的数组。
3,泛型Generic。JDK1.5新特性。
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用add方法即可。
java 中的泛型类型(或者泛型)类似于 c++ 中的模板,但是这种相似性仅限于表面, java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦出( erasure )(编译器使用泛型类型信息保证类型安全,然后再生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 java 厂商升级器 JVM 造成难以逾越的障碍,所以, java 的泛型采用了可以完全在编译器中实现的擦出方法。
//导入下面程序需要的各种包
import java.lang.reflect.*;
import java.util.*;
import com.itheima.day1.ReflectPoint;
public class GenericTest {
public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
ArrayList collection1 = new ArrayList();
collection1.add(1);
collection1.add(1L);
collection1.add("abc");
int i = (Integer)collection1.get(1); //返回Object,强转Integer。编译通过,运行时报错。因为取出是一个long类型。
//集合中使用泛型
ArrayList<String> collection2 = new ArrayList<String>();
//collection2.add(1); // 编译报错
//collection2.add(1L);
collection2.add("abc");
//ArrayList<String> collection5=collection2;
//System.out.println(collection5);
//System.out.println(collection5.get(1));
String element = collection2.get(0); //取出时不需要对类型进行强转
//反射中使用泛型
//new String(new StringBuffer("abc"));
Constructor<String> constructor1=String.class.getConstructor(StringBuffer.class);
String str2=constructor1.newInstance(/*"abc"*/new StringBuffer("abc")); //此处不需要强转。
System.out.println(str2.charAt(2));
//泛型只存在于源码中,编译器检查完会去掉泛型标识。称为“去类型化”。
ArrayList<Integer> collection3 = new ArrayList<Integer>();
System.out.println(collection2.getClass() == collection3.getClass()); //true,内存中只有一份字节码
//通过反射突破参数化的类型的实际参数的限制
collection2.getClass().getMethod("add", Object.class).invoke(collection2, 8);
System.out.println(collection2.size()+"::::"+collection2.contains(8));
System.out.println(collection2);
printCollection(collection2);
Class<Number> y=null;
Class<? extends Number> x;//=String.class.asSubclass(Number.class);
//y=x; //错误,相当于Class<Number> y=Class<? extends Number> x;参数化类型没有继承关系
x=y;
//写一个使用泛型的案例。
HashMap<String,Integer> maps=new HashMap<String, Integer>();
maps .put("age1", 24);
maps .put("age2", 23);
maps .put("age3", 27);
Set<Map.Entry<String,Integer>> entrySet=maps.entrySet(); //还有一种方法使用Set<K>= maps .keySet();
for(Map.Entry<String, Integer> entry:entrySet){
System.out.println(entry.getKey()+"::"+entry.getValue());
}
//类型推断,取交集,最小公倍数。
Integer i = add(3,7);
Number num = add(3,7.2);
Object o = add(3,"abc");
swap(new String[]{"abc","xyz","itheima"},1,2);
//swap(new int[]{1,46,2,8,3},1,2); 报错,泛型的实际参数只能是引用类型,不能是基本类型。 int[]本来就是对象类型,元素int无法自动装箱。
//调用自定义的方法,自动类型转化。
Object obj = "abc";
String str = autoConvert(obj);
copy1(new Vector<String>(),new String[10]);
//类型推断,参数求交集。最小公倍数Object
copy2(new Date[10],new String[10]);
//类型推断,参数类型传播性。Vector已经确定参数T的类型。
//copy1(new Vector<Date>(),new String[10]);
//使用自定义的泛型类型
GenericDao<ReflectPoint> dao = new GenericDao<ReflectPoint>();
dao.add(new ReflectPoint(3,5));
ReflectPoint s = dao.findById(1); //添加对象,查询出来也是对象。
================ (高难度,以后还要 结合框架再深入理解 ) ================
//用反射的方式来获取泛型的参数化类型。
// Vector<Date> v1 = new Vector<Date>();
// v1.getClass(). 由于去类型化,这种操作无法实现
//查阅Hibernate源码,得到如下解决方案:
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
//反射Method提供了方法来获取泛型参数类型
Type[] types = applyMethod.getGenericParameterTypes();
ParameterizedType pType = (ParameterizedType) types[0];
System.out.println(pType);
//得到原始的参数类型,即class java.util.Vector
System.out.println(pType.getRawType());
//得到实际的参数类型,即class java.util.Date
System.out.println(pType.getActualTypeArguments()[0]);
注意: 很多框架可以自动生成Date对象然后存入集合中,就是用上述方式获取参数类型,然后把对象转为Date存入。
}
//定义一个方法,把对象传进去,因为方法的反射可以获取到参数的泛型类型。
public static void applyVector(Vector<Date> v1){
}
//将任意类型数组中的所有元素填充为相应类型的某个对象。
private static <T> void fillArray(T[] a, T obj){
for(int i=0;i<a.length;i++){
a[i]=obj;
}
}
//将Object对象转化为任意类型
private static <T> T autoConvert(Object obj){
return (T)obj;
}
//交换数组中两个元素的位置。
private static <T> void swap(T[] arr,int x, int y){
T tmp = arr[x];
arr[x] = arr[y];
arr[y] = tmp;
}
private static <T>T add(T x,T y) {
//return x+y; //编译报错,并不是所有的类型都支持+号运算,这点没有C++强大,C++可以重载运算符。;
return y;
}
//定义方法,打印任意类型的集合,两种方法:
方法一: 使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
public static void printCollection(Collection<?> collection){
//collection.add("ccc"); //不能调用与 参数 类型 有关的方法。
System.out.println(collection.size());
System.out.println(collection);
}
方法二: 通配符的方案比泛型类型更有效
public static <T>void printCollection2(Collection<T> collection,T obj){
//collection.add("ccc");
System.out.println(collection.size());
System.out.println(collection);
collection.add(obj); //可以添加相应元素,而通配符做不到。
}
//把一个类型的数组中的元素复制到相应类型的集合中。
public static <T>void copy1(Collection<T> dest,T[] src){
}
//把一个类型的数组中的元素复制到相应类型的数组中。
public static <T>void copy2(T[] dest,T[] src){
}
}
4, ArrayList<E> 类定义和ArrayList<Integer>类引用中涉及如下术语:
整个 ArrayList<E> 称为泛型类型。
ArrayList<E> 中的E称为类型变量或类型参数。
整个 ArrayList<Integer> 称为参数化的类型。
ArrayList<Integer> 中的Integer称为类型参数的实例或实际参数类型。
ArrayList<Integer> 中的<>念着typeof
ArrayList 称为原始类型。
参数化类型与原始类型的可以互相兼容。
参数化类型不考虑类型参数的继承关系:
Vector<String>v=new Vector<Object>();// 错误
Vector<Object>v=new Vector<String>();// 错误
在创建数组实例时,数组的元素不使用参数化的类型:
Vector<Integer>vector[]=new Vector<Integer>[10];// 错误
限定通配符的上边界:
正确: Vector<? extends Number>x = new Vector<Integer>();//Number类型下面有八大基本类型
错误: Vector<? extends Number>x = new Vector<String>();
限定通配符的下边界:
正确: Vector<? super Integer>x = new Vector<Number>();
错误: Vector<? super Integer>x = new Vector<Byte>();
提示:
限定通配符总是包括自己。
只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y= newVector<Integer>();
Vector<Number> x = y;
上面的代码错误,原理与Vector<Object> x11 = new Vector<String>();相似, 只能通过强制类型转换方式来赋值。
并且边界还可以是多个边界用&符号,<T extends Serializable&Cloneable>同时实现两个接口。
只有引用类型才能作为泛型方法的实际参数。普通方法,构造方法,静态方法都可以用泛型。在创建数组实例时,数组的元素不使用参数化的类型。
用类型变量表示异常,称为参数化的异常,可以用于方法的 throws 列表中,但是不能用于 catch 子句中。
private static <T extends Exception> void sayHello throws T { //声明的T必须继承一个异常例如:Exception
try{
}catch(Exception e){ //必须明确具体抓哪个异常
throw (T)e; //包装成另一个异常抛出去,分层设计中应用较多
}
}
5,类型推断。
编译器判断泛型方法的实际类型参数的过程称为类型推断。 类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下: 1)
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如: swap(new String[3],3,4) -------->
static <E> void swap(E[] a, int i, int j) 2)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如: add(3,5) ----------->
static <T> T add(T a, T b) 3)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题: fill(new Integer[3],3.5f) ---------->
static <T> void fill(T[] a, T v) 4)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误: int x =(3,3.5f) -------->
static <T> T add(T a, T b) 5)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题: copy(new Integer[5],new String[5]) ------------>
static <T> void copy(T[]a,T[] b); copy(new Vector<String>(), new Integer[5]) ----------->
static <T> voidcopy(Collection<T> a , T[] b);
6,定义一个泛型类型。
import java.util.Set;
//Dao---Data Access Object,crud---对数据的四个基本操作简写。
public class GenericDao<T> {public void add(T x){
//添加对象}
public T findById(int id) {
//根据id查询对象return null;
}
public void delete(T obj){
//根据对象删
}
public void delete(int id){
//根据id删除对象}
public void update(T obj){
//覆盖掉原来编号相同的对象即可 }
//
静态方法不能用泛型类型。要用只能自己单来,此处的T和类上的T不是一回事。public static <T>void update2(T obj){
}
public T findByUserName(String name){ //登陆按姓名进行查询
return null;
}
public Set<T> findByConditions(String where){
//查出符合条件的一个对象集合return null;
}
}
7,类加载器。 java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader。 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器,它不是java类,而是由C++编写的二进制代码,这正是BootStrap。 类加载器之间的父子关系和管辖范围图:
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, Exception {
System.out.println(
ClassLoaderTest.class.getClassLoader().getClass().getName()
);
System.out.println(System.class.getClassLoader());
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
//有输出结果可以看到该类的类加载器是AppClassLoader,证明该类在ClassPath路径下。
//若将ClassLoaderTest打成jar包存到lin\ext\itheima.jar。则输出类加载器为extClassLoader。
while (loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
//在此类中测试下文我自己的类加载器。
//注意:有包名的类不能调用无包名的类
//System.out.println(new ClassLoaderAttachment().toString());
Class clazz = new MyClassLoader("itheimalib").loadClass(" com.itheima.day2.ClassLoaderAttachment");
//理解此处的加包名的原因:AppClassLoader指定到ClassPath路径下,高新技术1中关于路径的设置问题中已经理解了类加载器需要加包名,且开头不加\。故此处也要加包名。
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
}
}
类加载器的委托机制:
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
当 Java 虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。
如果类 A 中引用了类 B , Java 虚拟机将使用加载类 A 的类装载器来加载类 B 。
还可以直接调用 ClassLoader.loadClass () 方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛 ClassNotFoundException ,不是再去找发起者类加载器的儿子,因为没有 getChild 方法,即使有,可能有多个儿子,系统无法知道加载使用哪个类加载器。
模板方法设计模式:
父类 —>loadClass
子类 1 (自己干的代码不一样)
子类 2 (自己干)
子类1和子类2执行流程一样,那么就把这个流程放在父类里编写,所有的子类都继承父类,但是父类在执行过程中其中有一个过程不会干,需要不同的子类去不同的实现,这时就把这个不会的步骤写成一个抽象方法,让不同的子类去实现。
通过这种设计模式我们不用考虑流程的每一步,只用关心我们需要复写的方法即可,整个流程交给父类模板就Ola。
//自定义一个类加载器。让它同时具备加密的方法。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) throws Exception {
//直接用参数传需要加密的文件。
String srcPath = args[0]; //传进来一个原路径,为抽象路径。
String destDir = args[1]; //传进来一个目标目录。
FileInputStream fis =new FileInputStream(srcPath);
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1); //得到文件名
String destPath = destDir +"\\"+ destFileName; //拼出目标路径
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
//加密算法
private static void cypher(InputStream ips,OutputStream ops)throws Exception{
int b = -1;
while((b=ips.read())!=-1){
ops.write(b^0xff);
}
}
private String classDir;
//采用模板设计模式,此处复写findClass方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir +"\\"+name.substring(name.lastIndexOf('.')+1)+".class";
//子类不能比父类抛出更大的异常,只能try
try {
FileInputStream fis = new FileInputStream(classFileName);
//定义字节数组流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos); //解密
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
//自定义一个附件类,用来被加密。此处继承于Date,原因是得到该类的加密字节码实例化后返回父类型Date,若返回本类类型则编译报错,因为编译器识别不了加密的本类字节码。
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString(){
return "hello,itheima";
}
}
特别注意:
视频中讲解的web项目关于类加载器的分析,让自己的web项目用ExtClassLoader加载,导出jar包到jre\lin\ext下,这个路径必须是运行tomcat服务器的虚拟机的路径下才行。关于Tomcat服务器配置的虚拟机的路径,找到tomcat\bin\startup.bat右键编辑,即set JAVA_HOME=d:\MyEclipse\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013(我的虚拟机路径)。
8,代理。
程序中用到的代理,例如:一个类已经写好了,我们还需要为这个类添加日志等功能。这时我们就需要用到代理。
AspectOrientedProgramming) 面向方面的编程。
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService --------|-------------|------------|-------------
CourseService --------|-------------|------------|-------------
MiscService ---------|-------------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。 可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样 的,如下:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
JVM 可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
1)JVM 生成的动态类必须实现一个或多个接口,所以, JVM 生成的动态类只能用作具有相同接口的目标类的代理。
2)如果一个类没有实现接口,使用CGLIB库。CGLIB 库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1)在调用目标方法之前
2)在调用目标方法之后
3)在调用目标方法前后
4)在处理目标方法异常的catch块中
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.Handler;
public class ProxyTest {
public static void main(String[] args) throws Exception {
//利用API动态生成类,我们通常把它作为代理类。
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName()); //$Proxy0
System.out.println("------begin constructor list-------");
//拼出下面构造方法。
/*$Proxy0()
$Proxy0(java.lang.reflect.InvocationHandler)*/
//用反射得到动态类的构造方法数组
Constructor[] constructors = clazzProxy1.getConstructors();
//遍历构造方法数组
for(Constructor constructor: constructors){
String name = constructor.getName();
//使用StringBuilder,线程不同步,比StringBuffer效率更高。多线程中用StringBuffer,单线程中用StringBuilder。这里是单线程,不用考虑安全问题。
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
//得到构造方法的参数类型数组
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
//必须判断有参数才删最后的逗号
if (clazzParams.length != 0) {
//去掉最后一个参数末尾的逗号
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
//同样的原理得到动态类的方法。
System.out.println("------begin method list-------");
//反射的方法得到方法的数组
Method[] methods = clazzProxy1.getMethods();
//遍历数组得到各个方法
for(Method method: methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if (!(clazzParams.length==0)) {
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
//创建代理类的实例对象
System.out.println("-------begin create instance object-------");
//clazzProxy1.newInstance();这个不可以,它调用的是不带参数的构造方法。
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
// InvocationHandler是一个接口,我们自己定义一个实现类。
class MyInvocationHandler1 implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);
proxy1.clear();
//proxy1.size();编译报错,因为invoke方法返回null。
//因为只使用了一次,所以用匿名内部类
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
//为了把代理做成一个框架,不硬编码,抽取目标target和系统功能new MyAdvice()两个参数
//放在外面作为成员变量,证明操作的是同一个目标,所以size方法返回3.
final ArrayList target = new ArrayList();
//第三种方式:把创建代理类,实例化对象合二为一,Proxy提供了一个方法newProxyInstance。
Collection proxy3 = (Collection) getProxy(target,new MyAdvice());
System.out.println(proxy3.add("zhangsan"));
proxy3.add("lisi");
proxy3.add("wangwu");
System.out.println(proxy3.size());
//Proxy继承Object的方法。只派发hashCode,equals,toString三个方法,其他的自己实现。
System.out.println(proxy3.getClass().getName()); //$Proxy0,不交给Handler。
}
//抽取出获得代理的方法,作成框架形式。需要目标和系统功能两个参数。
private static Object getProxy(final Object target,final Advice advice) { //为了以后不仅对集合封装,用Object
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
/*new Class[]{Collection.class}, */
target.getClass().getInterfaces(), //与target实现相同的接口
new InvocationHandler(){
public Object invoke(Object proxy, Method method,O bject[] args) throws Throwable {
/*long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endIime = System.currentTimeMillis();
System.out.println(method.getName()+" runned time is "+(endIime - beginTime));
return retVal; */
//做成框架,用如下代码实现。防止硬编码
advice.beforeMethod(method);
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
});
return proxy3;
}
}
//系统功能的接口,一般应该有四个方法。方法之前,之后,前后,异常处理。
import java.lang.reflect.Method;
public interface Advice {
void beforeMethod(Method method);//可以接收三个参数target,args,method。例如Spring框架。
void afterMethod(Method method);
}
//实现Advice方法。正是我们AOP编程的切面程序。
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
long beginTime;
//复写接口方法,实现我们的功能。
public void beforeMethod(Method method) {
System.out.println("来黑马学习了");
beginTime = System.currentTimeMillis();
}
public void afterMethod(Method method) {
System.out.println("从黑马毕业找到好工作了");
long endIime = System.currentTimeMillis();
System.out.println(method.getName()+" runned time is "+(endIime - beginTime));
}
}
动态代理类的工作原理图:
9,AOP框架。
1)BeanFactory专门用来获取各种对象。
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.util.Properties;
import com.itheima.day3.Advice;
public class BeanFactory {
Properties props = new Properties();
public BeanFactory(InputStream ips){
try {
props.load(ips);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name){
String className = props.getProperty(name);
Object bean = null;
try {
Class clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean){
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
Object proxy = null;
try {
//读取配置文件,得到目标和系统功能
Advice advice = (Advice) Class.forName((String)props.get(name+".advice")).newInstance();
Object target = Class.forName(props.getProperty(name+".target")).newInstance(); //为ProxyFactoryBean传递目标和系统功能。
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
//获取代理对象。
proxy = proxyFactoryBean.getProxy();
} catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
2)ProxyFactoryBean。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.itheima.day3.Advice;
public class ProxyFactoryBean {
private Advice advice;
private Object target;
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
//下面这段代码已经在代理中实现过。
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)测试类。
import java.io.InputStream;
import java.util.Collection;
public class AopFrameworkTest {
public static void main(String[] args) {
//得到配置文件。
InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
//得到字符串对应的对象。
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}
重点理解:Spring的两大核心技术Bean工厂和AOP。