写点什么

Spring 框架基础知识 (01)

  • 2021 年 12 月 28 日
  • 本文字数:5053 字

    阅读完需:约 17 分钟

1. 什么是框架

框架在项目中的表现就是一系列的 jar 包,例如 Thymeleaf 就是一个框架。


每种框架都会解决某种特定的问题,可能是开发效率的问题,或运行效率的问题,或代码管理维护的问题等等。


项目中使用框架就相当于得到了一个“毛坯房”,使用了框架之后,开发人员只需要关心后续的“装修”即可。


绝大部分的框架都有特定的使用方式,在使用时,必须遵循框架的使用规则!


每个框架都可能是若干个开发人员甚至开发团队多年的工作积累的作品,对于初学者来说,不要过于钻牛角尖,尝试理解框架的底层实现原理!


简单的说:使用框架,可以让编程变得更加简单!在学习框架时,主要学习框架的正确使用方式!

2. 依赖关系

假设在项目中需要开发一个用户注册的功能!在项目中可能存在:


public class UserRegServlet {    private UserDao userDao = new UserDao();        public void doPost() {        userDao.reg(); // 调用userDao对象实现存储用户数据    }}
复制代码


public class UserDao {    public void reg() {        // 通过JDBC技术将用户数据存储到数据库中    }}
复制代码


在以上代码中,UserRegServlet就是依赖UserDao的!

3. 耦合度

如果某个类过于依赖于另外一个类,通常称之为了“耦合度较高”,是不利于代码的管理和维护的,简单的说,如果UserRegServlet依赖于UserDao,在未来的某一天,UserDao已经不能满足项目的需求了(可能是因为代码有 Bug,或者使用的技术比较落后等),如果需要把UserDao替换掉,替换难度大就会影响项目的管理和维护,为了解决这样的问题采取的解决方案就称之为“解耦”,使得依赖关系不那么明确,甚至就是不明确!


就以上UserRegServlet依赖UserDao的问题,如果要解耦,可以先创建一个接口:


public interface IUserDao {    void reg();}
复制代码


然后,使得UserDao是实现了以上接口的:


public class UserDao implements IUserDao {    public void reg() {        // 具体的实现了reg()方法应该实现的功能    }}
复制代码


经过以上调整以后,如果在UserRegServlet中需要使用到UserDao,以前的代码是这样的:


private UserDao userDao = new UserDao();
复制代码


现在就可以改为:


private IUserDao userDao = new UserDao();
复制代码


以上代码就相当于:


private List<String> strings = new ArrayList<>();
复制代码


改成这样以后,在同一个项目中,无论多少个Servlet组件需要使用到UserDao,都可以使用以上“声明为接口,创建实现类的对象”的语法风格,如果以后UserDao需要被替换掉,也只需要替换“赋值”的代码,声明部分是不需要替换的!例如需要把UserDao替换为UserMybatisDao时,原来的代码是:


private IUserDao userDao = new UserDao();
复制代码


新的代码就可以是:


public class UserMybatisDao implements IUserDao {    public void reg() {        // 使用更好的方式实现reg()应该实现的功能    }}
复制代码


在后续的使用中,就可以是:


private IUserDao userDao = new UserMybatisDao();
复制代码


也就是说,在UserDao换成了UserMybatisDao时,在各个Servlet中,都只需要调整等于号右侧的内容,而不再需要修改等于号左侧的部分!


当然,关于以上代码的右侧部分,还可以使用“工厂设计模式”作进一步的处理:


public class UserDaoFactory {    // 返回接口类型的对象    public static IUserDao newInstance() {        return new UserDao(); // 也可以返回UserMybatisDao的对象    }}
复制代码


当有了工厂后,此前的代码就可以进一步调整为:


private IUserDao userDao = UserDaoFactory.newInstance();
复制代码


可以发现,以上代码中不再出现任何一个实现类的名字了,无论是哪个Servlet组件需要访问数据库,都声明为以上代码即可,以后,如果实现类需要被替换,也只需要替换工厂方法的返回值即可!


在实际项目开发时,项目中的组件的依赖更加复杂,为每个组件都创建对应的接口及工厂是非常麻烦的,而 Spring 框架就很好的解决了这个问题,可以简单的将 Spring 理解为一个“万能工厂”,当使用了 Spring 框架后,就不必自行开发工厂了!

4. Spring 框架简介

Spring 框架的主要作用:解决了创建对象和管理对象的问题。

5. 通过 Spring 创建对象

创建 Maven Project,在创建过程中,勾选 Create a simple projectGroup Id 填为cn.teduArtifact Id 填为spring01,其它项保持默认即可。


使用 Spring 框架时,必须在项目的 pom.xml 中添加spring-context的依赖:


<!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.2.5.RELEASE</version></dependency>
复制代码


首先,在项目中,创建cn.tedu.spring包,并在这个包下创建BeanFactory类:


package cn.tedu.spring;
public class BeanFactory {
}
复制代码


当前,代码放在哪个包中并不重要,应该养成习惯,每个类都应该放在某个包中,不要让任何类不放在任何包中!

以上类的名称也不重要,是自定义的!


如果希望由 Spring 来创建并管理某个类的对象,必须在以上类中添加方法,关于这个方法:


