学习概述:Java 内省和Java Bean.java 注解,以及框架的部分知识
学习目标:理解内省的概念,学会使用内省来操作Java Bean,对于Java EE框架大脑里有一个大体的概念。学会使用Beanutils,以及对注解的反射应用,注解往往是自己以前比较忽略的部分,认为注解只是程序的注释,除了增强程序的可读性之外没什么作用,这种想法是错误的,对于这部分知识,一定要深入掌握,改变这种错误观点。
反射的最大价值:实现框架
1.框架与框架要解决的核心问题
框架与工具类的区别:工具类被用户类调用,而框架则是调用用户提供的类。
框架要解决的核心问题:因为在写程序时才知道要被调用的类名,所以在程序中无法采用new关键字创建实例对象,而要使用反射的方式来做。
模拟通过配置文件得到ArrayList实例对象(反射应用)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class TestReflect {
public static void main(String[] args){
try{
InputStream ips = new FileInputStream("test.properties");
Properties prop = new Properties();
prop.load(ips);
String classname = (String) prop.get("classname");
try{
Class<?> coll = Class.forName(classname);
@SuppressWarnings("unchecked")
Collection<String> a =(Collection<String>) coll.newInstance();
a.add("abc");
a.add("abc");
System.out.print(a.size());
}
catch(ClassNotFoundException e){
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
小细节:配置文件应该放在什么地方?在实际项目中,没有用相对路径的,一种解决方式是采用绝对路径。但是绝对路径不能用硬编码,而是计算出来。可以采用类加载器,示例如下:class.getClassLoader().getResourceStream( 文件名);
2.内省及引出JavaBean
什么是JavaBean,Javabean是一种特殊的Java类,主要用于传递的数据信息,类中的属性均为私有的,所有的属性都有get和set方法。外部程序不能直接访问Java Bean的状态信息,这种类中的方法主要用于访问私有变量,Java Bean是对面向对象三大特征之一:封装的完美诠释。
如果想在Java模块之间传递很多信息,可将这些新意一起封装到一个JavaBean中,特别是在Java EE开发中,Java Bean有广阔的应用,也被称为值对象(VO)。
一个Java Bean实例:
public class Person{
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return this.name;
}
public void setName(String name){
this.name=name;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age=age;
}
}
内省(Introspector)是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则(但你最好还是要搞清楚),这些API存放于包java.beans中。一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法
<1>简单内省操作
对Java Bean的简单内省操作比较容易。Java SAPI已经提供了相关操作类:PropertyDescriptor,示例代码如下:
public class IntroSpectorDemo {
/**
* @param args
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws IntrospectionException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, IntrospectionException, InvocationTargetException, NoSuchMethodException {
// TODO Auto-generated method stub
Person p = new Person(1,"james");
PropertyDescriptor pd = new PropertyDescriptor("name", Person.class);
Method mR = pd.getReadMethod();
Object object = mR.invoke(p);
System.out.println(object);
//得到bean的set方法改变bean的值
Method mW = pd.getWriteMethod();
mW.invoke(p,"kobe");
}
}
<2>复杂内省操作
实际上复杂内省操作也不算复杂,因为api也提供了相关类:Introspector,甚至我们还有更为专业的工具类BeanUtils操作bean。示例代码如下
import org.apache.commons.beanutils.BeanUtils;
/*
* javabean的内省操作,如果不适用内省,那么我们只能用反射的方法得到bean的get和set方法,有了内省之后操作方便了很多。
*/
public class IntroSpectorDemo {
/**
* @param args
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws IntrospectionException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, IntrospectionException, InvocationTargetException, NoSuchMethodException {
// TODO Auto-generated method stub
Person p = new Person(1,"james");
PropertyDescriptor pd = new PropertyDescriptor("name", Person.class);
Method mR = pd.getReadMethod();
Object object = mR.invoke(p);
System.out.println(object);
//得到bean的set方法改变bean的值
Method mW = pd.getWriteMethod();
mW.invoke(p,"kobe");
System.out.println(p.getName());
//使用IntroSpector进行复杂内省操作
BeanInfo beanInfo = Introspector.getBeanInfo(p.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor:pds){
System.out.println(propertyDescriptor.getReadMethod().getName());
}
//使用更加专业省事的工具类操作Java Bean
//System.out.println(BeanUtils.getProperty(p, "name"));
BeanUtils.setProperty(p, "age", "15");
Field f = Person.class.getDeclaredField("age");
f.setAccessible(true);
System.out.println(f.getInt(p));
}
}
注意:使用BeanUtils是不要忘记导入两个jar包:commons-beanutils.jar和commons-logging.jar.
3.注解的定义与反射调用
从JDK1.5开始,Java增加了对元数据的支持,也就是Annotation,其实注解是代码里的特殊标记,这些标记既可以在编译,类加载和运行时被读取,并执行相应的处理。通过使用注解,程序员在不改变原有逻辑的情况下,在原文件嵌入一些有用的信息。注解能被用来微程序元素设置元数据。值得提出的是:注解不影响代码的执行,无论删除或是添加注解,代码会如一的执行。
<1> Java提供了三类基本注解,如下:@Override,@Deprected,@SuppressWarnings。其中@Override表示限定重写父类,用来指定方法重载,他可以强制一个子类必须覆盖父类的方法。@Deprecated用来表示某个程序元素已过时,当其他程序使用已过时的类或者方法时,编译器会给出警告。@SuppressWarnings用来取消指定的编译器警告。
<2>自定义Annotation
定义新的注解类型使用@interface关节子,定义一个Annotation类型和定义一个接口非常像,如下所示:
public @interface Test{
}
定义了这个Annotation之后,就可以在程序的任何地方用来使用该Annotation,使用Annotation时的语法非常类似于public,final这样的修饰符。通常可用于修饰程序中的类,方法,变量,接口等定义,通常我我们应当把Annotation放在所有修饰符前面 。
一个自定义注解的实例:
package com.lee.Annotation;
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)
public @interface MyAnnotation {
}
package com.lee.Annotation;
//编写一个应用自定义注解的测试类
public class AnnotationDemo2 {
@MyAnnotation
public static void method1(){}
@MyAnnotation
public static void method2(){}
public static void method3(){}
}
package com.lee.Annotation;
import java.lang.reflect.Method;
//反射得到方法的注释,以有没有方法注释作为标准判定该方法是否应该执行
public class TestProcessor {
public static void process(String clazz) throws Exception, ClassNotFoundException{
int passed=0;
int failed=0;
//反射得到类的所有方法
for(Method m:Class.forName(clazz).getMethods()){
if(m.isAnnotationPresent(MyAnnotation.class)){
//测试的方法都是static修饰的
m.invoke(null);
passed++;
}
else{
failed++;
}
}
int methodSum = passed+failed;
System.out.println("共运行了: "+methodSum+ "方法");
System.out.println("成功了:" +passed+"个方法");
}
public static void main(String[] args) throws ClassNotFoundException, Exception{
process("com.lee.Annotation.AnnotationDemo2");
}
}
<3>带成员变量的Annotation,成员变量在Annotation中以无参数方法的形式用来声明。其方法名和返回值定义了该成员的名字和类型,如下所示:
public @interface Test{
String name();
int age();
}
特别要注意的是:一旦使用了带有成员变量的Annotation时,需要为该Annotation中的变量赋值,如下所示
public class Test{
@Test(name="abc",age=6)
public void info(){
... ...
}
}
4. 提取注解信息
Java使用Annotation接口来代表程序前面的注释,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下实现类:Class: 类定义。Constructor:构造器定义。Field:类成员变量定义。Method :类方法定义。Package:类的包定义。
该包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect包所提供的反射API扩充了读取运行的注解的能力。当一个注解类型被定义为运行时的Annotation后,该注解才是运行可见的,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement接口是所有程序元素的父接口,所以程序通过反射获取某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下三个方法来访问Annnotation信息:
<1>getAnnotation(Class<T> annotationClass):翻译该程序元素上存在的注解,注定类型的注解。如果该类型不存在,则返回null
<2>Annotation[ ] getAnnotations():该返回程序元素上所存在的注释
<3>boolean isAnnotationPresent(Class<?extends Annotation>AnnotationClass):判断该程序元素上是否包含指定类型的注释,存在则返回true,否则则返回false
示例代码1.一个注解的最简单应用
package com.lee.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
}
package com.lee.Annotation;
import java.lang.annotation.Annotation;
/**
*
* @李亮亮
* 自定义注解的应用
*/
@AnnotationDemo
public class AnnotationTest {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
//先判断注解在不在
if(AnnotationTest.class.isAnnotationPresent(AnnotationDemo.class)){
Annotation annotation = AnnotationTest.class.getAnnotation(AnnotationDemo.class);
System.out.println(annotation);
}
}
}
5.JDK的元Annotation
JDK除了在java.lang下提供了3个基本Annotation之外,还在java。lang。annotation包下提供了四个Meta Annotation,这四个Annotation都是用于修饰其他Annotation定义
<1> @Retention 只能用于修饰一个Annotation定义,用于指定该Annotation可以被保留多长时间,其中使用该注解时必须为value成员变量赋值,value成员变量有三个:
【1】RetentionPolicy.CLASS:编译器会将注释记录在class文件中。当运行Java程序时,JVM不再保留注释。这是默认值。
【2】RetentionPolicy.RUNTIME:编译器会将注释记录在class文件中。当运行Java程序时,JVM会保留注释,程序可以通过反射获取该注释。
【3】RetentionPolicy.Source:编译器直接丢弃这种策略的注释
<2>@Documented用于该元Annotation修饰的Annotation类将被javadoc工具提取成文档。
<3> @Inherited 指定被他修饰的将具有继承性:如果某个Annotation使用了@Inherited修饰,则其子类将自动具有父类注释
<4> @Target 用于被指定被修饰的Annotation能用于修饰那些程序元素。
【1】ElementType.ANNOTATION_TYPE:表示定义的Annotation只能修饰Annotation
【2】ElementType.CONSTRUCTOR:表示定义的Annotation只能修饰构造方法
【3】ElementType.FIELD:表示定义的Annotation只能修饰属性
【4】ElementType.LOCAL_VARIABLE:表示定义的Annotation只能修饰局部变量
【5】ElementType.METHOD:表示定义的Annotation只能修饰方法
【6】ElementType.PACKAGE:表示定义的Annotation只能修饰包定义
【7】ElementType.PARAMETER:表示定义的Annotation只能修饰参数
【1】ElementType.TYPE:表示定义的Annotation只能修饰类,接口,枚举类
学习总结:1.新知识:学会了使用专门的工具类(beanUtils)操作Java Bean
2.对于以前直接比较忽视的注释部分有了全新的认识,认识到注释的作用极其强大,并深入认识了元数据这一概念