目录
一.导读
1)为什么要学习javaee
2)学什么:
3)之前项目流程:(servlet时代)
二.Spring核心思想
总结:
1.如何理解Spring?
2.IoC 和 DI 是啥?有什么区别?
3.Spring 最核⼼的功能是啥?
三.Spring的创建和读取:
Spring创建:
Bean对象存储
获取bean的方式:
常见面试题
四.Spring更加简单的读和取
从Spring中更简单的获取bean,实现方式
常见面试题:
2.另一种注入关键字@Resouce
@Resouce vs @Autowired
特殊情况:
五.bean的作用域和生命周期
1.什么是bean的作用域
2.bean的作用域分类:
3.设置作用域
六.Spring的执行流程:
面试题:说一下Spring 执行流程
bean生命周期
高效简单易用,学会后:针对博客系统
a)框架升级Servlet -->SSM
b)密码 明文/MD5 --->手工加盐算法
c)Session 持久升级 --->MySql/Redis[分布式服务]
a主机上登录功能无法在b上访问,a关机服务关闭,后续无法访问网站
d)功能升级(分页功能)
e)登录功能的验证升级(拦截器)
a)创建一个maven项目
b)添加servlet依赖(依照tomcat版本,选择合适版本)内置tomcat,自动springboot自动识别版本号
c)配置web.xml
d)编写代码(一个类对应一个url) 一个类可以有多个url,方法
e)运行项目(按装smart tomcat) ,下载一个tomcat在本地 直接运行
f)部署:linux上下载运行外置的tomcat,将jar放在tomcat/webapp下,重启tomcat 打包即可
Spring是什么:
Spring 是包含了众多⼯具⽅法的 IoC 容器。 (控制反转)对象的生命周期
什么是loc先看看案例:
//底盘
public class Bottom {
private Tire tire;
public Bottom(int size,String color){
tire=new Tire(size,color);
}
public void init(){
System.out.println("执行了bottom init方法");
tire.init();
}
}
public class App {
public static void main(String[] args) {
Car car=new Car(30,"bule");
car.init();
}
}
public class Car {
private Framework framework;
public Car(int size,String color) {
framework = new Framework(size,color);
}
public void init() {
System.out.println("执行了car init方法");
framework.init();
}
}
//车身
public class Framework {
private Bottom bottom;
public Framework(int size,String color) {
bottom=new Bottom(size,color);
}
public void init(){
System.out.println("执行了framework init方法");
bottom.init();
}
}
//轮胎
public class Tire {
private int size;
private String color;
public Tire(int size,String color){
this.size=size;
}
public void init(){
System.out.println("执行了轮胎初始化方法,size: "+this.size);
}
}
一旦底层的逻辑变化,整个调用链跟着变化
修改前执行顺序:Car -> Framework -> Bottom -> Tire
改进后代码:
使用控制反转(依赖注入)
package old;
//轮胎
public class Tire {
private int size;
private String color;
public Tire(int size,String color){
this.size=size;
this.color=color;
}
public void init(){
System.out.println("执行了轮胎初始化方法,size: "+this.size);
}
}
package old;
//底盘
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire=tire;
}
public void init() {
System.out.println("执行了bottom init方法");
tire.init();
}
}
package old;
//车身
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom=bottom;
}
public void init(){
System.out.println("执行了framework init方法");
bottom.init();
}
}
package old;
public class Car {
private Framework framework;
public Car(Framework framework) {
this.framework=framework;
}
public void init() {
System.out.println("执行了car init方法");
framework.init();
}
}
package old;
public class App {
public static void main(String[] args) {
Tire tire=new Tire(10,"蓝色");
Bottom bottom=new Bottom(tire);
Framework framework=new Framework(bottom);
Car car=new Car(framework);
car.init();
}
}
改进后执行顺序:Tire -> Bottom -> Framework -> Car
低耦合:Tire修改之后,整个调用链不用发生变化
对比:
控制反转控制的是对象的生命周期,将对象交给别人来控制而不是程序员
Spring 是包含了众多⼯具⽅法的 IoC 容器,什么是loc(控制反转),a类引入b类,b引入c类,之前的写法是a new b ,b new c,有一个缺陷当c变化时,整个调用链都会发生变化,当有一天传入当前对象时,不再new时,当c变化时,整个调用链还是之前的调用链,但是代码层不会发生变化,这就是依赖注入(控制反转的体现),减少了代码的耦合性。我们不需要关注具体是怎么调用的,而是在需要的时候,取出对象,对象的实现细节交给Spring来处理,这样对象的生命周期就从程序员控制转到Spirng控制了。
loc(控制反转),生命周期的托管,Spring就是一个简单的loc容器,传统意义上的读和取new对象,loc就是提前将对象引入到loc容器中,必要时候取,降耦,这个过程生命周期发生了变化
DI依赖注入,在程序运行期间动态的将某个对象引入到当前类的行为是具体的实现细节,
从广义上讲loc==di,从不同维度来描述一个问题
从狭义上讲loc是设计思想,di是具体的实现
将对象存到容器中
从容器中取出对象
1. 创建⼀个普通 Maven 项⽬。
2. 添加 Spring 框架⽀持(spring-context、spring-beans)。
org.springframework
spring-context
5.2.3.RELEASE
org.springframework
spring-beans
5.2.3.RELEASE
3. 添加启动类。
1.先创建bean对象
所谓的 Bean 就是 Java 语⾔中的⼀个普通对象,
2.将Bean对象存储到Spring中
a)在resources下创建一个spring配置文件
外部资源:resoucers非java代码就是外部资源
b)将bean对象配置到spring配置文件中
id就是对象名称(不能重复),class表示对象本身填写包名+类名,存储对象的位置,id和class类似于域名和ip的区别
c)从Spring中读取到bean对象
1)得到Spring对象
2)从Spring对象中取出bean对象
3)使用bean对象
public class App {
public static void main(String[] args) {
//先得到Spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("Spring-config.xml");
//从spring中取出bean对象
User user=(User) context.getBean("user");
//使用bean(可选)
System.out.println(user.sayHi());
}
}
Applicationcontext是一个接口,需要调用接口从指定的xml中读取bead对象
context.getbean返回object对象
方式1:根据bean名称来获得,上述事例。
User user=(User) context.getBean("user");
隐患:需要强转,可能bean对象是null对象
方式2:通过Bead类型来获取bean
User user=context.getBean(User.class);
问题:当Spring中存在相同的对象时,使用相同类型的bean就会报错
方式3:根据Bean名称+Bean对象
User user=context.getBean("user",User.class);
最常见的手法,不用担心null的问题(强转),也可以及时相同对象可以根据bean的名称区分
得到bean对象的两种方式:
BeanFactory context=new XmlBeanFactory(new ClassPathResource("Spring-config.xml"));
ApplicationContext context=new ClassPathXmlApplicationContext("Spring-config.xml");
BeanFactory vs AppliactionContext
相同点:
不同点:
ApplicationContext属于BeanFactroy的子类:BeanFactory只有最基础访问Bean的能力,而ApplicationContext除了拥有BeanFactory功能之外,还包含了更多的功能,如国际化支持,资源访问,事件传播等。
ApplicationContext 加载方式是将Bean对象一次性加载,所以后面在访问Bean对象时会很快(饿汉模式),BeanFactory需要将某个Bean时,采取加载Bean对象,所以它在执行Bean获取时,比较慢。(懒汉模式)
小结:
前置工作:
设置要扫描的路径
添加注解存储bean对象
五大类注解:
@Controller:控制器
@Service服务
@Reponsitory仓库
@Component:组件
@Configiration:配置
方法注解:@Bean ->将当前修饰方法对象存储到Spring当中
@Service举例:
@Service
public class USerService {
public void sayhi(String name){
System.out.println("hi"+name);
}
}
ApplicationContext context=new ClassPathXmlApplicationContext("Spring-config.xml");
USerService userService=context.getBean("USerService", USerService.class);
userService.sayhi("zhangsan");
不需要写从resource里面存对象代码,利用注解更简单的存,同时读取的时候要注意
当使用5大类注解时,默认情况下获取bean对象,只需要将类名首字母大小写即可。
然而,当bean对象首字母和第二个字母都是大写的时候,此时需要使用原类名才能正确获取到bean对象
1.为什么需要五个类注解
a)通过类注解可以了解当前类的用途(看到车牌陕A,就知道这是西安的车一样)
b)功能细微不同
五大类注解用途:
@Controller(控制器)归属于业务逻辑层,用来控制用户的行为,他用来检查用户参数的有效性
@Service(服务):归属于服务层,调用持久类实现相应的功能[不直接和数据库交互的,相当于控制中心]
@Reponsotory(仓库):归属于持久层,是直接和数据库进行交互的,通常每一个表都会对应一个@Configuration(配置】:归属于配置层,是用来配置当前项目的一些信息
@Component(组件):归属于公共工具类,提供某些公共方法
2.五大类注解有什么关系
小结:@Componet是@Controller,@Service,@Responsitory,@Configuration父类
3.使用方法注解存取Spring
使用@bean方法注解:将返回的对象存储到Spring当中
先建一个Student类
package com.demo.model;
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
接着建一个StudentBean用于存储bean,注意这里正常不能new,为了简化便于理解而写
注意这里不仅要加bean还要在加入类注解,因为及时一个类要扫描的方法是很多的,加上类注解加快速度。bean注解的方法一定是要返回对象的
package com.demo.component;
import com.demo.model.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class StudentBean {
@Bean
public Student student(){
//伪代码,构建对象
Student stu=new Student();
stu.setAge(18);
stu.setName("zhansgan");
stu.setId(1);
return stu;
}
}
在App下创建Spring并使用bean,注意这里第一个参数是方法名(默认情况下),第二个参数是方法返回的对象
下面我们来探讨@bean特殊情况:相同对象的存和取
当我们创建了另一个类有相同Student方法被bean注释
@Component
public class StudentBean2 {
@Bean
public Student student() {
//伪代码,构建对象
Student stu = new Student();
stu.setAge(18);
stu.setName("lisi");
stu.setId(1);
return stu;
}
}
此时,由于返回的对象都是student,在app下运行,会覆盖掉之前返回的student对象,Spring里面已经存储俩个student对应bean对象了,此时输出lisi对象,而不是zhangsan,但是不报错,要想正确区分
特殊情况下:可以设置@bean(name属性)
@Bean(name = {"s1","s2"})
Student student=context.getBean("s1",Student.class);,通过s1或者s2访问到Studnet张三的信息
注意事项:一旦起了别名原来的名字在getbean无法获取到了,只能使用设置的名称才能获取
ApplicationContext context=new ClassPathXmlApplicationContext("Spring-config.xml");
Student student=context.getBean("student",Student.class);
System.out.println(student);
灵感来自于
按照传统的方式需要三步,Spring上下文,getbean,如何简化:
方式1:属性注入:
需要在需要使用类型或者变量前加入@Autowirde关键字,如下事例:
第一种加入注解,不需要写赋的值是多少,交给Spring对象保管,第二种是程序员自定义的值,这俩方法都可以实现属性,区别是控制反转,需要的时候从Spring中拿变量或者对象即可
下面是注入一个对象
@Controller
public class StudentController {
//1.使用属性注入的方式获取bean
@Autowired
private StudentService studentService;
public void sayHi(){
studentService.say();
}
}
package com.demo.component;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void say(){
System.out.println("hi service");
}
}
可以看到StudentService是提前在Spirng中准备好的对象,(更简单的存),要想更简单的取,可以直接加入@Autowirde,实现属性注入,就可以在自己类中使用注入的对象了。(更简单的取)
属性注入的优缺点
优点:实现简单
缺点:
不能注入不可变的对象
final修饰的变量或者对象不可变,要想解决这个问题,要么是直接输入一个固定的数字或对象,或者是通过构造方法来赋值(这是jdk规定的)
只适用于ioc容器
@Autowirde来自于Spirng,spring是ioc容器,所以只适用ioc容器
更容易违背单一设计原则(针对对象是类)
这因为属性注入实现起来很简单,(没有什么惩罚力度,一个注解的事),程序员可能会滥用,在一个类中注入不用的对象,这就违背了单一设计原则
方式2:Setter注入
不正确的写法
看似没问题,实则没有和Spirng建立关系,这里的StudentService只是原生java写法,并没有声明StudentService对象从哪来(应该从Spring中取),java原生写法中并没有这样一个对象,所以引入的是空对象,后续调用sayhi方法就出错
正确写法在方法前加上注解@Autowirde
@Autowired
public void setStudentService(StudentService studentService){
this.studentService=studentService;
}
public void sayHi(){
studentService.say();
}
set注入的优缺点:
优点:更加符合单一设计原则
一个set往往针对一个对象,写法比属性注入复杂,出现滥用概率低(针对对象方法级别)
缺点:
不能注入不可变对象(final修饰对象)
如同属性注入
注入对象可被修改:
普通的set方法可能会被多次调用,后续别的类方法调用可能修改了注入对象
实现方法3:构造方法注入:
private final StudentService studentService;
// @Autowired
public StudentController(StudentService studentService){
this.studentService=studentService;
}
public void sayHi(){
studentService.say();
}
注意这里@Autowired当一个类中只有一个构造方法时,可以不写
可以看到如果有多个构造方法必须指明具体哪个构造方法@Autowired
优点:
1.可以注入一个不可变的对象
构造方法辅值
2.注入的对象不会被修改
a)加入了final修饰符
b)构造方法是随着类加载一次的(不像set有可能执行多次被修改的风险)
3.注入的对象会被完全初始化(使用构造方法带来的优点)
当使用这个类是这个类会被完全加载好,因为通过构造方法注入,后续调用一定是被完全初始化的
4.通用性更好
任何语言框架都支持构造方法,而不是仅仅只支出loc容器
缺点:没有属性注入实现简单
小结:日常开发中,使用属性注入实现更简单的读取Bean,依然是主流的实现方式(简单)
Spring有几种注入方式,他们有什么区别
这样也能实现属性注入和set注入
resouce不能用于构造方法注入(可能造成循环依赖,Spring实现的Autowired作了优化)
相同点:都是用来实现依赖注入的
不同点:
1.功能支持不同:@Autowired支持属性注入,setter注入,构造方法注入@resouce不支出构造方法注入
2.出身不同:@Autowired来自Spring框架,而@Resouce来自于jdk
3.参数支出不同:@Resouce支持更多参数,@Autiwired只支持requied参数
一个类存在多个对象,如何区分取的对象
无论resouce还是autowired都是根据类型和名称来找的,当一个类存在俩个对象时
解决方案:
@Resource(name="user1") 定义。
@Autowired需加入@Qualifier("对象名字“)
bean在整个Spring框架(项目)中的某种行为模式(特质)
案例:
有一个公共的bean供a,b使用,然而a在使用的时候悄悄改了bean对象,那么b拿到的bean是否还是完整的bean呢
上代码:
使用lombook快速构建User
package com.bit.model;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String password;
}
在Componet包中存取user对象:(姓名张三)
package com.bit.component;
import com.bit.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class Userbeans {
@Bean
public User user1(){
User user=new User();
user.setId(1);
user.setName("lisi");
user.setPassword("123");
return user;
}
}
李四使 用公用user
package com.bit.controller;
import com.bit.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private User user1;
public void getUser(){
System.out.println("LISIUser1:"+user1);
User u=user1;
u.setName("lisi");
System.out.println("u"+u);
}
}
wangwu在拿到bean对象
package com.bit.controller;
import com.bit.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserAdviceController {
@Autowired
private User user2;
public void getUser(){
System.out.println("wangwu|user2:"+ user2);
}
}
在app下执行:
UserController uc=context.getBean("userController",UserController.class);
uc.getUser();
UserAdviceController ua=context.getBean("userAdviceController",UserAdviceController.class);
uc.getUser();
执行如下:
案例来说存取的是张三的信息,李四修改后,王五再拿取该信息应该是完整的,应该输出王五,实际上拿到的是李四修改后的信息,这就体现在了bean的作用域上
解释如下:
Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同 ⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton 单例模式
1.singleton 单例模式
2.prototype 原型模式(多例模式)
只要注入一个对象,就会克隆一个对象,把新的对象给他
3.request 请求作用域
和http挂钩,spring mvc使用,
4.session会话作用域
多线程中的Threadlocal
5.application:全局作用域
6.websocket:spring websocket中
bean对象在存储的时候可以设置作用域
1. 直接设置值:@Scope("prototype")
2. 使⽤全局变量设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
上代码:
将作用域修改为原型模式(使用全局变量)
@Component
public class Userbeans {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public User user1(){
User user=new User();
user.setId(1);
user.setName("lisi");
user.setPassword("123");
return user;
}
}
可见改变作用域后取的是同一个对象,对象并没有改
1.启动容器(启动项目)
2.读取配置文件,初始化
a)使用xml直接注册bean
b)配置bean根(扫描)路径
3.将bean存储到spirng中,通过类注解进行扫描和装配
4.将bean从spring中读取出来,装配到相应的类
类似于建一个房子的声明周期,从无到有的过程
1.实例化(对应jvm中的加载)从无到有,将字节码转换成内存中的对象,只是分配了内存
2.设置属性(bean的注入和装配)ex:购买装修材料(引入外部资源)
3.初始化:ex:房子装修
a)各种通知 ex:打电话给各个装修师傅来施工(水工,电工...)
b)初始化的前置工作ex:师傅到达现场,先勘察环境,在制定方案(前置工作)
c)进行初始化的工作【使用注解@postConstruct初始化,使用xml init-method初始化,前者优先级高】ex:俩类师傅进行装修,技术好的先干
d)初始化的后置功能 ex:装修后的清理工作
4.使用bean ex房子可以入住
5.销毁bean ex拆了
代码测试:
public class App2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring-config.xml");
BeanLifeComponent beanLifeComponent=applicationContext.getBean("myComponent",BeanLifeComponent.class);
System.out.println("使用bean");
//销毁bean
applicationContext.destroy();
}
}
package com.bit.component;
import org.springframework.beans.factory.BeanNameAware;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class BeanLifeComponent implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println();
}
@PostConstruct
public void postConstruct(){
System.out.println("执行了PostConstruct");
}
public void init(){
System.out.println("执行了init-method方法");
}
@PreDestroy
public void preDesory(){
System.out.println("执行了销毁方法");
}
}