注解/JavaBean/内省机制
加载配置文件
什么是硬编码?
在Java开发中,我们很多地方都需要使用到配置文件
以前我们所有的数据都是放在Java代码中的,
如:
String username=”admin”;
String password=”123”;
在今后,我们的密码是可以修改的,所以在登录的时候需要将Java代码中写死的密码同时修改。
首先要找到项目的源码--->再修改对应的Java代码--->编译---->发布
总结:硬编码的概念。
1.将数据写死在Java代码中
2.后期会不定时的去做修改
解决方案:使用配置文件解决硬编码问题。
把数据写到一个配置文件中,通过Java在运行时去读取里面的数据
什么是配置文件
在java的开发中,配置一些以后可能经常修改的数据,参数到一个文件中,这个文件存放在项目中,通过写代码进行加载该文件,并进行读取文件中存的数据。
配置文件特点:无论是开发过程中还是编译之后,还是生产环境中,都是一个状态
编写配置文件的分类
在java中,配置文件分为两类。
一个是以.properties为扩展名的文件
配置文件是以properties为扩展名的,其内容为键值对形式存储,且键名和键值都是字符串格式。JAVA提供java.util.Properties类,可以非常方便的读取配置文件的信息。
java在对*.properties文件进行操作的时候,实际上是通过IO对文档进行逐行的扫描,然后将文中非注释的部分存放在一个properties对象中。Properties 实际上是继承了hashtable,实现了Map接口。可以这样理解,它是进行了进一步封装的HashMap。存放到properties中后,可以对properties进行一系列的操作,此时的数据保存在内存中。最后,当需要保存数据的时候,是将properties中所有的键值重新写入到文件中去。 对properties文件的操作,jdk提供了一系列的API。实现了对properties文件的增删查改的功能。
一个是以.xml为扩展名的文件
是一种可扩展的标记语言,类似HTML。
文件是使用一堆标签来包装数据的。
一般存储的数据具有一定的结构性,层次性。例如省市区。
两种配置文件相比较,第一种编写起来和使用起来更为简单些。
编写properties配置文件
选中项目右击鼠标,选择SourceFolder选项,创建一个resources文件夹,这个文件夹,是专门用来存储配置文件的。
在开发工具中,源文件夹(Source Folder)中的资源都会同步输出到字节码输出目录。
什么是字节码输出目录?
Java源文件编译后,字节码存放的目录,对于STS开发工具来讲默认就在当前项目的bin目录中
我们在Java开发中,如果要加载外部的资源到JVM中通常情况下都是从字节码输出目录中加载的,因为对于Java程序而言无论什么情况下,都一定会有字节码输出目录的
加载properties配置文件
类加载器是把Java字节码加载到JVM中的工具,其主要是从字节码输出目录中加载
类加载器可以从字节码的输出目录中加载资源
ClassLoader类中的方法:
InputStream getResourceAsStream(String name) : 从字节码输出路径中加载资源
读取到的数据在流中,但是取出数据操作时存在很多的不方便
在java中提供了Properties类来方便我们操作流中的属性信息
properties = new Properties();
//加载配置文件方式一:(不推荐) 通过类的加载器从项目的编译路径classPath路径加载 ,不安全
//InputStream inputStream = 当前类.class.getClassLoader().getResourceAsStream("config.properties");
//加载配置文件方式二:(推荐)通过Thread类来获取当前类的类加载器
InputStream inputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("config.properties");
properties.load(inputStream);
JavaBean/内省机制
什么是JavaBean?
JavaBean 是一种JAVA语言写成的可重用组件。
为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。
JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制发现和操作这些JavaBean 的属性。
JavaBean中的属性和字段
字段:在类中定义的成员变量
private String name;
属性:由类中的getter或setter方法决定
getName--->属性名是去掉get,将首字母小写得到的名称就是属性名:name
setName---->属性名是去掉set,将首字母小写得到的名称就是属性名:name
字段名和属性名绝大多数时候是一样的,但是可以不一样
注意:普通的字段对应的get方法就是getXxx();
如果字段类型是boolean,那么对应的get方法是:isXxx();
JavaBean的类中包含的成员
1.字段
2.方法
3.事件:了解,SE中的内容
Introspector:内省机制核心类
如果使用反射,我们可以操作JavaBean中的字段/方法/构造器等,如果我们在开发中需要大量的操作JavaBean的属性(getter/setter方法)
所以,我们可以选择一种更专业的手段来操作JavaBean的属性---内省机制(底层使用反射实现)
内省机制的主要作用:用于操作javabean中的属性
操作步骤:
1.获取JavaBean相关的信息对象:BeanInfo
2.该BeanInfo中就会封装有当前Bean的成员(字段/属性/事件)
3.获取到对应的属性,对其操作
java.beans.Introspector类:
常用API:
static BeanInfo getBeanInfo(Class<?> beanClass) : 获取字节码对象对应的JavaBean信息
static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
通过一张图来认识一下这两个API的区别。
1.pngjava.beans.BeanInfo接口:
常用API:
PropertyDescriptor[] getPropertyDescriptors() : 获取所有的属性描述器
java.beans.PropertyDescriptor类:
常用API:
String getName() : 获得属性的名称
Class<?> getPropertyType() : 获得属性的类型
Method getReadMethod() : 获得用于读取属性值的方法
Method getWriteMethod() : 获得用于设置属性值的方法
lombok插件
Lombok是什么
Lombok是一款小巧的代码生成工具。官方网址:http://projectlombok.org/
LomBok主要特性有:自动生成默认的getter/setter方法、自动化的资源管理(通过@Cleanup注解)及注解驱动的异常处理等。目前在国外广泛应用。
LomBok它和jquery一样,目标是让程序员写更少的代码,以及改进一些原始语法中不尽人意的地方。Lombok能做到这一点。既不是用annotations process,也不是用反射。而是直接黑到了编译过程中。所以对运行效率没有任何影响,我们可以通过反编译class文件进行验证。
为何项目中要引入Lombok
主要因为以下三点:
- 提高开发效率
- 使代码直观、简洁、明了、减少了大量冗余代码(一般可以节省60%-70%以上的代码)
- 极大减少了后期维护成本
如何安装,如何使用
-
1:java -jar lombok.jar
-
2:在Java项目中导入lombok.jar包
-
3:使用注解@Setter/@Getter/@ToString/@Data/@AllArgsConstructor/@NoArgsConstructor等
插件lombok的安装流程图
3.png 4.png流程图中通过java -jar的命令,来安装lombok插件。其本质是:
5.pnglombok插件的使用流程图
6.png 7.png 8.png 9.png 10.pngJavaBean和MAP的互转
Map是由key-value组成,key是不能重复的.
JavaBean是由属性名和属性值组成,属性名是不同的.
如果把JavaBean的属性名看做是Map的key,把属性值看做是Map的value,那么一个Map对象和一个JavaBean是等级的.
如图:
// 需求:把一个javaBean 转换成Map
public Map<String, Object> bean2Map(Object bean) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
// 获取传入对象的字节码对象
Class<? extends Object> clz = bean.getClass();
// 通过内省获取该类的BeanInfo对象
BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
// 通过BeanInfo对象获取属性描述器
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : descriptors) {
// 获取属性名称
String name = propertyDescriptor.getName();
// 获取getter方法对象
Method readMethod = propertyDescriptor.getReadMethod();
// 通过反射执行方法获取值
Object value = readMethod.invoke(bean);
map.put(name, value);
}
return map;
}
// 需求:把一个Map转换成javaBean
public <T> T map2Bean(Map<String, Object> map, Class<T> clz) throws Exception {
// 根据字节码对象获取类的真实对象
T instance = clz.newInstance();
// 通过内省获取该类的BeanInfo对象
BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
// 通过BeanInfo对象获取属性描述器
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : descriptors) {
// 属性名称作为map中的key,根据key获取value
Object value = map.get(propertyDescriptor.getName());
// 获取方法对象
Method writeMethod = propertyDescriptor.getWriteMethod();
// 把value 作为参数,通过反射调用setter方法
writeMethod.invoke(instance, value); // 相当于instance.setXxx(value) 方法
}
return instance;
}
}
注解
什么是注解
java.lang.annotation,接口 Annotation。
对于Annotation,是Java5的新特性,JDK5引入了Metadata(元数据)很容易的就能够调用Annotations。Annotations提供一些本来不属于程序的数据,比如:一段代码的作者或者告诉编译器禁止一些特殊的错误。An annotation 对代码的执行没有什么影响。Annotations使用@annotation的形式应用于代码:类(class),属性(attribute),方法(method)等等。一个Annotation出现在上面提到的开始位置,而且一般只有一行,也可以包含有任意的参数。
通过一张图来深入理解一下注解的含义:
11.png使用注解需要注意,必须有三方参与才有意义:
1):得有注解标签本身;
2):被贴的程序元素(类,字段,构造器,方法,参数上,包上等);
3):由第三方的程序使用反射的手段来赋予注解特殊的功能(也是Java代码).
JDK中的内置注解
public class CommonAnnotation {
// JDK5中提供的三个注解
/*
@Override 只能贴在方法上
@Deprecated 可以贴在变量上,方法上, 类上
@SuppressWarnings 可以贴在变量上,方法上,类上
*/
/*作用:表示该方法是重写父类的方法,
会帮我们验证方法名称是否和父类中的方法名称是否一致*/
@Override
public String toString() {
return super.toString();
}
/*作用:表示该方法已经过时,
不建议使用,但是可以使用*/
@Deprecated
private void MylocalString() {
}
/*作用:表示抑制警告,
只是看不到警告,不代表没有警告*/
@SuppressWarnings("rawtypes")
List list = new ArrayList();
// JDK7中新增了一个注解
/*
@SafeVarargs 一个方法中同时出现可变参数和泛型
*/
/*抑制堆污染发出的警告
问题依然存在*/
@SafeVarargs
public static <T> List<T> asList(T... a) {
return null;
}
// JDK8中增加一个注解
/*@FunctionalInterface 函数式接口
*/
/*接口中只有一个抽象方法*/
@FunctionalInterface
public interface MyRunnable {
void run();
}
}
JDK中的元注解
元注解有4个:
@Retention、@Target、@Documented、@Inherited
其中@Documented、@Inherited只需要了解
@Documented 表示注解会被javadoc指令编辑到API中
@Inherited 表示注解会遗传给子类
@Target
@Target:决定了该注解可以贴在什么地方
可以贴注解的地方有很多,都封装在枚举:ElementType中
ElementType.ANNOTATION_TYPE 贴在注解上
ElementType.CONSTRUCTOR 贴在构造方法上
ElementType.FIELD 贴在字段上(包括枚举常量)
ElementType.LOCAL_VARIABLE 贴在局部变量上
ElementType.METHOD 贴在方法上
ElementType.PACKAGE 贴在包上(极少使用)
ElementType.PARAMETER 贴在参数上
ElementType.TYPE 贴在类、接口或枚举上
其中贴在类/字段/方法上要重点知道,以后要用到
@Retention
@Retention:决定注解可以保存到哪个时期
注解的有效期有3个都封装在枚举:RetentionPolicy中
RetentionPolicy.SOURCE: 表示注解只存在于源文件中,不会被编译到字节码中
RetentionPolicy.CLASS: 表示注解会被编译到字节码中,但是JVM不加载注解
RetentionPolicy.RUNTIME: 表示注解会被编译到字节中,会随着字节码的加载而进入JVM,因此可以反射性地读取
通过一张图来区分三者的有效期的不同
12.png我们开发中的自定义的注解的有效期都要使用RUNTIME,这样才能在程序运行时使用反射赋予注解功能
反编译演示有些注解不会被编译到字节码中
思考:
自定义的注解应该保存到哪个时期呢?
RUNTIME时期,为啥?
需要使用反射来赋予注解特殊的含义,反射是在运行阶段才生效
定义注解的语法
使用@interface 注解名称
例如:public @interface VIP {
//抽象方法 属性
}
注解的抽象方法称为属性,如果要给这个属性赋予默认值可以在抽象方法后使用default 值
使用注解语法:@注解名[(属性名=属性值,属性名=属性值)]
例如:@VIP
注意:给属性赋值时如果只有1个属性要赋值且名称叫value时,可以省略value不写 如:@VIP("xxx")
注意:属性的返回值类型只能是基本类型/枚举/Class/注解/String/枚举以及他们各自的数组
自定义注解的步骤:
1.自定注解: public @interface 注解名称{}
2.说明当前方法能够贴的位置和能够保存的时期
@Target: 值保存在ElementType枚举类中
@Retention: 值保存在RetentionPolicy枚举类中
3.将注解贴在能够贴的位置
4.为注解传递参数
需要在注解中定义属性
语法:和接口中定义抽象方法是一样的
类型 属性名() default 默认值 ;
特殊使用: 如果属性名是value,同时在使用的时候我们只需要为value设值的话,此时可以省略value属性名
例如:在后面的Servlet中,会用到一个注解: @WebServlet(“/hello”)
操作注解:
意识:注解是贴在类上,方法上,字段上等位置
所以,对应的Class/Method/Field类上应该会有操作注解的方法
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
自定义注解的案例:
MyAnnotation.java
@Target({ ElementType.METHOD, ElementType.TYPE }) // 确定作用目标
@Retention(RetentionPolicy.RUNTIME) // 存活周期
public @interface MyAnnotation {
String value() default "少壮不努力老大徒伤悲";
//
// int age();
//
// String name();
/*
注意:
ElementType.METHOD 表示可以作用在方法上,
ElementType.TYPE 表示可以作用在类上,接口上,
RetentionPolicy.RUNTIME 表示当前注解可以在运行阶段使用
如果在使用注解的时候,属性名为value的属性传值并且注解中只有这一个抽象方法,
此时可以省略属性名
可以通过default 来设置默认值
*/
}
Employee.java
@MyAnnotation("哈哈")
// 注解括号中的key=value 内容 相当于注解中的抽象方法的实现
public class Employee {
@MyAnnotation(value = "哈哈")
public void init() {
System.out.println("init 方法 ");
}
}
AnnotationTest.java
public class AnnotationTest {
// 通过反射获取注解的内容
@Test
public void testAnno() throws Exception {
// 通过反射获取Employee的字节码对象
Class<?> clz = Class.forName("cn.wolfcode.day02._02_.annotation.Employee");
// 通过字节码对象获取自定义注解对象
MyAnnotation annotation = clz.getAnnotation(MyAnnotation.class);
// 通过注解对象调用自身的抽象方法获取对应的值
String value = annotation.value();
System.out.println(value);
Method method = clz.getMethod("init");
MyAnnotation annotation2 = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation2.value());
}
}
网友评论