在我们学习Spring的过程中,我们经常看到Spring的IOC(控制反转)和DI(依赖注入)。在Spring的环境下这两个概念其实差不多,因为控制反转是通过依赖注入来实现的。
1.SpringIOC和DI
IOC是指我们代码里需要的实现的对象创建、维护对象间的关系,然后反转给容器来帮忙实现。所以我们需要创建一个容器,并且需要用一种描述来使容器知道需要创建的对象与对象之间是什么关系。注入依赖的目的只是起到解耦合的作用,进而体现出一种“组合”的理念。
//Spring IOC容器负责创建Bean,并通过容器将Bean注入到需要的Bean对象上。同时SpringIOC容器还负责维护Bean对象与Bean对象之间的关系。SpringIOC如何来实现对象与对象的关系?spring提供了xml配置和java配置等方式。
@Service
public class UserserviceImpl implements UserService{
@Resource
private UserDao userdDao;
public List<User> findAll(){
return UserDao.findAll();
}
}
spring提供的注解有很多,比如声明Bean的注解和注入Bean的注解,这些注解在开发中经常被用到。消息面大家一起回顾一下:
声明Bean的注解:
- @Component:没有明确的角色。
- @Service:在服务层(业务逻辑层)被使用 。
- @Repository:在数据访问层(Dao层)被使用。
- @Controller:在表现层(控制层)被使用。
注入Bean的注解:
@Resource:JSR-250提供的注解。
@Autowired:Spring提供的注解。
注意,@Resource这个注解是属于J2EE的,他默认按照名称进行装配,名称可以通过name属性属性进行指定。如果没有指定的name属性,当注解写在字段上,默认取字段名进行查找;如果注解写在setter方法上,则默认取属性名进行装配。当找不到与名称匹配的Bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照表名称进行匹配,具体代码如下。
@Resource(name= "useDao")
private UserDao userDao:
而@Autowired这个注解是属于Spring的,默认按类型装配。默认情况下要求依赖对象必须存在,如果要允许null值,可以设置它的requierd的属性为false,如@Autowired(required false),如果我们想用名称会装配,可以结合@Qualifier注解来视野,代码如下:
@Autowierd
@Qualifire
private UserDao userdao;
2.单例模式
Spring依赖注入Bean实例默认都是单例的。
单例模式【Singleton Pattern】:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类;它提供了全局访问的方法。单例模式是一种对象创建模式。
//放一个比较老的传统代码:如下:
/**
*描述:传统创建实例
*@author lpr
*@create 2020/8/28
*/
public class Case_1{
public static void main(String[],args){
singleton singleton = new Singleton();
Singleton singleton2= new Singleton();
}
}
//描述:单例类
class Singleton{
}
在上述传统的方法代码中,每次new Singleton(),都会创建一个Singleton实例,显然不符合一个类只有一个实例的要求,所以需要对上述代码进行修改,具体修改结果如下:
//单例模式实例
public class Case_1{
public static void main (String{],args){
Singleton singleton = new singleton();
//单例
Singleton singleton = SingLeton.getInstance():
}
}
/**
*描述:单例类
*/
class Singleton{
//step 2.自行对外提供实例。
private static final Singleton singleton = new Singleton;
//step1.构造函数私有化
private Singleton(){}
//step 3.提供外界可以获得该实例的办法
public static Singleton getInstance(){
return singleton;
}
}
单例模式有很多不一样的写法:上述的代码是比较简单的饿汉式的实现方法,它在类加载的时候就创建了单立类的对象,根据上面的代码可知,实现一个单例模式需要有三个步骤:
- 构造函数的私有化。
- 自行对外提供实例;
- 提供外界可以获得该实例的方法。
与饿汉模式相对的还有懒汉模式,懒汉模式类似于延迟加载的意思,具体代码如下:
//懒汉模式(存在多线程并发的问题,不属于完全正确的方法)
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance(){
//1.判断对象是否创建
if(null == singleton){
//2.创建对象
singleton = new Singleton();
}
return singleton;
}
}
如果我们创建单例对象会消耗大量资源,那么延迟创建对象就是我们很好的选择,但是懒汉模式也存在一个明显的问题,那就是线程安全的问题,当处于多线程并发的情况下,它会并发调用getInstance()方法,从而导致系统同时创建多个单例类实例,这明显是不符合我们的要求。可以通过给getInstance()方法添加锁的方式来解决该问题,代码如下:
//懒汉模式(添加synchronized锁的方法)
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
//1.判断对象是否创建
if(null == singleton){
//2.创建对象
singleton = new Singleton();
}
return singleton;
}
}
加了synchronized锁后虽然可以保证我们线程的安全,但是每一次调用getInstance()方法的时候,都会有这个加锁和解锁的操作,同时synchronized锁是加在方法上的,锁住的范围很大,而单例类他是全局唯一的,那么这个锁的这个操作会成为系统的瓶颈,所以还需要进一步完善代码,由此引出了“双重校验锁”的方式,具体代码如下:
//懒汉模式--双重校验锁(带有指令重排问题)
class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance(){
//第一次校验:
if(singleton == null){
synchronized(Singleton.class){
//第二次检验:
if(singleton == null){
//创建对象,非原子操作
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重校验锁它会出现指令重排的问题,所谓指令重排是指JVM为了优化命令,提高程序运行的效率,在不影响单线程程序执行结果的前提下,尽可能的提高并行度。Singleton()看似原子操作,其实不然,singleton = new Singleton()实际上可以抽象下面几条JVM指令:
//1.分配对象的内存空间
memory = allocate();
//2.初始化对象
ctorInstance(memory);
//3.设置instance指向刚分配的内存地址
singleton = memory;
上述的操作2 依赖于操作1;但是操作3并不依赖于操作2,所以JVM是可以针对他们进行指令的优化重新的,重新排序的顺序变成了1-3-2;
可以看出,指令重排之后,singleton指向分配好的内存放在了前面,这段内存的初始化被排在了后面。在线程A执行这段赋值语句,在初始化分配对象之前就已经将其赋值给singleton引用,恰好B线程进入方法判断singleton引用不为null,然后就将其返回使用,导致程序出错。
我们可以采用volatile关键字来修饰singleton字段。volatile关键字的一个语义就是禁止指令的重新排序优化,组织JVM对其相关代码进行指令重排,这样就能够按照既定的顺序执行指令。修改后的代码如下:
//懒汉模式--双重校验锁(volatitle解决指令重排问题)
class Singleton{
private static volatile Singleton singleon = null;
private Singleton(){}
public static Singleton getInstance (){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
除了双重校验锁的写法外,还有一种比较推荐大家使用的单例模式写法(静态内部类的单例模式)
当第一次访问类中的静态字段时,会触发类加载,并且同一个类只能加载一次。静态内部类也是如此,类加载过程由类加载器负责加锁,从而保证了线程的安全。和双重检验锁的写法比起,这种更加简洁明了。
//静态内部类的单例模式
class Singleton{
//2.私有的静态内部类,类加载器负责加锁
priavte static class Singleton{
private static Singleton singleton = new Singleton();
}
//1.私有化构造方法
private Singleton(){}
//3.自行对外提供实例
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
本文部分内容摘自于“SpringMVC+Mybatis 快速开发”一书,感兴趣的大家可以去读一读
。