Spring 单例+反射(详细)

一.什么是单例?

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

简单来说单例模式就是指在内存中只会创建且仅创建一次对象的设计模式,当程序中其他地方需要使用到该对象的相同功能时,都会调用创建好的这一个,不会再额外创建实例,这样做的好处就是避免过多的创建相同作用的对象使得内存浪费。

二.单例模式分类

在单例模式中主要分为两类,分别是懒汉式和饿汉式

懒汉式:在程序调用时才创建实例

饿汉式:在程序加载时就创建好实例,等待被调用

3.单例模式的实现

3.1 懒汉式

懒汉式是在程序调用时才会创建实例,在程序调用时首先会进行判断,如果已经存在该实例,则直接返回,若不存在该实例则创建并返回,懒汉式流程图如下:
在这里插入图片描述
代码实现
为避免类被多次实例化,所以将类中的构造函数限定为private,这样就能保证其他程序无法通过new关键字来实例化,达到真正的单例。

package com.lee.singleton;

public class Singleton {
	private static Singleton instence = null;
	private Singleton() {}
	public static Singleton getInstence() {
		if(instence == null)
			instence =  new Singleton();
		return instence;
	}
}

通过以上代码就简单的实现了懒汉式的单例模式,但是在上面的代码中还有不完美的地方:如果当程序为多线程时,那么如果线程A和线程B同时调用了Singleton的getInstence()方法,然后同时进入了if判断,这样的话由于线程不安全就会导致被实例化两个对象。
下面通过两个线程来模拟调用该单例模式程序,然后通过实例的hashCode来判断是只创建了一个对象还是创建了两个对象。
测试多线程调用单例模式

public class SingletonTest {
	public static void main(String[] args) {
		Thread thread = new Thread() {
			public void run() {
				Singleton instence = Singleton.getInstence();
				int hashCode = instence.hashCode();
				System.out.println(Thread.currentThread().getName() + ":" + hashCode);
			}
		};
		thread.start();
		Thread thread2 = new Thread() {
			public void run() {
				Singleton instence = Singleton.getInstence();
				int hashCode = instence.hashCode();
				System.out.println(Thread.currentThread().getName() + ":" + hashCode);
			}
		};
		thread2.start();
	}
}

运行结果为:
在这里插入图片描述
由结果我们可以看到两个线程所创建的对象的hashCode值不一样,那么也就代表如果多线程调用该单例模式则会出现线程安全问题。
3.2 懒汉式优化
如果说到解决线程安全问题,最先想到的肯定就是synchronized方法和synchronized代码块。最简单的就是在getInstence方法上加上synchronized,这样当一个线程进入到该方法时,另外的线程就无法进入,可以确保单例。
使用synchronized。

//方法加synchronized锁的方式
public synchronized static Singleton getInstence() {
	if(instence == null)
		instence =  new Singleton();
	return instence;
}
//synchronized代码块的方式
public static Singleton getInstence() {
	synchronized(Singleton.class){
		if(instence == null)
			instence =  new Singleton();
	}
	return instence;
}

但是使用以上方法还是不够完美,那就是当已经有了实例之后每次调用getInstence还是会首先获取到锁,然后再进行判断,这样当高并发的情况下性能就会及其低下,所以如果需要安全并且性能好的话就只能使用以下方法。
最终优化方法

public Singleton getInstence() {
	if(instence == null) {
		synchronized(Singleton.class) {
			if(instence == null) {
				instence = new Singleton();	
			}	
		}
	}
	return instence;
}

该方法只是在synchronized代码块的方式外层又加了一层if判断,这样的话当已经有实例的情况下就会直接返回实例化并不会进入到if中,也就不会去获取锁。并且在没有实例的情况下如果两个线程都进入到了最外层的if判断,那么在线程A获取到锁并进入第二层if中并实例化对象之后,线程B就不会进入到第二层if,能够确保单例。

3.2 饿汉式

饿汉式是指在程序加载时就创建对象,当需要调用时则直接返回实例,不需要和懒汉式一样进行判断是否实例化,饿汉式流程图如下所示:
在这里插入图片描述
代码实现

public class Singleton {
	private static final Singleton instence = new Singleton();
	private Singleton() {}
	public Singleton getInstence() {
		return instence;
	}
}

三.反射

一.什么是反射?

一、什么是反射:
(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
二、反射的原理:
下图是类的正常加载过程、反射原理与class对象:

Class对象的由来是将.class文件读入内存,并为之创建一个Class对象。
在这里插入图片描述
三、反射的优缺点:
1、优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;

(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

四、反射的用途:
1、反编译:.class–>.java

2、通过反射机制访问java对象的属性,方法,构造方法等

3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。

4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

5、例如,在使用Strut2框架的开发过程中,我们一般会在struts.xml里去配置Action,比如:

<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute">   
    <result>/shop/shop-index.jsp</result>           
    <result name="error">login.jsp</result>       
</action>

比如我们请求login.action时,那么StrutsPrepareAndExecuteFilter就会去解析struts.xml文件,从action中查找出name为login的Action,并根据class属性创建SimpleLoginAction实例,并用Invoke方法来调用execute方法,这个过程离不开反射。配置文件与Action建立了一种映射关系,当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截,然后StrutsPrepareAndExecuteFilter会去动态地创建Action实例。
比如,加载数据库驱动的,用到的也是反射。

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动

五、反射机制常用的类:
Java.lang.Class;

Java.lang.reflect.Constructor;

Java.lang.reflect.Field;

Java.lang.reflect.Method;

Java.lang.reflect.Modifier;

六、反射的基本使用:
1、获得Class:主要有三种方法:

(1)Object–>getClass

(2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性

(3)通过class类的静态方法:forName(String className)(最常用)

package fanshe;
 
public class Fanshe {
	public static void main(String[] args) {
		//第一种方式获取Class对象  
		Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
		Class stuClass = stu1.getClass();//获取Class对象
		System.out.println(stuClass.getName());
		
		//第二种方式获取Class对象
		Class stuClass2 = Student.class;
		System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
		
		//第三种方式获取Class对象
		try {
			Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
			System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值