反射
反射即反向探知
值在java程序运行的装填中
1.对于给定的一个类class对象,可以获得这个类对象的所有属性和方法
2.对于给定的一个对象,class都能够调用他的任意一个属性和方法这种动态获取类的内容的以及动态调用对象的方法和获取属性的机制。
反射的优缺点:
优点:增加程序灵活性,避免固有逻辑写死到程序中
代码相对简洁,提高程序的复用性
缺点:相比于直接调用,反射有比较大的性能消耗
内部暴露和安全隐患
优点实例
新建一个ball借口,定义打球的方法
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) {
getBallByKey("basket").playBall();
}
// 根据传入的字符串判断对应的生成实例的对象
public static Ball getBallByKey(String key){
if ("basket".equals(key)){
return new BasketBall();
}
if ("foot".equals(key)){
return new BasketBall();
}
return null;
}
如果这个时候存在一个新的类,比如打乒乓球,需要重新定义一个类,并在if后面加判断,显得比较麻烦,我们可以使用反射的方式去实现他
public static Ball getBallValidateByKey(String key) throws Exception {
// 定义类所在的路径,通过包名的方式找到对应的类
String packageName = "fanshe";
Class<?> clazz = Class.forName(packageName + "." + key);
Ball ball = (Ball) clazz.newInstance();
return ball;
}
调用如下:
public static void main(String[] args) throws Exception {
getBallValidateByKey("BasketBall").playBall();
}
反射的缺点实例:
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
getBallByKey("basket");
}
long end = System.currentTimeMillis();
System.out.println("正常加载耗时 " + (end - start));
start= System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
getBallValidateByKey("FootBall");
}
end = System.currentTimeMillis();
System.out.println("使用反射耗时 " + (end - start));
}
执行结果如下:
正常加载耗时 10
使用反射耗时 584
我们查看反射的源码分析:
Class.forName 方法
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
调用了forName0方法,是一个本地方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
查看clazz.newInstance()方法
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
checkMemberAccess 方法进行了安全检查比较耗时
反射慢的地方:
1.调用了native方法
2.调用newInstance都做了安全检查,比较耗时
反射的操作
基本操作:
获取类对象的四种方式:
Class<BasketBall> ballClass = BasketBall.class;
Class<?> aClass = Class.forName("fanshe.BasketBall");
Class<? extends BasketBall> aClass1 = new BasketBall().getClass();
Class<?> clazz = Test.class.getClassLoader().loadClass("fanshe.BasketBall");
基本信息操作
// 获取类相关结构
//获取类的相关修饰符
int modifiers = ballClass.getModifiers();
//获取的值得含义,具体可以百度jdk 1.8 modifier的值研究,主要是对类的修饰符,private/public等修饰符做定义
System.out.println(ballClass.getPackage());
System.out.println(ballClass.getName());
System.out.println(ballClass.getSuperclass());
System.out.println(ballClass.getClassLoader());
System.out.println(ballClass.getSimpleName());
System.out.println(ballClass.getInterfaces());
System.out.println(ballClass.getAnnotations());
字段的操作
public class Father {
private String FatherName;
public Integer fatherAge;
}
public class Son extends Father{
public String name; //公有
private String address; //私有
public static int age;// 静态
}
public static void main(String[] args) {
Class<Son> clazz = Son.class;
//可以获取父类和自己的公有方法
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
System.out.println("=======================");
//可以获取自己的所有方法
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field.getName());
}
}
运行结果如下:
name
age
fatherAge
=======================
name
address
age
// 对类的属性赋值
Field name = clazz.getField("name");
Son son = new Son();
name.set(son,"张三");
System.out.println(son.name);
//私有方法调用,需要设置访问权限
Field address = clazz.getDeclaredField("address");
address.setAccessible(true);
address.set(son,"成都");
System.out.println(son.getAddress());
//静态变量访问
Field age = clazz.getDeclaredField("age");
age.set(null,12);
System.out.println(son.age);
类中的方法操作
public static void main(String[] args) {
// 反射的方式操作方法
Son son = new Son();
//getMethods获取本类和父类中所有的公有方法public修饰
Method[] methods = son.getClass().getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("==================");
//getDeclaredMethods 获取本类中的所有方法
Method[] methods2 = son.getClass().getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method.getName());
}
}
//方法的调用
public void say(){
System.out.println("say");
};
private static void jump(String str){
System.out.println("jump " + str);
};
调用
//无参方法调用
Method say = son.getClass().getMethod("say");
say.invoke(son);
//有参静态方法调用
Method jump = son.getClass().getDeclaredMethod("jump",String.class);
jump.setAccessible(true);
jump.invoke(null,"666");
构造器使用:
public Son(String name) {
this.name = name;
}
public Son() {
}
private Son(String name, String address) {
this.name = name;
this.address = address;
}
调用
public static void main(String[] args) throws Exception {
// 反射的方式操作方法
Son son = new Son();
Class<? extends Son> aClazz = son.getClass();
Constructor<?>[] constructors = aClazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
System.out.println("=======================");
Constructor<?>[] constructors2 = aClazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors2) {
System.out.println(constructor.getName());
}
// 通过构造器实现对象的创建
Son son1 = aClazz.newInstance();
// 获取对象的construcator对象获取实例
Constructor<? extends Son> declaredConstructor =
aClazz.getDeclaredConstructor(String.class, String.class);
declaredConstructor.setAccessible(true);
Son son2 = declaredConstructor.newInstance("张三", "成都");
System.out.println(son2.name + son2.getAddress());
}
反射调用构造器,导致单例模式失效的情况
产生的原因是反射可以调用私有构造器造成的,可以在私有构造器中加入逻辑判断,对于已经存在实例的情况下,重新创建就抛出错误
先定义一个单利的类
public class Single {
private static Single instance;
public Single(){
}
public static Single getInstance(){
if (instance == null){
instance = new Single();
}
return instance;
}
}
调用
public static void main(String[] args) {
System.out.println(Single.getInstance());
System.out.println(Single.getInstance());
System.out.println(Single.getInstance());
}
运行结果如下
single.Single@1b6d3586
single.Single@1b6d3586
single.Single@1b6d3586
可以看到生成的三个对象都是一个地址,满足单利的模式,这个时候我们使用反射的方法调用
public static void main(String[] args) throws Exception {
System.out.println(Single.getInstance());
System.out.println(Single.getInstance());
System.out.println(Single.getInstance());
Constructor<Single> declaredConstructor = Single.class.getDeclaredConstructor();
System.out.println(declaredConstructor.newInstance());
}
运行结果如下:
single.Single@1b6d3586
single.Single@1b6d3586
single.Single@1b6d3586
single.Single@4554617c
这个时候我们发现最后生成的对象和之前的不是同一个对象,不满足单利模式
我们发现,不管是反射,还是单利,都会走构造器,所以我们需要在构造器中加一个判断,防止对象已经生成的情况下再生成新的实例
public Single(){
if (instance != null){
throw new RuntimeException("实例已经创建了...");
}
}
执行时发现存在重新创建新的实例的情况就会抛出错误
反射的使用场景
1.JDBC的封装
2.SpringIOC
3.jdbcTemplate
4.mybatis (mybatisGenerate – 代码生成器,根据包路径生成对应的模板和代码)
5.freeMarker 网页静态化技术等
反射的应用 SpringIOC
IOC 控制翻转,就是一种设计思想,容器管理对象
创建一个maven工程,在resouces下心剑spring-ioc.xml
定义bean的属性
<bean id="son" class="org.example.Son"/>
在方法中调用
public static void main( String[] args )
{
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-ioc.xml");
Son son = (Son) ac.getBean("son");
System.out.println(son);
}
//上文中没有直接new son对象,而是从ac容器中调用getBean方法取出了该对象