  • 应该使用public权限;

  • 返回值类型就是需要 Spring 创建并管理的类的对象的类型;

  • 方法名称可以自定义;

  • 参数列表暂时为空;

  • 在方法体中,自行编写创建返回值对象的代码。


假设需要 Spring 来创建Date类型的对象,则在类中添加方法:


public Date aaa() {    // 规范,规则}
复制代码


Spring 框架要求:创建对象的方法必须添加@Bean注解,并且,这样的方法必须在配置类中!任何一个类添加了@Configuration注解都可以作为配置类!


package cn.tedu.spring;
@Configurationpublic class BeanFactory { @Bean public Date aaa() { return new Date(); }
}
复制代码


完成后,应该使用一个可以运行的类,或通过单元测试来检验“是否可以通过 Spring 容器获取对象”。本次先创建一个可以运行的类:


package cn.tedu.spring;
public class SpringTests {
public static void main(String[] args) { // 1. 加载配置类,得到Spring容器 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(BeanFactory.class); // 2. 从Spring容器中获取所需要的对象 Date date = (Date) ac.getBean("aaa"); // getBean()方法的参数就是创建对象的方法的名称 // 3. 测试获取到的对象 System.out.println(date); // 4. 关闭 ac.close(); }
}
复制代码

6. 关于 @Bean 注解

当方法的声明之前添加了@Bean注解,就表示这个方法是需要由 Spring 框架所调用,并且,由 Spring 框架管理该方法返回的对象的!默认情况下,该方法的名称就是后续获取对象时,调用getBean()方法的参数!


由于添加了@Bean注解的方法是被 Spring 框架调用的,不需要自行编写代码来调用这个方法,所以,Spring 的建议是“使用合理的属性名称作为方法名,并不需要使用动词或动词为前缀的方法名”,简单的说,如果方法是为了获取Date类型的对象的,该方法的名称应该是date,而不是getDate(),则后续调用getBean()时,参数就是date这个名称!


当然,如果不遵循 Spring 的建议,还可以在@Bean注解中配置注解参数来指定 Bean 的名称,例如:


@Bean("date")public Date getDate() {    return new Date();}
复制代码


则后续就根据注解参数来获取对象:


Date date = (Date) ac.getBean("date");
复制代码


其关系如下图:



其实,在开发项目时,真的不必关心这些问题,也就是说,例如是一个获取Date对象的方法,其名称到底是date还是getDate都是正确的!毕竟这个方法最终就是由 Spring 框架来调用,开发人员不会自行调用该方法!

7. Spring 管理对象的作用域

由 Spring 管理的对象,默认情况下,是单例的!所以,其作用域就非常久!


在 Spring 管理对象的情况下,讨论对象的作用域,其本质就是讨论其是否单例!


在创建对象的方法之前,添加@Scope注解,并配置注解参数为prototype,就可以使得该对象不是单例的:


@Scope("prototype")@Beanpublic User user() {    return new User();}
复制代码


由 Spring 管理的对象,如果是单例模式的,默认情况下,是饿汉式的!在创建对象的方法之前,添加@Lazy注解,就可以调整为懒汉式的:


@Bean@Lazypublic User user() {    return new User();}
复制代码


一般,在开发项目时,极少调整对象的作用域!

8. 当天小结:

  • Spring 的主要作用:创建对象,管理对象;

  • 如果某个方法是用于给 Spring 框架创建对象的,这个方法就必须添加@Bean注解;

  • 所有添加了@Bean注解的方法,其所在的类应该添加@Configuration注解,凡添加了@Configuration注解的类称之为配置类

  • 默认情况下,由 Spring 管理的对象是单例的,使用@Scope注解可以将 Spring 管理的对象调整为“非单例”的;

  • 默认情况下,由 Spring 管理的单例的对象是是“饿汉式”的,使用@Lazy可以将它们改为“懒汉式”的。

附 1:设计模式之单例模式

单例模式的特点:在同一时期,某个类的对象一定最多只有 1 个!也许会尝试多次的获取对象,但是,获取到的一定是同一个对象!


假设项目中有King类:


public class King {}
复制代码


很显然,目前它并不是单例的,因为,可以:


King k1 = new King();King k2 = new King();King k3 = new King();
复制代码


以上代码就创建了 3 个King类型的对象!如果要实现单例,首先,就必须限制构造方法的访问,例如:


public class King {    private King() {    }}
复制代码


每个类中都可以有若干个构造方法,如果某个类没有显式的声明任何构造方法,编译器就会自动添加 1 个公有的、无参数的构造方法!如果类中已经声明任何构造方法,则编译器不会自动添加构造方法!


由于将构造方法声明为私有的,则原有的King k1 = new King();这类代码就不能用于创建对象了!


限制构造方法的访问,其目的是“不允许随意创建对象”,并不是“不允许创建对象”,在King类的内部,还是可以创建对象的,可以添加方法,返回内部创建的对象:


public class King {    private King king = new King();        private King() {    }        public King getInstance() {        return king;    }}
复制代码


所以,当需要King类型的对象时,可以通过getInstance()方法来获取!


但是,以上代码是不可行的!因为,如果要调用getInstance()方法,必须先获取King的对象,而获取King对象的唯一方式就是调用getInstance()方法!为了解决这个问题,必须在getInstance()方法的声明之前添加static修饰符,最终,就可以通过类名.方法名()的语法格式来调用方法了!同时,由于“被static修饰的成员,不可以访问其它未被static修饰的成员”,所以,全局属性king也必须被static修饰:


public class King {    private static King king = new King();        private King() {    }        public static King getInstance() {        return king;    }}
复制代码


至此,基本的单例模式的代码就设计完成了!


以上代码是“饿汉式”的单例模式,另外,还有“懒汉式”的单例模式!


基本的懒汉式单例模式的代码是:


public class King {    private static King king = null;        private King() {    }        public static King getInstance() {        if (king == null) {            king = new King();        }        return king;    }}
复制代码


注意:以上代码是多线程不安全的!


在开发领域中,只要数据的产生、变化不是开发人员预期的,就称之为“不安全”,也就是“数据安全问题”。


为了保障线程安全,应该为以上创建对象的代码片断“加锁”,例如:


public class King {    private static King king = null;        private King() {    }        public static King getInstance() {        synchronized ("hello") {            if (king == null) {                king = new King();            }        }        return king;    }}
复制代码


当然,无论是哪个线程在什么时候执行以上代码,都必须先“锁住”代码片断后才能开始执行,是没有必要的,“锁”的性能消耗是浪费的,所以,可以进一步调整为:


public class King {    private static King king = null;        private King() {    }        public static King getInstance() {        if (king == null) { // 判断有没有必要锁定接下来的代码            synchronized ("java") {                if (king == null) { // 判断有没有必要创建对象                    king = new King();                }            }        }        return king;    }}
复制代码


至此,懒汉式的单例模式就完成了!

发布于: 刚刚
用户头像

公众号:海拥 2021.11.29 加入

【个人网站】haiyong.site 【软件技能】Java,Python,JS 【兴趣爱好】学习使我快乐,编程令我永生 【个人称号】HDZ核心组成员,CSDN原力作者

评论

发布
暂无评论
Spring框架基础知识(01)