1 日志
日志是为了取代 System.out.println()
输出程序信息,它可以设置输出样式、输出级别(禁止某些级别的输出),可以重定向到文件,可以按照包名控制日志级别。同时日志的输出还会自动夹带着 日期时间,类名以及方法名称。
1.2 Commons Logging
Commons Logging 是 Apache 创建的日志模块,它可以挂接不同的日志系统,会在 CLASSPATH
中自动搜索并使用 Log4j。如果没有 Log4j,则会自动使用 JDK 的 Logging。
它定义了6个日志级别:
- FATAL:非常严重的错误
- ERROR:错误
- WARNING:警告
- INFO:普通信息,默认级别
- DEBUG:输出调试信息
- TRACE:更底层的调试信息
引用Log:
// 在静态方法中引用 Log
public class Main {
static final Log log = LogFactory.getLog(Main.class); // 相当于获取了 Main 类的 logger 实例,输出日志中的类名就是这个类
}
// 在实例方法中引用 Log
public class Person {
final Log log = LogFactory.getLog(getClass()); // 相当于获取了 Person 类的 logger 实例
}
// 在父类中引用 Log
public abstract class Base {
protected final Log log = LogFactory.getLog(getClass());
}
1.3 Log4j2
Log4j2 是一个组件化设计的日志系统,它可以通过 Appender(输出源)把同一条日志输出到不同的目的地,在使用的过程中,无需调用API,而是通过配置文件实现。
maven 引用的依赖:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.6.1</version>
</dependency>
配置文件 log4j2.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<!-- 定义xml文件中的全局变量 -->
<Properties>
<Property name="log.pattern">%d{MM-dd HH:mm:ss:SSS} {%t} %-5level %logger{36}%n%msg%n%n</Property>
<Property name="file.all.filename">log/all.log</Property>
<Property name="file.all.pattern">log/all.%i.log.gz</Property>
<Property name="file.err.filename">log/err.log</Property>
<Property name="file.err.pattern">log/err.%i.log.gz</Property>
</Properties>
<!-- Appenders配置是其输出的日志形式,有log文件输出/控制台输出/数据库写入/消息发送等方式 -->
<Appenders>
<!-- 控制台输出 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 输出格式 -->
<PatternLayout pattern="${log.pattern}" />
</Console>
<!-- 全部级别的日志存放文件,RollingFile 可以将日志文件自动切割成多个日志文件进行保存 -->
<RollingFile name="all" bufferedIO="true" fileName="${file.all.filename}" filePattern="${file.all.pattern}">
<!-- 输出格式 -->
<PatternLayout pattern="${log.pattern}" />
<!-- 日志文件拆分条件 -->
<Policies>
<!-- 日志大小为 1M -->
<SizeBasedTriggeringPolicy size="1 MB" />
</Policies>
</RollingFile>
<!-- 存放警告信息日志文件 -->
<RollingFile name="err" bufferedIO="true" fileName="${file.err.filename}" filePattern="${file.err.pattern}">
<!-- 输出格式 -->
<PatternLayout pattern="${log.pattern}" />
<!-- 日志文件拆分条件 -->
<Policies>
<!-- 日志大小为 1M -->
<SizeBasedTriggeringPolicy size="1 MB" />
</Policies>
</RollingFile>
</Appenders>
<!-- 配置实装,只有实装了,才会生效 -->
<Loggers>
<!-- 根配置,全体配置 -->
<Root level="info">
<AppenderRef ref="console" level="info" />
<AppenderRef ref="all" level="info" />
<AppenderRef ref="err" level="error" />
</Root>
<!-- 单独对某个类进行配置 -->
<Logger name="top.Seiei.logger.AboutCommonsLoggingDemo" level="debug">
<AppenderRef ref="console" level="debug" />
</Logger>
</Loggers>
</Configuration>
2 Class
Java除了基本类型外其他的都是 class
(包括 interface
),而 class
的本质就是 数据类型 Type
。
那么 class
的数据类型是什么呢,那就是 Class
(注意大写),每加载一个 class
,JVM 就会为其创建一个 Class
类型的实例,并关联起来,如下:
注意
Class
实例是JVM内部自行创建的,查看源码可发现Class
的构造方法是private
,因此自己的Java程序无法创建Class
实例的
JVM持有的每个 Class
实例都指向一个数据类型,而一个 Class
实例本身中保存着该 class
的完整信息,即获取了某个 Class
实例,就可以获取该实例对应的 class
的所有信息,通过 Class
实例获取 class
信息的方法称为 反射。
2.1 获取 Class
实例及其比较
获取 Class
实例,可以通过以下方式:
Type.class
:class
本身的静态属性class
getClass()
:class
实例的getClass()
方法Class.forName(String name)
:Class
本身的静态方法,其中name
是完整类名,如:java.lang.String
Class
实例在 JVM 中是唯一的,可以用 ==
比较两个 Class
实例是否相等。 然而通常情况下,应该使用 instanceof
来判断数据类型,因为面向抽象编程的时候,我们并不关心具体的类型是否相同。
2.2 反射
反射的目的是:当获取某个 Object
实例时,可以获取该 Object
的所有 class
信息,比如当使用 Eclipse 时,当查看某个变量X时,Eclipse 可以使用反射来获取该变量的属性和方法。
从 Class
实例获取 class
信息:
getName()
:全类名,如java.lang.String
getSimpleName()
:类名,如String
getPackage().getName()
:包名,如java.lang
- 还可以判断
class
的类型:isInterface()
isEnum()
isArray()
isPrimitive()
:是否为基本数据类型,注意基本类型变量本身的数据类型并不是Class
,但调用方法时,JVM内部会其创建相应的Class
实例。
- 获取字段信息:
getField(name)
:获取某个public
的字段的Field
对象(包括父类)getDeclaredField(name)
:获取当前类的某个字段的Field
对象getFields()
:获取所有public
的字段的Field
对象(包括父类)getDeclaredFields()
:获取当前类的所有字段的Field
对象
- 获取方法信息:
getMethod(name,Class...)
:获取某个public
的Method
对象(包括父类),其中Class
为可选参数,是获取方法中的参数getDeclaredMethod(name,Class...)
:获取当前类的某个Method
对象getMethods()
:获取所有public
的Method
对象(包括父类)getDeclaredMethods()
:获取当前类的所有的Method
对象
- 获取构造方法:
getConstructor(Class...)
:获取某个public
的Constructor
对象(包括父类),传入的可选参数为要获取的构造函数的参数,如Class cls = Integer.class; cls.getConstructor(int.class)
getDeclaredConstructor(Class...)
:获取当前类的某个Constructor
对象getConstructors()
:获取所有public
的Constructor
对象(包括父类)getDeclaredConstructors()
:获取当前类的所有的Constructor
对象
- 获取继承关系以及实现的接口
getSuperclass()
:获取父类的Class
实例,值得注意的是Object
和 接口的getSuperClass
返回的是null
isAssignableFrom()
:方法可以判断一个向上转型是否正确,eg:Person.class.isAssignableFrom(Student.class);// true
getInterfaces()
:返回类直接实现的接口,不包括父类实现的接口
newInstance
:Class
实例创建class
实例,注意只能使用无参构造方法创建,而且如果没有使用泛型声明Class
,还必须使用显式转换,若想要使用其它构造方法,则需要获取Constructor
对象,调用它的newInstance
方法
部分反射API是泛型,例如
Class<T>
和Constructor<T>
2.2.1 Field
对象
由 Class
对象可以获取 Field
对象,通过 Field
对象可以获取字段的信息,常用的有:
getName()
:获取名称getType()
:获取类型getModifiers
:获取修饰符
通过 Field
实例可以读取或设置某个 class
对象的字段:
get(Object instance)
:获取实例字段的值,其中instance
是class
实例对象,当字段是静态字段的时候,只需传入null
set(Object instance, value)
:设置实例字段的值
注意获取
Field
对象的方法对修饰符的限制,即创建不是public
修饰的字段Field
对象时,需要用的是getDeclaredField(name)
,而且当Field
对象访问class
实例的非public
修饰的字段值时,就需要使用Field
对象实例的setAccessible(true)
方法强制访问了
同样的Method
对象,Constructor
对象也跟Field
对象差不多,也是需要通过定义setAccessible(true)
2.3 动态加载
JVM 总是动态加载 class
,可以在运行期根据条件不同而加载不同的实现类(Class
实例),这就比如 Commons Logging 默认使用 log4j,只有 Log4j 不存在才使用 JDK 的 Logging。
// 判断一个类的是否存在,只需使用 Class.forName 方法,并捕获异常
boolean isClassPresent(String name) {
try {
Class.forName(name);
return true;
} catch (Exception e) {
return false;
}
}
LogFactory factory;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
factory = createLog4j();
} else {
factory = createJdklog();
}
这里要注意的关键点在于,当JVM执行程序时,并没有运用到 log4j class
类型本身,这就使得没有引用 log4j 依赖时,编译器也不会报出异常。