Java基础笔记(日志,反射)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38801354/article/details/83044632

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 + Maven的配置文件示例详解》《log4j2 按天分日志》《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 类型的实例,并关联起来,如下:

Created with Raphaël 2.2.0 加载 String 类:读取String.class文件 为 String 类创建一个 Class 实例:Class cls = new Class(String);

注意 Class 实例是JVM内部自行创建的,查看源码可发现 Class 的构造方法是 private ,因此自己的Java程序无法创建 Class 实例的

JVM持有的每个 Class 实例都指向一个数据类型,而一个 Class 实例本身中保存着该 class 的完整信息,即获取了某个 Class 实例,就可以获取该实例对应的 class 的所有信息,通过 Class 实例获取 class 信息的方法称为 反射

2.1 获取 Class 实例及其比较

获取 Class 实例,可以通过以下方式:

  • Type.classclass 本身的静态属性 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...):获取某个 publicMethod 对象(包括父类),其中 Class 为可选参数,是获取方法中的参数
    • getDeclaredMethod(name,Class...):获取当前类的某个 Method 对象
    • getMethods():获取所有 publicMethod 对象(包括父类)
    • getDeclaredMethods():获取当前类的所有的 Method 对象
  • 获取构造方法:
    • getConstructor(Class...):获取某个 publicConstructor 对象(包括父类),传入的可选参数为要获取的构造函数的参数,如 Class cls = Integer.class; cls.getConstructor(int.class)
    • getDeclaredConstructor(Class...):获取当前类的某个 Constructor 对象
    • getConstructors():获取所有 publicConstructor 对象(包括父类)
    • getDeclaredConstructors():获取当前类的所有的 Constructor 对象
  • 获取继承关系以及实现的接口
    • getSuperclass():获取父类的 Class 实例,值得注意的是 Object 和 接口的 getSuperClass 返回的是 null
    • isAssignableFrom():方法可以判断一个向上转型是否正确,eg:Person.class.isAssignableFrom(Student.class);// true
    • getInterfaces():返回类直接实现的接口,不包括父类实现的接口
  • newInstanceClass 实例创建 class 实例,注意只能使用无参构造方法创建,而且如果没有使用泛型声明 Class,还必须使用显式转换,若想要使用其它构造方法,则需要获取 Constructor 对象,调用它的 newInstance 方法

部分反射API是泛型,例如 Class<T>Constructor<T>

2.2.1 Field 对象

Class 对象可以获取 Field 对象,通过 Field 对象可以获取字段的信息,常用的有:

  • getName():获取名称
  • getType():获取类型
  • getModifiers:获取修饰符

通过 Field 实例可以读取或设置某个 class 对象的字段:

  • get(Object instance):获取实例字段的值,其中instanceclass 实例对象,当字段是静态字段的时候,只需传入 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 依赖时,编译器也不会报出异常。

猜你喜欢

转载自blog.csdn.net/qq_38801354/article/details/83044632