一、XML
Xml格式的文件在接下来的框架学习尤为重要,比如在框架中的配置文件大多都是用的xml格式的文件来配置的,所以xml格式的文件创建与解析特别重要。
- XML概念
- XML(eXtensible Markup Language)是一种可扩展标识语言,是一种简单的数据存储语言,使用一系列简单的标记描述数据。目前推荐遵循的是W3C组织于2000年发布的XML1.0版本。
- XML语法规则
- 声明标签定在首行:<?xml version=“1.0” encoding=“utf-8|gb2312”?>
- 文档有且只有一个根元素
- 非空元素都必须有一对标签:起始/关闭标签
- 空元素的标签中同时表示起始和结束标签</>
- 大小写敏感
- 标签必须正确地嵌套
- 元素的属性必须有属性值且属性值必须加引号(单引或双引都可以)
- 在 XML 中,应使用实体引用来代替特殊字符
- 实体引用
- < 小于号< 【必须】
- > 大于号>
- & 和号& 【必须】
- ' 单引号‘
- " 双引号”
- 元素命名规则
- 名称可以含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字符 “xml”(或者 XML、Xml)开始
- 名称不能包含空格
- 可使用任何名称,没有保留的字词
- XML的特点
- XML 仅仅是纯文本
- XML 的设计宗旨是传输数据,而非显示数据
- XML 是独立于软件和硬件的信息传输工具
- XML 标签没有被预定义,需要自行定义标签
- Dom4j概念
- dom4j是一个Java的XML API,是jdom的升级品,用来读写XML文件的。dom4j是一个十分优秀的JavaXML API,具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom技术,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面还可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,所以可以知道dom4j无论在哪个方面都是非常出色的。如今可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这已经是必须使用的jar包, Hibernate也用它来读写配置文件。
- XML文档示例
<?xml version="1.0" encoding="UTF-8"?> <books> <!--This is a test for dom4j, holen, 2004.9.11--> <book show="yes"> <title>Dom4j Tutorials</title> </book> <book show="yes"> <title>Lucene Studing</title> </book> <book show="no"> <title>Lucene in Action</title> </book> <owner>O'Reilly</owner> </books>
- 解析XML文档
- 解析/创建都是基于dom4j实现
public static void main(String[] args) throws DocumentException { File file = new File("hello.xml"); SAXReader sax = new SAXReader(); Document doc = sax.read(file); Element rootElement = doc.getRootElement(); String name = rootElement.getName(); System.out.println("根节点="+name); //判断是否是根节点 System.out.println(rootElement.isRootElement()); Iterator it = rootElement.elementIterator(); while(it.hasNext()){ Element ele = (Element)it.next(); String eleName = ele.getName(); if(eleName.equals("book")){ Attribute attribute = ele.attribute(0); System.out.println(attribute.getName()+"="+attribute.getValue()); Iterator it2 = ele.elementIterator(); while(it2.hasNext()){ Element ele2 = (Element)it2.next(); System.out.println(ele2.getName()+"="+ele2.getText()); } }else{ System.out.println(ele.getText()); } } }
- 解析/创建都是基于dom4j实现
- 创建XML文档
public static void main(String[] args) throws IOException, XMLStreamException { Document document = DocumentHelper.createDocument(); Element booksElement = document.addElement("books"); booksElement.addComment("This is a test for dom4j, holen, 2004.9.11"); Element bookElement = booksElement.addElement("book"); bookElement.addAttribute("show","yes"); Element titleElement = bookElement.addElement("title"); titleElement.setText("Dom4j Tutorials"); /** 类似的完成后两个book */ bookElement = booksElement.addElement("book"); bookElement.addAttribute("show","yes"); titleElement = bookElement.addElement("title"); titleElement.setText("Lucene Studing"); bookElement = booksElement.addElement("book"); bookElement.addAttribute("show","no"); titleElement = bookElement.addElement("title"); titleElement.setText("Lucene in Action"); /** 加入owner节点 */ Element ownerElement = booksElement.addElement("owner"); ownerElement.setText("O'Reilly"); //用于格式化xml内容和设置头部标签 OutputFormat format = OutputFormat.createPrettyPrint(); //设置xml文档的编码为utf-8 format.setEncoding("utf-8"); Writer out = new FileWriter("hello2.xml"); XMLWriter writer = new XMLWriter(out,format); writer.write(document); writer.close(); }
二、反射
-
反射定义
反射即反向探知,有点像考古学家根据发掘的物品来探知以前的事情。
程序中的反射指程序运行状态中,
1、对于给定的一个类(Class)对象,可以获得这个类(Class)对象的所有属性和方法;
2、对于给定的一个对象(new XXXClassName<? extends Object>),都能够调用它的任意一个属性和方法。
这种动态获取类的内容以及动态调用对象的方法和获取属性的机制,就叫做Java反射机制
-
反射初识
public class User { public void eat(){ System.out.println("---eat---"); } } public class TestReflect{ public static void main(String[] args) throws Exception { Class<User> clazz = User.class; //获取类对象对应的属性和方法 System.out.println(clazz.getName()); System.out.println(clazz.getPackage()); System.out.println(clazz.getSuperclass()); System.out.println(clazz.getClassLoader()); //获取一个对象实例 User user = clazz.newInstance(); Method method = clazz.getDeclaredMethod("eat"); method.invoke(user); }
-
为何用反射
直接new User()不就可以了,为什么要用这种反射写法?
实例化一个User()对象,不使用反射,如果想再实例化其他对象比如new Person(),那么
就需要修改源代码,并重新编译。使用反射后就不需要修改源代码只需要灵活改动类描述就
可以了,不需要重新再编译。 如下代码所示
//类描述,可根据需要实例化的描述而改动 String className = "com.tledu.pojo.User"; //使用反射中API创建对象 Class.forName(className).newInstance();
-
反射的优缺点
-
优点:
增加程序的灵活性,避免固有逻辑写死到程序中
代码相对简洁,可以提高程序的复用性
-
缺点:
相比于直接调用反射有比较大的性能销毁
内部暴露和安全隐患
-
灵活性测试
public interface Ball { void playBall(); } public class BasketBall implements Ball{ @Override public void playBall() { System.out.println("打篮球"); } } public class FootBall implements Ball{ @Override public void playBall() { System.out.println("踢足球"); } } //---------------------------- public static void main(String[] args) { //System.out.println(getInstanceByKey("basket")); System.out.println(getInstanceReflectByKey("FootBall")); } public static Ball getInstanceByKey(String key) { if("foot".equals(key)){ return new FootBall(); } if("basket".equals(key)){ return new BasketBall(); } return null; }
如果我们想在添加一个乒乓球那么除了创建一个乒乓球类,还需要在方法中加入获取乒乓球对应的判断和乒乓球创建对象的返回,很显然不够灵活出现了写死代码的情况。
public static Ball getInstanceReflectByKey(String key) { String basePackage = "com.tledu.mjw"; Ball ball = null; try { Class clazz = Class.forName(basePackage + "." + key); ball = (Ball)clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } return ball; }
使用反射的方式获取对象,可以看出代码灵活了很多而且不需要改动创建返回对象的源码。
-
性能测试
public static void main(String[] args) { //System.out.println(getInstanceByKey("basket")); //System.out.println(getInstanceReflectByKey("FootBall")); long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++){ getInstanceByKey("basket"); //getInstanceReflectByKey("FootBall"); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); }
为什么这么慢呢?(分别查看forName()方法和newIntance()方法源码分析)
1、调用了native方法
2、每次调用newInstance()方法都会做安全检查,比较耗时
-
反射的操作
-
获取类对象的四种方式
//获取类对象的四种方式 Class<User> clazz1 = User.class; Class<?> clazz2 = Class.forName("com.tledu.mjw.User"); Class<? extends User> clazz3 = new User().getClass(); Class<?> clazz4 = Demo3.class.getClassLoader().loadClass("com.tledu.mjw.User");
-
类中基本信息的获取
System.out.println(clazz1.getClassLoader()); System.out.println(clazz1.getSuperclass()); System.out.println(clazz1.getPackage()); System.out.println(clazz1.getModifiers()); //获取类的修饰符 System.out.println(clazz1.getName()); System.out.println(clazz1.getSimpleName()); System.out.println(clazz1.getInterfaces().length);//获取类实现的所有接口 System.out.println(clazz1.getAnnotations().length);
-
类中字段的操作
创建User类继承Person类添加相应属性
public class Person { public String idCard; private String userName; } public class User extends Person{ private String name; public String sex; public static String address; public void eat(){ System.out.println("---eat---"); } }
测试方法调用
public static void main(String[] args) throws Exception { Class<User> userClass = User.class; User user = userClass.newInstance(); //getFields()获取的是本类及父类中的公共属性 Field[] fields = userClass.getFields(); for (Field field : fields) { System.out.println(field.getModifiers() + " " + field.getName()); } System.out.println("---------------"); //getDeclaredFields()获取本类中的所有属性 Field[] fields2 = userClass.getDeclaredFields(); for (Field field : fields2) { System.out.println(field.getModifiers() + " " + field.getName()); } System.out.println("---------------"); //获取name字段对应的field Field nameField = userClass.getDeclaredField("name"); //如果需要修改私有属性信息那么我们要放开权限 nameField.setAccessible(true); nameField.set(user,"天亮教育"); System.out.println(nameField.get(user)); //System.out.println(user.getName()); System.out.println("---------------"); //如何对静态的属性赋值 Field addressField = userClass.getDeclaredField("address"); addressField.set(null,"Jack"); System.out.println(addressField.get(null)); //System.out.println(User.address); }
-
类中方法的操作
在User类中将eat方法改为私有的,并添加静态方法say
private void eat(){ System.out.println("---eat---"); } public static void say(String msg){ System.out.println(msg); }
在Person类中添加共有的fn1()方法和私有的fn2()方法
public void fn1(){} private void fn2(){}
测试方法调用
public static void main(String[] args) throws Exception { Class<User> userClass = User.class; User user = userClass.newInstance(); //获取本类及父类中的公共方法 Method[] methods1 = userClass.getMethods(); for (Method method : methods1) { System.out.println(method.getModifiers() + " " + method.getName()); } System.out.println("--------------"); //获取本类中所有的方法包括私有 Method[] methods2 = userClass.getDeclaredMethods(); for (Method method : methods2) { System.out.println(method.getModifiers() + " " + method.getName()); } System.out.println("--------------"); //调用方法执行 Method eatMethod = userClass.getDeclaredMethod("eat"); //调用私有方法时要先开放权限 eatMethod.setAccessible(true); eatMethod.invoke(user); System.out.println("--------------"); //调用静态的方法 Method sayMethod = userClass.getDeclaredMethod("say",String.class); sayMethod.invoke(null,"你好"); }
-
类中构造器的操作
在User中添加如下构造方法
public User() { } public User(String name) { this.name = name; } private User(String name,String sex) { this.name = name; this.sex = sex; }
通过反射操作User中的构造方法
public static void main(String[] args) throws Exception { Class<User> userClass = User.class; //获取公共的构造器 Constructor<?>[] constructors = userClass.getConstructors(); for (Constructor<?> constructor : constructors) { System.out.println(constructor.getModifiers() + " " + constructor.getName()); } System.out.println("------------------"); //获取所有的构造器 Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors(); for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor.getModifiers() + " " + declaredConstructor.getName()); } //1、直接通过newInstance创建对象 User user = userClass.newInstance(); //2、获取对应的Constructor对象获取实例 Constructor<User> constructor = userClass.getDeclaredConstructor(String.class, String.class); //操作私有的构造器要先打开权限 constructor.setAccessible(true); User user1 = constructor.newInstance("乔森", "男"); }
-
-
- 反射破局单例
-
单例模式
public class PersonSingle { private static PersonSingle instance = null; private PersonSingle(){} public static PersonSingle getInstance(){ if(instance == null){ instance = new PersonSingle(); } return instance; } }
测试单例、通过反射可以调用私有构造
public static void main(String[] args) throws Exception { PersonSingle instance1 = PersonSingle.getInstance(); PersonSingle instance2 = PersonSingle.getInstance(); PersonSingle instance3 = PersonSingle.getInstance(); System.out.println(instance1); System.out.println(instance2); System.out.println(instance3); System.out.println("---------------------"); Class<PersonSingle> personSingleClass = PersonSingle.class; Constructor<PersonSingle> declaredConstructor = personSingleClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); System.out.println(declaredConstructor.newInstance()); }
改进单例如下
private PersonSingle(){ if(instance != null){ throw new RuntimeException("实例已经存在不允许再创建") } }
-
- 反射使用场景
- jdbc封装
- SpringIOC
- JdbcTemplate
- Mybatis
三、注解
-
注解的概念
注释:用文字描述程序,给程序员看的
/* * 多行注释 */ // 单行注释
注解:说明程序的,给计算机看的
定义:
注释(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用:
- 编写文档:通过代码里标识的注释生成文档【生成doc文档】
- 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
- 生成文档演示:
创建User类
/** * 注解JavaDoc描述 * @author mark * @since jdk1.5 * @version 1.0 */ public class User { private Integer id; private String userName; @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + '}'; } /** * 两数相加 * @param a 第一个整数 * @param b 第二个整数 * @return 返回两数和 */ public int add(int a,int b){ return a+b; } }
执行命令生成说明文档:javadoc User.java
-
JDK预定义的注解
@Override:检查被该注解标注的方式是否是继承自父类【接口】
@Deprecated:该注解表示注释的内容过时
@SuppressWarnings:压制警告
代码演示:
import java.util.Date; @SuppressWarnings("all") public class AnnoDemo01 { @Override public String toString() { return "AnnoDemo01{}"; } @Deprecated public void show1(){ //发现过时了,功能跟不上需求了 } //@SuppressWarnings("all") public void show2(){ //功能更加强大的方法 } public void demo(){ show1(); //不推使用但可以使用 //show2(); Date date = new Date(); date.getYear(); } }
-
自定义注解
查看jdk预定义的注解源码发现格式如下
public @interface 注解名{ }
自定义注解 MyAnno
public @interface MyAnno{ }
执行javac MyAnno.java 生成MyAnno.class
执行javap MyAnno.java 将class文件反编译
Compiled from "MyAnno.java" public interface MyAnno extends java.lang.annotation.Annotation { }
注解的本质其实就是一个接口,继承Annotation父接口
属性:在接口中定义的抽象方法
public @interface MyAnno{ String show1(); Int show2(); String[] show3(); MyAnno2 show4(); PsersonEnum show5(); }
返回结果必须是如下类型
- 基本数据类型
- String类型
- 枚举类型
- 注解
- 以上类型的数组
-
如何来用这些属性呢?
public @interface MyAnno{ String name(); int age(); //default 18; }
赋值给属性
@MyAnno(name="tom",age=18) public void demo(){ }
如果注解类中的属性是value且只有一个属性,那么可以再赋值的时候省去value
public @interface MyAnno{ String value(); //String name(); //int age(); //default 18; }
给注解中只有一个value的属性赋值
//@MyAnno(value="tom") @MyAnno("tom") public void demo(){ }
在注解类中添加注解类型、枚举类型、数组类型的属性并赋值
public @interface MyAnno{ String value(); String[] show3(); MyAnno2 show4(); PsersonEnum show5(); }
给不同类型的属性赋值
//@MyAnno(value="tom") @MyAnno(value="tom",show3={“a”,”b”},show4=@MyAnno2,show5=PsersonEnum.name}) public void demo(){ }
属性赋值注意点:
- 如果定义属性时,使用default关键字给属性默认初始值,可以在使用注解时不赋值
- 如果只有一个属性需要赋值,而且该属性的名称是value,那么赋值时value可以省略
- 数组赋值的时候,使用{}括起来,如果数组中只有一个值,那么{}可以省略
-
元注解
JDK中给我们提供的4个元注解
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface MyAnno2 { }
- @Target:描述当前注解能够作用的位置
-
ElementType.TYPE: 可以作用在类上
-
ElementType.METHOD:可以作用在方法上
-
ElementType.FIELD: 可以作用在成员变量上
-
- @Retention:描述注解被保留到的阶段
-
SOURCE<CLASS<RUNTIME
-
SOURCE:表示当前注解只在代码阶段有效
-
CLASS:表示该注解会被保留到字节码阶段
-
RUNTIME:表示该注解会被保留到运行阶段JVM
-
自定义的注解:RetentionPolicy.RUNTIME
-
-
@Documented:描述注解是否被抽取到JavaDoc api中
- @inherited:描述注解是否可以被子类继承
- @Target:描述当前注解能够作用的位置
- 自定义注解案例
-
创建InvokAnno注解
/** * 自定义注解 * 该注解表示要执行哪个类中的哪个方法 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface InvokAnno { String className(); String methodName(); }
创建Student1类
public class Student1 { public void show(){ System.out.println("show1方法"); } }
创建MyMain测试类进行测试
@InvokAnno(className = "com.tledu.anno2.Student2",methodName = "show") public class MyMain { public static void main(String[] args) throws Exception { Class<MyMain> clazz = MyMain.class; InvokAnno anno = clazz.getAnnotation(InvokAnno.class); /** * 注解本质是接口 获取到的其实是接口实现 * public class MyInvokAnno implements InvokAnno{ * String className(){ * return "com.tledu.anno2.Student1"; * } * String methodName(){ * return "show1"; * } * } */ String className = anno.className(); String methodName = anno.methodName(); Class<?> aClass = Class.forName(className); Method method = aClass.getDeclaredMethod(methodName); Object o = aClass.newInstance(); method.invoke(o); } }
-