SpringFramework笔记
代码下载地址: https://gitee.com/chengylaoshi/spring-framework.git
Spring
1. 什么是框架
Thymeleaf 就是一个环境(框架!)
框架在项目中的具体表现就是一个jar包!每一种框架都是用来解决某种特定问题的!可能是开发效率方面的问题,或者是运行效率的问题,代码管理维护的问题!
绝大多数的空间都有特定的使用方式!在使用时必须遵循框架的使用规则!对于初学者来讲,只需学习使用方式 和使用规则,并不需要钻牛角尖,尝试着去理解框架的底层含义或者实现原理!
Spring 让每个人都可以更快、更轻松、更安全地编写 Java。
2. 依赖关系
假设我们的项目中需要开发一个用户注册的功能!在项目中可能存在:
public class UserRegServlet { // 依赖 //private UserDao userDao = new UserDao(); //MyBatisDao md = new MyBatisDao(); public void doPost() { //userDao.reg(); // 调用依赖的持久层Dao 实现用户数据的存储 //md.register(); } } public class UserDao{ void reg(){ // 使用jdbc技术实现数据存储到数据库中 } }
在以上的代码中,UserRegServlet 就是依赖于 UserDao类!
3. 耦合度
如果一个类过于依赖另外一个类,通常称之为:“耦合度较高”!是不利于代码的管理和维护,简答的说如果UserRegServlet依赖于UserDao,,当未来的某个时刻,如果我们要对齐进行修改,因为不满足项目的需求(可能项目有bug的,或者技术有更新...)如果需要替换掉当前的UserDao,替换难度比较大,就会影响整个项目的管理和维护,为了解决这样的问题,采取的解决方案就是对其进行“解耦”!降低或者弱化耦合关系是的依赖关系不那么明显!
public interface IUserDao { void reg(); }
实现解耦的方式就是先创建一个接口。,使得子类进行实现该接口即可
public class UserDao implements IUserDao { public void reg(){ // 具体的实现细节 } } public class MybatisDao implements IUserDao { public void reg(){ // 具体的实现细节 } } public class UserRegServlet { // 依赖 // private IUserDao userDao = new UserDao(); private IUserDao userDao = new MybatisDao(); public void doPost() { userDao.reg(); // 调用依赖的持久层Dao 实现用户数据的存储 } }
其实代码:private IUserDao userDao = new MybatisDao();
相当于List<String> lists = new ArrayList<>();
如果需要再进一步弱化掉依赖关系。我们是可以通过“工厂设计模式”解决这个问题
public class UserDaoFactory{ public static IUserDao newInstance(){ // return new UserDao(); return new MyBtatisDao(); } } public class UserRegServlet { // 依赖 private IUserDao userDao = UserDaoFactory.newInstance(); public void doPost() { userDao.reg(); // 调用依赖的持久层Dao 实现用户数据的存储 } }
通过上面的代码。我们会发现无论是哪个servlet需要访问数据库,都可以使用上面的声明方式进行!在实际项目开发时,项目中的组件的依赖是更为复杂的,为每个组件都创建对应的接口或者工厂,这个过程是非常的繁杂且冗余的,进而就引入了spring这样的框架,来帮我们解决此类问题!可以简单的将spring理解为一个“万能的工厂”!当使用了spring框架后,就不必自行开发接口或者工厂类了!
4. Spring框架简介
4.1 什么是Spring
Spring框架是springmvc、springboot等springXXX框架的根框架!
由spring开头的框架都是spring框架衍生的而来的!那我们学习spring框架 ,就是学习spring到底有什么功能!spring框架本身的功能其实并不复杂,核心功能就是两个:
-
IOC
控制反转 【DI
依赖注入】 -
AOP
面向切面编程!
Spring框架的主要作用:
-
解决了对象如何创建的问题
-
解决了对象如果管理的问题
4.2 IOC概念
IOC: Inversion of control
控制反转
我们在编程程序之前,先将需要的所有对象保存在一个“外部容器”当中,而“外部容器”可以完成对象和相关组件的拼接或者组装(赋值)!当需要时,直接可以从“外部容器”中获取拼接或者拼装完的对对象!
我们所讲到的:“外部容器” 就是框架spring
就是当我们需要什么类型的对象时,直接向spring获取即可!
spring框架就会按照既定好的规则,将各个组装好的或者赋值好的对象成品直接返回给我们!
5. 通过Spring创建对象
我们开始编写Ioc的程序
可以分为3个步骤
-
定义要保存的类
-
编写代码,将这个类的对象保存到Spring容器
-
编写代码从Spring容器中获得该对象
/* 实体类 */ public class Student { private Integer id; private String name; private String gender; // getter setter topString方法 } /* 配置类 */ @Configuration public class StudentConfig { // 1.spring要求将存储的对象设置为方法的返回值 // 2.spring要求要存储对象的方法必须添加@Bean注解 // 3.这个方法的方法名spring会作为这个对象的唯一标识名称保存到容器 @Bean("stu") public Student student(){ Student student = new Student(); student.setGender("男"); student.setName("张三"); student.setId(00001); return student; } } /* 测试类 */ public class SpringTest { public static void main(String[] args) { // 程序运行时,会从这个容器中创建要使用对象!!!! AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(StudentConfig.class); // 获取容器中的对象 Object obj = acac.getBean("student"); Student student = (Student) obj; System.out.println(student); // 容器对象实例化时。是需要传入配置类的反射作为参数的。这个容器对象时候化时,就可以安装配置类中配置信息将指定的对象保存到spring容器中 // 从spring容器中获取配置类中对象,是依据存入对象的方法名进行获取的!!!! Student student1 = acac.getBean("student", Student.class); System.out.println(student1); } }
课上作业
完成Teacher类的Ioc
创建cn.tedu.teacher包
包中创建Teacher类.属性可以和Stu类一致(或做合理修改)
再创建TeacherConfig使用@Bean注解向Spring容器保存一个老师对象
创建测试类的cn.tedu.teacher包
创建测试类TeacherTest,在类中编写方法获得Spring容器中的老师对象
可以使用@Before和@After注解实现运行前后的代码复用工作
6. 关于@Bean注解
当方法的声明前添加了@Bean注解,就表示这个方法是需要由spring框架所管理的,默认情况下,该方法的名称是后续对象返回时,唯一的依据!因为spring给我们主动返回的,那么不需要自行编程代码来调用这个方法,spring建议“使用合理的属性名称作为方法名称,不需要添加动词或者动词作为前缀的方法”!
如果不遵循spring的建议,我们还可以通过@Bean注解来配置自定义的获取名称!
@Bean("stu") public Student student(){ Student student = new Student(); student.setGender("男"); student.setName("张三"); student.setId(00001); return student; }
后续获取Student对象时,需要通过:stu
来从spring容器中获取, 使用方法名student获取报错
7. 使用组件扫描实现IOC
组件扫码的方式较为简单,直接在实体类上添加@Component注解,表面当前的类的对象是可以添加到spring容器中的,添加到容器中的动作,依旧是通过配置类完成的,且需要添加扫描注解@ComponentScan("全包名")【注意,当类添加了@Component注解后,但是没有被扫描,依旧是没法被spring管理的!】
还有其他的注解可以完成和@Component注解一样的功能:
-
@Controller: 控制器
-
@Service : 业务逻辑
-
@Repository : 数据仓库
他们的前区别仅仅是语义上的区别,功能完全一致!
组件扫描生成对象id的有特殊情况
类名 对象id
VIPCotroller -> VIPCotroller
Cat - > cat
MYbatis - > MYbatis
MyBatis - > myBatis
myBatis - > myBatis
一般情况下,没有自定义id的对象会使用当前类的类名称首字母小写的字符串作为对象id的名称
特殊情况下,如果类名称是连续的两个或者两个以上的大写字母开头,就没有这个变化了
@Component //@Controller //@Service //@Responsitory // 以上的四个注解作用一致,只是语义不同,表示当前的类的对象是可以被spring所管理的 public class Cat { private Integer age = 15; private String name ="小花"; private String color="黑色"; // 还可以创建构造方法来进行实例化属性值 // getter setter toString方法 } @Configuration // 当添加了该注解,spring就会主动将该类标识为配置类 // 添加了@ComponentScan注解后,spring就会扫码指定包下的所有添加了@Component注解的类 // 并将类的对象实例化后存储到容器中 @ComponentScan("cn.tedu.spring.cat") // 注意:扫描的范围越具体越好 public class CatConfig { }
8. Spring管理对象的作用域
Spring Bean管理就是管理spring容器中的java对象!
Bean 的作用域(Scope):其实就是值spring容器中某个id的对象在被多次获取时,返回的是同一个还是新的多个不同对象!
spring在设计这个需要时,分别设置了两个作用域来与之对应!
-
singleton(单例):从spring容器获取不管多少次,返回的都是同一个对象
-
prototype(原型):从spring容器中每获取一次,都返回的是新的对象
单例作用域
Spring框架默认情况下,保存到容器中的对象是单例的
!
原型作用域
需要通过注解@Scope("protopyte") 指定当前的组件是原型模式,也就是多例!
spring中有一个比较重要的Bean管理方式:”惰性初始化“
惰性初始化有些时候还被称之为:“懒加载”
惰性初始化指spring容器中单例作用域
的对象是通过设置来修改实例化当前对象的时机的配置!
两种时期:
一种是在spring容器实例化是就实例化当前对象
另一个种就是在spring容器中获取该对象时才进行实例化该对象
设置懒加载:@Lazy(value = true)
,默认情况下spring的加载是“饿加载”
@Lazy(value = true) // 懒加载 @Scope("singleton") // 单例模式 @Component //@Scope("prototype") public class Teacher { public Teacher (){ System.out.println("Teacher构造方法运行了!!!"); } // 其他属性和方法 } /* 测试类 */ public class TeacherTest { AnnotationConfigApplicationContext acac; @Test public void test(){ Teacher teacher1 = acac.getBean("teacher",Teacher.class); Teacher teacher2 = acac.getBean("teacher",Teacher.class); teacher2.setName("熊大"); System.out.println(teacher1); System.out.println(teacher2); System.out.println(teacher1 == teacher2); } @Before public void init(){ acac = new AnnotationConfigApplicationContext(TeacherConfig.class); System.out.println("spring容器初始化...."); } @After public void destory(){ acac.close(); } }
输出结果:
spring容器初始化....
Teacher构造方法运行了!!!
对象创建,是在spring容器初始化后,被调用时才完成的! --- 懒加载
小结:
-
Spring的主要作用:创建对象,管理对象;
-
Spring框架管理类的对象的做法:
-
自定义方法返回某个对象,并在方法的声明之前添加
@Bean
注解;且所有添加了@Bean
注解的方法,其所在的类应该添加@Configuration
注解,凡添加了@Configuration
注解的类称之为配置类;(1)Bean的名称(对象id)可以自定义:
@Bean("自定义名称")
(2)默认情况下,自定义方法的名称,就是被spring管理的Bean名称(对象id)
-
使用组件扫描方式,需要在创建对象的类上面添加:
@Component
/@Controller
/@Service
/@Repository
注解,表示当前类是可以被spring管理且创建对象的;与此同时,需要在配置类上添加组件扫描的注解@ComponentScan("指定包名")
;spring启动时就会扫描指定包及其子包下的所有添加了组件注解的类,将其管理!(1)使用组件扫描方式时,必须保证被spring管理的对象所归属的类存在无参构造方法
(2)Bean的名称(对象id)可以自定义:
@Component("自定义名称")
(3)默认情况下,如果被管理的类类名的第一个字母是大写的,第二个字母是小写的(不关心其他字母),则会把类名的第一个字母改为小写,其他不变作为Bean的名称,也就是对象id,再获取对象时使用!
如果不满足以上情况,则类名就是Bean的名称,即:对象id。
-
-
默认情况下,由Spring管理的对象是单例的,使用
@Scope
注解可以将Spring管理的对象调整为“非单例”的; -
默认情况下,由Spring管理的单例的对象是是“饿汉式”的,使用
@Lazy
可以将它们改为“懒汉式”的。
9. 由spring管理的对象的生命周期
如果需要管理Bean的生命周期,spring框架提供了解决方案,可以在对于的类中自定义生命周期方法(初始化方法、销毁方法)
关于初始化方法和销毁方法,spring有做明确的说明:
-
应该使用public权限
-
使用void 表示没有任何返回值
-
方法名称可以自定义
-
参数列表为空
public class User { public User(){ System.out.println("无参构造方法运行了......"); } public void initUser(){ // 被指定为初始化的方法后,该方法在容器实例化之前运行,且只执行一次 System.out.println("initUser()运行了......"); } public void destroyUser(){// 被指定为销毁方法后,该方法在容器销毁时运行,且只执行一次 System.out.println("destroy()运行了......"); } } /* 配置类 */ @Configuration // 表示当前的类是一个配置类 //@ComponentScan("cn.tedu.spring.user") public class UserConfig { @Bean(initMethod = "initUser", destroyMethod = "destroyUser") // 指定方法 public User user(){ User user = new User(); return user; } } /* 测试类 */ public class UserTest { AnnotationConfigApplicationContext acac; @Test public void test(){ // 2. 获取容器中的指定对象 User u = acac.getBean("user",User.class); System.out.println(u); } @Before public void init(){ // 1.实例化spring容器 acac = new AnnotationConfigApplicationContext(UserConfig.class); System.out.println("spring容器初始化...."); } @After public void destory(){ // 3. 关闭容器 acac.close(); } }
输出结果:
无参构造方法运行了......
initUser()运行了......
spring容器初始化....
cn.tedu.spring.user.User@7a4ccb53
destroy()运行了......
10. 使用Spring读取.properties文件
-
在项目的 src/main/resources 下撰写jdbc.properties文件,
-
然后,在项目中,自定义某个类,在这个类中,声明对应数量的属性,这些属性的值将会是以上配置信息的值!
-
当需要读取以上jdbc.properties配置文件时,需要在以上类的声明之前添加
@PropertySource
注解,并配置需要读取的文件的位置: -
接下来,就可以把读取到的值赋值给类中的属性,可以通过
@Value
注解来实现: -
最后,整个的读取过程是由Spring框架来完成的。所以,以上JdbcProperties类还应该被Spring框架所管理,可以采取组件扫描的做法,则创建配置类,用于指定组件扫描的包
-
然后,在
JdbcProperties
类的声明之前,补充添加@Component
注解,使得Spring框架扫描到这个类时,能明确的知道“这个类是组件类”,从而创建该类的对象 -
全部完成后,可以自定义某个类,用于测试运行
@PropertySource(value = "jdbc.properties") @Component // 表示当前的类是可以被spring管理组件 public class JdbcSpring { @Value(value = "${db.driver}") // 在注解参数的大括号中的值,是jdbc.properties文件中等号左侧的值 private String driver; @Value(value = "${db.url}") private String url; @Value(value = "${db.username}") private String username; @Value(value = "${db.password}") private String password; @Value("${db.maxActive}") private Integer maxActive; @Value("${db.initialSize}") private Integer initialSize; // getter setter toString方法 } /* 配置类 */ @Configuration @ComponentScan("cn.tedu.spring.tools") public class SpringConfig { } /* 测试类 */ public class JdbcSpringTest { AnnotationConfigApplicationContext acac; @Test public void test(){ // 2. 获取容器中的指定对象 JdbcSpring jdbcSpring = acac.getBean("jdbcSpring",JdbcSpring.class); System.out.println(jdbcSpring); } @Before public void init(){ // 1.实例化spring容器 acac = new AnnotationConfigApplicationContext(SpringConfig.class); System.out.println("spring容器初始化...."); } @After public void destory(){ // 3. 关闭容器 acac.close(); } }
需要注意的是:
属性的赋值全程是有spring框架完成的,且spring框架会自动处理数据类型的转换,所以在声明属性时,声明为所期望的类型就可以了!
Spring读取.properties文件内容
使用DI依赖注入的形式也是可以完成spring自动读取jdbc.properties文件内容并赋值给属性的过程
@PropertySource(value = "jdbc.properties") @Component // 表示当前的类是可以被spring管理组件 public class JdbcSpringDI { // 当前的类是依赖于Environment @Autowired // 添加该注解后,当spring管理了该类。扫描该注解,则会主动为该变量赋值(创建对象) private Environment environment; public Environment getEnvironment() { // 当前environment一定是有值的,因为spring加载是饿加载模式,也就是spring容器初始化前对象就创建好 return environment; } } /* 配置类 */ @Configuration @ComponentScan("cn.tedu.spring.tools") public class SpringConfig { } /* 单元测试 */ @Test public void test1(){ // 2. 获取容器中的指定对象 JdbcSpringDI jdbcSpring = acac.getBean("jdbcSpringDI",JdbcSpringDI.class); System.out.println(jdbcSpring); System.out.println(jdbcSpring.getEnvironment().getProperty("jdbc.driver")); System.out.println(jdbcSpring.getEnvironment().getProperty("jdbc.url")); System.out.println(jdbcSpring.getEnvironment().getProperty("db.username")); System.out.println(jdbcSpring.getEnvironment().getProperty("db.password")); System.out.println(jdbcSpring.getEnvironment().getProperty("db.maxActive")); System.out.println(jdbcSpring.getEnvironment().getProperty("db.initialSize")); }
可以看到。使用这种做法时,spring框架会自动读取所有配置信息到 Enviroment
类型的对象中,当需要时直接从这个对象的属性中获取:获取方式是: getProperty("指定获取的key")
返回值类型是String类型,如果希望的数据类型不是String,则需要手动进行类型转换!
一般情况下,推荐使用@Value
注解的方式进行一一配置值!使用起来更加灵活!
11. DI 依赖注入
DI : Dependecy Injection
依赖注入!
Spring框架通过DI实现了IOC,通个注解或者某些方法(做法)将对象的控制权交给spring框架!
程序中对象的依赖关系:
A类中的方法需要使用到B类的对象才能执行,那么我们就讲A类是依赖于B类的!
那么依赖注入就是将一个复杂的对象中各个组件进行赋值装配的过程!进而最终能够实现,从spring容器中直接获取装配好的对象功能!
如果容器中不存在需要注入类型的对象,则报错!
expected at least 1 bean which qualifies
如果容器中存在需要注入类型对象,超过了1个,则报错!
expected single matching bean but found 2: saw1,saw2
解决以上的问题有两种方案:
-
在使用的属性类型前添加
@Qualifier
注解来指定使用的Bean具体是哪个@Qualifier("saw1") Saw saw; 表示当前对象注入的saw1对象ID指代的Bean @Bean public Worker worker(@Qualifier("saw2") Saw saw){ // 通过添加了@Bean注解的方法的参数列表,可以将所需的对象从spring容器转入 Worker worker = new Worker(); // 将spring传入的对象添加到work对象中 worker.setSaw(saw2); return worker; }
-
直接修改传递的属性名称,其属性名称一定要和spring容器中已有的对象id对应
@Bean public Worker worker(Saw saw2){ // 通过添加了@Bean注解的方法的参数列表,可以将所需的对象从spring容器转入 Worker worker = new Worker(); // 将spring传入的对象添加到work对象中 worker.setSaw(saw2); return worker; }
Spring 框架默认的注入匹配规则: javaBean匹配规则(注入规则)
大体逻辑:
首先安装变量类型匹配注入,如果变量类型有冲突,则按照名称进行注入!
-
按照类型进行匹配:
-
将注入变量类型和Spring容器中Bean的类型进行匹配
-
如果匹配不成功,则直接抛出异常!
-
如果按照类型匹配到
唯一
的一个对象!则注入成功! -
如果按照类型匹配到两个或者两个以上的对象,就会进一步按照Bean名称(对象id)匹配
-
-
-
按照Bean名称进行匹配:
-
根据注入的变量名和Bean的名称进行匹配
-
如果没有匹配到唯一的一个Bean对象,就会直接抛出异常
-
如果匹配到了一个Bean对象,则注入成功!
-
-
-
使用@Bean实现组件扫描实现注入
-
使用组件扫描实现注入
@Autowired 用法:
-
@Autowired可以标注在属性上,包括私有属性,spring会利用java的反射机制API将属性进行值的注入
-
在spring 5.0版本开始,不推荐在属性上使用@Autowired注解,直接对属性进行注入是程序员的开发习惯,尽量不推荐但是是被广泛使用的!
- @Autowired private Axe axe;
-
-
@Autowired 可以标注在set方法上,利用set方法进行注入
-
@Autowired // 当前的方法没有返回值,且添加的注解不是@Bean,那么spring进行管理时是不会创建任何对象的! // 添加了@Autowired注解,spring进行管理时,发现有参数列表操作,那么spring就会在容器中查找该类对象! // 如果有且唯一,则注入成功! 否则找到的该类型对象不止一个,则按照名称进行注入,匹配则注入成功,反之直接报错 // 如果能够给参数列表中的属性注入值成功,则给成员变量的赋值也应一定成功! public void setAxe(Axe axe) { this.axe = axe; }
-
-
@Autowired 可以标注在构造方法上,实现构造器参数注入
-
@Autowired // 构造方法完成对象注入 public Worker(Axe axe){ this.axe = axe; }
-
@Resouce
@Resource 和@Autowired注解的作用是一模一样的
-
@Autowired注解 是有spring框架提供的
-
@Resource 注解是java提供的注解,但spring框架是支持的;
-
@Resource 可以标注在属性、还可以标注在set方法上,但不能标注在构造器上面
-
-
@Resource 匹配规则
-
首先根据注入变量名在Spring匹配查找唯一的Bean ID
-
如果匹配到唯一的一个JavaBean 就注入成功
-
如果按照名字没有匹配到唯一的JavaBean, 则继续按照类型匹配:
-
-
根据注入时候的变量类型,在Spring查找唯一的一个java Bean
-
如果匹配到唯一的一个JavaBean, 则注入成功
-
如果匹配到两个以上的JavaBean, 则抛出异常
-
-
-
@Resource 和 @Autowired 的区别? (经典面试题目)
12. 利用DI依赖注入进行解耦
强耦合: 组件之间紧密连接关系
松散耦: 组件之间松散连接关系