---------------------- <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! ----------------------
反射就是把Java类中的各中成分映射成相应的Java类。一个类中的每个成员都可以用相应的的反射API类的一个实例对象来表示。通过调用Class类的方法可以得到这些实例对象。
(一)Constructor类(构造函数的反射)
代表Java类中的构造方法。
1、得到某个类所有的构造方法:Constructor[] getConstructors();
2、得到某一个构造方法:Constructor getConstructor();通过参数的类型来区别要得到的是哪个构造方法。
package cn.itcast.day1;
import java.lang.reflect.Constructor;
public class ReflectTest {
public static void main(String[] args) throws Exception{
//用反射的方法创建String类的对象,使用带StringBuffer()参数的构造方法
//1.通过String类的字节码对象,获取其带StringBuffer()参数的构造函数
Constructor constructor1=
String.class.getConstructor(StringBuffer.class);
//2.用这个构造方法创建对象
String str2 =(String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str2.charAt(2));
}
}
3、Class.newInstance()方法:直接调用无参的构造函数,创建这个类的实例。
(二)Field类(成员变量的反射)
Field类代表Java类中的成员变量。
1.获取费私有成员变量的方法: Field getField();只能返回类中非私有的成员变量。类中私有的成员变量对这个方法是不可见的。
2.获取私有成员变量的方法:Field getDeclareField();
3.要想取得私有成员在某个对象中的取值,还要把这个私有成员设置成可得到的:方法是setAccessible(true);
package cn.itcast.day1;
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
package cn.itcast.day1;
import java.lang.reflect.Field;
public class ReflectTest1 {
public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3,5);
//获取非私有的成员变量y的字节码对象
Field fieldy = pt1.getClass().getField("y");
//获取该成员变量在某个特定实例中的值,注意fieldy不是某个类身上的成员变量,而是类的成员变量
System.out.println(fieldy.get(pt1));
//获取私有成员变量x的字节对象
Field fieldx = pt1.getClass().getDeclaredField("x");
//获取私有成员变量在特定对象中的值,要先把私有成员变成可得到的后才能取值
fieldx.setAccessible(true);
System.out.println(fieldx.get(pt1));
}
}
(三)Method类
代表Java类中的成员方法类。获取该类对象的方法是:Method getMethod();
package cn.itcast.day1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception{
String str1 = "abc";
//获取String类的charAt(int)方法
Method methodCharAt = String.class.getMethod("charAt",int.class);
//在某个对象上使用这个方法
System.out.println(methodCharAt.invoke(str1, 2));
//注意:invoke(null,orgs)这时所使用的是静态方法,没有对象。
}
}
事例:用反射的方式执行某个类中的main方法:被调用的类是在运行的时候传进去的。
package cn.itcast.day1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception{
//用反射的方式执行某个类中的main方法
String startClassName = args[0];
Method mainMethod = Class.forName(startClassName).getMethod("main",String[].class );
//使用这个main方法
// mainMethod.invoke(null,new Object[]{new String[]{"aaa","bbb","vvv"}});//在这儿要把参数再封装一下
//第二种封装的方法
mainMethod.invoke(null, (Object)new String[]{"aaa","bbb","vvv"});
}
}
class TextArguments
{
public static void main(String[] args)
{
for(String arg:args)
{
System.out.println(arg);
}
}
}
(四)数组的反射类--Array类
1.具有相同维数和相通元素类型的数组属于同一个类型,即具有相同的Class实例对象。
package cn.itcast.day1;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectTest {
int[] a1= new int[]{1,2,3};
String[] a2 = new String[]{"eage","erge","ergeg"};
printObject(a1);
printObject("dfseg");
}
private static void printObject(Object obj) {
// TODO Auto-generated method stub
Class objClass = obj.getClass();
if(objClass.isArray())
{
int len = Array.getLength(obj);
for(int i=0;i<len;i++)
{
System.out.println(Array.get(obj, i));
}
}
else
{
System.out.println(obj);
}
}
}
框架的概念及用反射技术开发框架的原理
简单的说框架就是已经写好了的程序如何调用还没有写的类。
1.建立配置文件
2.在要用到的程序读取配置文件
package cn.itcast.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
//读取配置文件建立文件读取流对象
InputStream is = new FileInputStream("config.properties");
Properties prop = new Properties();
prop.load(is);
is.close();
//Collection collection = new HashSet();
//反射在框架下的应用,一定记住用完整的路径,这个绝对路径是运算出来的
Collection collection =
(Collection)Class.forName(prop.getProperty("className")).newInstance();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(4,4);
ReflectPoint pt3 = new ReflectPoint(5,5);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
}
*****************************************************************************************************************************************************************************************
用类加载器的方式管理资源和配置文件
package cn.itcast.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
//读取配置文件建立文件读取流对象
InputStream is = new FileInputStream("config.properties");
Properties prop = new Properties();
prop.load(is);
is.close();
// Collection collection = new HashSet();
//反射在框架下的应用,一定记住用完整的路径,这个绝对路径是运算出来的
Collection collection =
(Collection)Class.forName(prop.getProperty("className")).newInstance();
//用类加载器来加载配置文件的演示
//Collection collection=
ReflectTest2.class.getClassLoader().getResourceAsStream(cn/itcast/day1/config.properties);
//用类直接加载文件
//ReflectTest2.class.getResourceAsStream("config.properties");
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(4,4);
ReflectPoint pt3 = new ReflectPoint(5,5);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
}
*****************************************************************************************************************************************************************************************
内省-----了解JavaBean
(一)JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。方法名都是以set和get开头。
(二)对JavaBean的简单的内省操作:用到一个API 是PropertyDescriptor
package cn.itcast.day1;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpecterTest {
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3,5);
//JavaBean的内省操作,获取x属性的值
//System.out.println(pt1.getX());
String propertyName = "x";
//获取对类中x属性描述的对象
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
//用内省的方法设置x属性的值,想要设置给个属性的值,就必须先得到这个属性对象
Integer value = 7;
//PropertyDescriptor pd1 =
new PropertyDescriptor("x",pt1.getClass());
//Method methodSetX=pd1.getWriteMethod();
//methodSetX.invoke(pt1, value);
setProperty(pt1,propertyName,value);
System.out.println(pt1.getX());
}
private static Object getProperty(Object pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor pd =
new PropertyDescriptor(propertyName,pt1.getClass());
//通过这个属性对象获取读取该属性的方法
Method methodGetX =pd.getReadMethod();
//通过返回属性x的值得方法来获取pt1对象中的x的值
Object retVal = methodGetX.invoke(pt1);
return retVal;
}
private static void setProperty(Object pt,String propertyName,Object value)throws Exception{
PropertyDescriptor pd2=
new PropertyDescriptor(propertyName,pt.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt, value);
}
}
(三)对JavaBean的复杂内省操作----introSpecttor类
采用遍历BeanInfo的所有属性的方式来查找和设置某个ReflectPoint对象的X属性。在程序中把一个类当做JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息。
package cn.itcast.day1;
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;
public class IntroSpecterTest {
public static void main(String[] args)throws Exception {
ReflectPoint pt1 = new ReflectPoint(3,5);
Object retVal = getProperty1(pt1,propertyName);
System.out.println(retVal);
}
private static Object getProperty1(Object pt1,String propertyName)throws Exception{
Object value = null;
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor pd:pds)
{
if(pd.getName().equals(propertyName))
{
Method methodGetX = pd.getReadMethod();
methodGetX.invoke(pt1);
break;
}
}
return value;
}
}
(四)使用BeanUtils工具包操作JavaBean
1.导入BeanUtils的jar包和logging的jar包
//获取属性的值
BeanUtils.getProperty(pt1,"x",);
//设置属性的值
BeanUtils.setProperty(pt1,"x","9")//注意:参数的类型都是字符串
*****************************************************************************************************************************************************************************************
注解
注解是向javac或者开发工具传递某种信息的标记。每一个注解都是一个类。注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,javac编译器、开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,有什么样的标记,就去做相应的操作。标记可以加在包,类,字段,方法的参数以及局部变量上。
(一)注解的定义与反射调用
1、基本注解:Deprecated、Override、SuppressWarning
2、注解的生存阶段:java源文件、class文件、内存(运行阶段)。
3、标注注解生存阶段的方法:可以在注解上再添加注解(也叫元注解)并标明被注解的注解存在于那个阶段。@Retention(RetentionPolicy.SOURCE)(注解存在于源 文件),@Retention(RetentionPolicy.CLASS),
@Retention(RetentionPolicy.RUNTIME)
package cn.itcast.day2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//让自定义注解生存在运行阶段
@Retention(RetentionPolicy.RUNTIME)
//设置子定义注解的要作用的成分
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ItcastAnnotation {
}
package cn.itcast.day2;
@ItcastAnnotation
public class AnnotationTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//用反射的方法查看标注到该类上的自定义注解是否存在
if(AnnotationTest.class.isAnnotationPresent)(ItcastAnnotation.class))
{
ItcastAnnotation annotation =(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class );
System.out.println(annotation);
}
}
}
(二)为注解增加各种属性
package cn.itcast.day2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.itcast.day1.EnumTest;
//让自定义注解生存在运行阶段
@Retention(RetentionPolicy.RUNTIME)
//设置子定义注解的要作用的成分
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ItcastAnnotation {
//为注解添加属性,注解类似于接口,定义属性以方法的形式定义
String color() default "blue";//设置属性的默认值
//value特殊的属性名,如果注解中只有该属性要设置值,则不需要写该属性名称,直接写值即可。
String value();
//为注解增加数组类型的属性,并为数组属性设置默认值
int[] arrayAtrr() default {3,4,5};
//为注解增加枚举属性,并增加默认值
EnumTest.TrafficLamp lamp()default EnumTest.TrafficLamp.RED;
//为注解添加注解类型的属性,并赋默认值
MetaAnnotation annotation1() default @MetaAnnotation("vae");
}
对各种注解属性的初始化实例:
package cn.itcast.day2;
//在使用注解实例时就要为注解中的属性赋值,以及数组属性赋值的方法,在加上为注解类型的属性赋值
@ItcastAnnotation(color="red",value="sfwe",arrayAtrr={1,2,3},annotation1=@MetaAnnotation("ifuh"))
public class AnnotationTest {
/**
* @param args
*/
//只需给value属性赋值时,就不用写属性名value了
@ItcastAnnotation("sfgewg")
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//用反射的方法查看标注到该类上的自定义注解是否存在
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){
ItcastAnnotation annotation =(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class );
//在查看注解属性时,要以方法形式调用
System.out.println(annotation.color());
//查看value属性
System.out.println(annotation.value());
//查看数组属性
System.out.println(annotation.arrayAtrr().length);
//查看枚举属性
System.out.println(annotation.lamp().nextLamp().name());
//查看注解属性
System.out.println(annotation.annotation1().value());
}
}
}
*****************************************************************************************************************************************************************************************
类加载器
(一)类加载器的介绍
Java提供的加载类的工具,也是一个java类。Java虚拟机安装多个类加载器,系统默认三个主要类加载器,每个类加载器负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader。类加载器也是Java类,那么类加载器本身也要被类加载器,显然必须有一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有累加器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其制定一个父级类加载器或者默认采用系统类加载器为其父级类加载器。
package cn.itcast.day2;
public class ClassLoaderTest{
public static void main(String[] args){
// TODO Auto-generated method stub
//查看ClassLoaderTest是哪个类加载器加载的
System.out.println(ClassLoaderTest.class.getClassLoader().getClass()
.getName());
//查看System类是由哪个类加载器加载的,这个类是由BootStrap加载的,它不 是java类,所以名为空
System.out
.println(System.class.getClassLoader());
//查看类加载器的结构
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader!=null)
{
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
}
}
(二)类加载器的委托机制
1.首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。还可以直接调用ClassLoder.loadClass()方法来指定某个类加载器去加载某个类。
2、每个类加载器加载类时,又现委托给其上级类加载器。当所有的祖宗类加载器都没有加载到类时,则回到发起者类加载器,如果还加载不了,则抛出ClassNoFoundException,而不是去找发起者类加载器的儿子,因为没有getChild方法。
(三)自定义类加载器
1、自定义类加载器必须继承ClassLoader,使用父类LoadClass()方法和defineClass()方法,覆盖findClassLoader()方法。
十四、代理
(一)代理的概念及作用
1.代理可以为已存在的多个具有相同接口的目标的各个方法增加一些系统功能,如:异常处理、日志、计算方法的运行时间、事务管理、等等。
2、编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法加上系统功能的代码。
(二)AOP(Aspect oriented program)现象方面的编程
交叉业务的编程问题即为面向方面的编程,目标就要是交叉业务模块化,可以采用将将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一致的。
(三)动态代理技术
1.JVM可以在运行时期动态生成类的字节码,这种动态生成的类往往被用做代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
2.CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
3.代理类的给个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,换可以在代码方法中的如下四个位置加上系统功能代码:
a.在调用目标方法之前
b.在调用目标方法之后
c.在调用目标方法前后
d.在处理目标方法异常的代理快中
4.JVM动态代理类的创建演示:
package cn.itcast.day3;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//JVM使用Proxy类动态生成类演示
Class clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
//利用动态生成的代理类来获取它的构造方法
Constructor[] constructors=clazzProxy1.getConstructors();
for(Constructor constructor: constructors)
{
String name =constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
//取出构造方法的参数
Class[]clazzParameters=constructor.getParameterTypes();
for (Class clazzParameter:clazzParameters)
{
sBuilder.append(clazzParameter.getName()).append(',');
}
//去掉最后一个‘,’
if(clazzParameters.length!=0&&clazzParameters!=null)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
Class clazzMethod1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
//利用动态生成的代理类来获取它的构造方法
Method[] clazzMethods=clazzMethod1.getMethods();
for(Method method: clazzMethods)
{
String name =method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
//取出构造方法的参数
Class[]clazzParameters=method.getParameterTypes();
for (Class clazzParameter:clazzParameters)
{
sBuilder.append(clazzParameter.getName()).append(',');
}
//去掉最后一个‘,’
if(clazzParameters.length!=0&&clazzParameters!=null)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
}
}
5、创建动态类的实例对象及调用其方法
package cn.itcast.day3;
//这个借口是为InvocationHandler服务的
public interface Advice {
void beforeMethod();
void afterMethod();
}
//接口的实现类
package cn.itcast.day3;
public class MyAdvice implements Advice {
long beginTime = 0;
@Override
public void beforeMethod() {
// TODO Auto-generated method stub
beginTime = System.currentTimeMillis();
}
@Override
public void afterMethod() {
// TODO Auto-generated method stub
long endTime = System.currentTimeMillis();
System.out.println(endTime-beginTime);
}
}
package cn.itcast.day3;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
/**
* @param args
*/
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
//JVM使用Proxy类动态生成类演示
Class clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
//利用动态生成的代理类来获取它的构造方法
Constructor[] constructors=clazzProxy1.getConstructors();
for(Constructor constructor: constructors)
{
String name =constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
//取出构造方法的参数
Class[]clazzParameters=constructor.getParameterTypes();
for (Class clazzParameter:clazzParameters)
{
sBuilder.append(clazzParameter.getName()).append(',');
}
//去掉最后一个‘,’
if(clazzParameters.length!=0&&clazzParameters!=null)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
Class clazzMethod1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
//利用动态生成的代理类来获取它的构造方法
Method[] clazzMethods=clazzMethod1.getMethods();
for(Method method: clazzMethods)
{
String name =method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
//取出构造方法的参数
Class[]clazzParameters=method.getParameterTypes();
for (Class clazzParameter:clazzParameters)
{
sBuilder.append(clazzParameter.getName()).append(',');
}
//去掉最后一个‘,’
if(clazzParameters.length!=0&&clazzParameters!=null)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
//创建动态生成类的实例对象及调用其方法,因为动态生成类中只有一个带参数的构造方法所以不能直接用类来创建对象。
//要创建对象只能获取该类有参数的构造方法了
Constructor constructor =
clazzProxy1.getConstructor(InvocationHandler.class);
//用构造方法来创建对象
Collection proxy1=
(Collection)constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);
proxy1.clear();
//用匿名内部类的方式创建实例对象
Collection proxy2 =(Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
//目标,方法里的内部类只能访问被final修饰的局部成员变量
final ArrayList target =new ArrayList();
//用proxy类的静态方法newProxyInstance直接把上面的方法和二为一
Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
proxy3.add("zhangsan");
proxy3.add("lisi");
proxy3.add("wangwu");
System.out.println(proxy3.size());
}
private static Object getProxy(final Object target,final Advice advice) {
Object proxy3 = Proxy.newProxyInstance(
//Collection.class.getClassLoader(),
target.getClass().getClassLoader(),
//new Class[]{Collection.class},
target.getClass().getInterfaces(),
new InvocationHandler(){
//在这里就是代理和目标的集合地,创建一个目标
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(endTime-beginTime);
return retVal;*/
advice.beforeMethod();
Object retVal = method.invoke(target, args);
advice.afterMethod();
return retVal;
}
});
return proxy3;
}
}
class MyInvocationHandler1 implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
---------------------- <a href="http://edu.csdn.net/heima" target="blank">android培训</a>、<a href="http://edu.csdn.net/heima" target="blank">java培训</a>、期待与您交流! ----------------------
详细请查看:<a href="http://edu.csdn.net/heima" target="blank">http://edu.csdn.net/heima</a>