框架是半成品软件,由它生成通用的程序,需要做的是补充配置信息,指导框架。由配置文件、注解两种方法实现。两者各有优缺点。配置文件可以统一配置,清晰明了;但代码量比较大。注解更简单便捷但信息不一目了然。
一、Spring简述:
Spring集成形框架,浅集成,更复杂。 1.0
SpringMVC属于Spring的web部分,是以Spring为基础二次开发形成的框架
SpingBoot是对Spring 的扩展,不仅仅是浅集成,深入集成,更加简便。 2.0
Spring Cloud是在SpringBoot基础上扩展的,完全针对于分布式、微服务的一套框架。 3.0
Spring的核心就是IOC和AOP。
IOC
- IOC的本质就是——把bean的管理交给框架去做,spring自己维护一个bean容器,将所有bean进行统一管理,这样一来,所有需要用到实例的场景都不需要写繁琐且重复的实例化代码,而是——简单地完成bean声明和注入依赖就可以了。
class QueryController{
UserService userService;
BookService bookservice;
QueryController() {
//需要程序自己实例化对象。
userService = new UserService;
userService.setUserDao(new UserDao());
bookservice = new BookService;
bookservice.setBookDao(new BookDao());
}
public static void main(Strings[] args) {
QueryController queryController = new QueryController();
}
}
@Controller
class QueryController{
@Autowired
UserService userService;
@Autowired
BookService bookservice;
}
AOP
- AOP的本质是——利用动态代理完成统一切面的功能,利用AOP,我们可以把一些横向的同一类型代码进行复用,比如登录拦截,身份校验,安全管理等等,这些——不需要内嵌到业务代码中,但却经常要用到的东西,就可以利用AOP来做成一个切面,然后指定需要拦截的方法,AOP通过动态代理会将切面代码加入到代理对象中,于是你执行业务代码时,相当于在执行代理对象,就会相应地调用切面方法。
一个最简单的动态代理实现如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
二、Spring的Bean
JavaBean(Java中的对象Bean)→ Spring的Bean既不用实现接口也不用继承父类,只用创建自己的类;Spring负责创建对象和处理关系。(由SPring创建对象交出创建对象权力的Bean就是Spring的Bean对象) ——Bean对象在beanfactory工厂创建——引入动态代理模式 ——SSM符合Spring的要求(一)、Bean 的完整生命周期
记住
- spring负责实例化和关系注入——IOC的核心
- 实例化后依旧不可以对对象进行操作,还需要进行一系列的操作,也是丰富对象的过程
- 容器开对象创建,容器关对象才被销毁。
传统的Java应用中,bean的生命周期: 很简单,使用Java关键字 new 进行Bean 的实例化 → 然后该Bean 就能够使用了。 → 一旦bean不再被使用,则由Java自动进行垃圾回收。
Spring管理Bean的生命周期: 复杂多了,正确理解Bean 的生命周期非常重要,因为Spring对Bean的管理可扩展性非常强,下面展示了一个Bean的构造过程
如上图所示,Bean 的生命周期还是比较复杂的,下面来对上图每一个步骤做文字描述:
-
Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化【beanfactory创建对象】
-
Bean实例化后对将Bean的引入和值注入到Bean的属性中【依赖注入di,属性注入】
-
如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法【名字方法注入】
-
如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入【工厂方法注入】
-
如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。【上下文(对象创建时所带配置信息的指令等)】
-
如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
-
如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。
-
如果bean使用init-method声明了初始化方法,该方法也会被调用
-
如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。【单例模式,保证对象使用,但该对象使用的生命周期被拉长了,长期占用内存】
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。——结束
(二)SpringBean循环依赖和三级缓存 🔍
三级缓存
https://www.cnblogs.com/semi-sub/p/13548479.html
https://baijiahao.baidu.com/s?id=1711380208642133437&wfr=spider&for=pc
只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题
(三)、Bean的两种创建方式
注解逐渐替代XML配置文件
引入支持pom.xml
maven——简化jar的引入
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
spring-beans——创建对象
spring-context——读上下文——读取配置文件
(1)基于配置文件的Bean
一般类 ——new对象
public class SomeBean {}
//调用
{
SomeBean someBean = new SomeBean();
}
Spring配置文件创建Bean对象
【任意类都是对象,且类不能由new,不能创建对象。必须由Spring来创建对象】——单例,唯一对象
=>bean factory
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--SomeBean someBean = new SomeBean()-->
<bean id="someBean" class="com.jd.javaconfig.SomeBean"></bean>
</beans>
对比
SomeBean someBean = new SomeBean();
<bean id="someBean" class="com.jd.javaconfig.SomeBean"></bean>
测试类1 ⭐
public class MyTest {
@Test
public void test1(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
//读配置文件
SomeBean sb = (SomeBean)ctx.getBean("someBean");
//getBean获得对象
System.out.println(sb);
//使用
}
}
(ClassPath☞类的根路径——src文件夹下)
src源文件,真正可以用的是编译后出现的class下的
(2)基于注解的Bean 👇
类
public class SomeBean {}
public class OtherBean {}
Spring注解创建Bean对象 ⭐
=>bean factory
// 作为Spring的主配置文件
@Configuration
public class AppConfig {
//@Bean标签表示让Spring托管bean
@Bean
public SomeBean someBean(){
return new SomeBean();
}
@Bean
public OtherBean otherBean(){
return new OtherBean();
}
}
AppConfig这个类是一个配置文件,不是XML配置文件——工厂模式
测试类2⭐
@Test
public void test() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
//读取配置信息
SomeBean sb = ctx.getBean(SomeBean.class);
OtherBean ob = ctx.getBean(OtherBean.class);
//getBean()获取对象
System.out.println(sb);
System.out.println(ob);
//使用
}
这个是基于AnnotationConfigApplicationContext来配置的。(Annotation注解,Config配置)
到这里我们就已经学完了两个重要的标签 @Configuration和@Bean, @Configuration标签表示这个类可被Spring识别的配置对象的类,只有有这个标记的标签的类才能使用 @Bean标签作用于对应的方法上面 @Bean(destroyMethod = "destory", initMethod = "init")也可以通过这样的写法来配置bean的销毁方法和初始化方法(3) @Component标签 👇
类
@Component
public class SomeBean {}
@Component
public class OtherBean {}
Spring注解创建Bean
//@Configuration标签表示这个类可被Spring识别的配置对象的类,这有有这个标记的标签的类才能使用@Bean标签作用于对应的方法上面
// @ComponentScan:开启组件自动扫描;默认情况下,它会扫描当前类所在的包及其子包中的所有标签对象加载到Spring容器
@Configuration
@ComponentScan(basePackages="com.jd.scan")
public class AppConfig {}
测试类
public class MyTest {
@Test
public void test() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
SomeBean sb = ctx.getBean(SomeBean.class);
OtherBean ob = ctx.getBean(OtherBean.class);
System.out.println(sb);
System.out.println(ob);
}
}
@ComponentScan:开启组件自动扫描; 默认情况下,它会扫描当前类所在的包及其子包中的所有标签对象加载到Spring容器
- Spring注解扩展:
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean [@Mapper]
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean
三、依赖注入(DI)
控制对象与对象的关系,注入——> 设置属性
(一)三种方式:构造器、setter方法、接口注入
例子:打印机
ink 、paper是打印机printer的两个组件
用不同注入方式——都是为了给ink和paper这两个属性赋值
(1)构造器注入
Printer类
public Printer(Ink ink, Paper paper, String brand) {
this.brand = brand;
this.ink = ink;
this.paper = paper;
}
application.xml
<bean id="a4Paper" class="org.lanqiao.print.A4Paper"></bean>
<bean id="blackInk" class="org.lanqiao.print.BlackInk"></bean>
<bean id="printer" class="org.lanqiao.print.Printer">
<constructor-arg name="ink" ref="blackInk"></constructor-arg>
<constructor-arg name="paper" ref="a4Paper"></constructor-arg>
<constructor-arg name="brand" value="惠普"></constructor-arg>
</bean>
构造器注解注入使用注解@Autowired(对非值构造方法使用@Autowired注解)
可能有循环注入问题(类似于死锁)。 抛出BeanCurrentlyInCreationException异常表示循环依赖 → 根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成实例化的东东,所以构造器的循环依赖无法解决~~~(2) setter注入
一定要提供setter方法,才可以实现setter注入。
<bean id="printer" class="org.lanqiao.print.Printer">
<constructor-arg name="brand" value="佳能"/>
<property name="ink" ref="blackInk"/>
<property name="paper" ref="a4Paper"/>
</bean>
(3)接口注入
接口注入有点复杂,被注入对象如果想要IOC容器为其注入依赖对象,就必须实现某个接口,这个接口提供一个方法,用来为被注入对象注入依赖对象,IOC容器通过接口方法将依赖对象注入到被注入对象中去。相对于前两种注入方式,接口注入比繁琐和死板,被注入对象就必须专声明和实现另外的接口
(二)两种方式:配置文件 and 注解(3)
1. @Autowired注解
Spring提供。 @Autowired(自动注入)修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。 如果默认byType注入,前提条件是只能有一个实现类。- constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
- byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。需要和@Qualifier注解配合使用。
- byType:查找所有的set方法,将符合符合参数类型的bean注入。这是默认的注入方式。
2. @Resource注解
Java提供 javax.annotation.Resource这个注解同样可以实现注入功能。 @Resource注解和@Autowired有所区别的是: @AutoWried按byType自动注入,而@Resource默认按byName自动注入。@Resource(name="a4Paper")
private Paper paper;
3. @Value注解
由于@Autowired、@Qualifier、@Resource三者自动装配只能针对于注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。因此有了@Value这个注解,@Value专门用来服务基本类型和String类型。
注意:@Value无法作用于构造方法。
四、Bean的五种作用域
正常spring框架支持两个:
- singleton(默认)
——单例模式,有且只有一个实列。好处:不会频繁的进行对象创建和销毁,内存压力小。但只能在描述一个固定作用的类且大家都可以用才可以使用(打印机),不通用的对象不可以使用(筷子)。 - prototype
——多例模式,需要频繁创建对象。可以用属性设置。每次请求对象不同
web项目,5个:
- singleton:使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。
- prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。
- request:该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。(一次请求响应,新对象)
- session:该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。
- global-session:该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
Spring默认的作用域是 :singleton
SSM仅有singleton,所有数据绑在方法的参数里
五、Bean的五种自动装配By配置文件
- no
该模式表示默认情况下,不自动装配,通过“ref”attribute手动设定。
<bean id="printer" class="org.lanqiao.printer.Printer">
<property name="ink" ref="blackInk"/>
<property name="paper" ref="b5"/>
</bean>
<bean id="colorInk" class="org.lanqiao.printer.ColorInk"/>
<bean id="blackInk" class="org.lanqiao.printer.BlackInk"/>
<bean id="a4" class="org.lanqiao.printer.A4Paper"/>
<bean id="b5" class="org.lanqiao.printer.B5Paper"/>
- byName
@Resource ——byName
该模式表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
<bean id="printer" class="org.lanqiao.printer.Printer" autowire="byName"/>
<bean id="colorInk" class="org.lanqiao.printer.ColorInk"/>
<bean id="ink" class="org.lanqiao.printer.BlackInk"/>
<bean id="a4" class="org.lanqiao.printer.A4Paper"/>
<bean id="paper" class="org.lanqiao.printer.B5Paper"/>
- byType
@Autowire —— byType
该模式表示根据Property的数据类型(Type)自动装配,Spring会总动寻找与属性类型相同的bean,若一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。
注意:使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常。
<bean id="printer" class="org.lanqiao.printer.Printer" autowire="byType"/>
<bean id="blackInk" class="org.lanqiao.printer.BlackInk"/>
<bean id="b5Paper" class="org.lanqiao.printer.B5Paper"/>
- constructor
使用构造方法完成对象注入,其实也是根据构造方法的参数类型进行对象查找,相当于采用byType的方式。即Spring会寻找与参数数据类型相同的bean,通过构造函数将其注入。
<bean id="printer" class="org.lanqiao.printer.Printer" autowire="constructor"/>
<bean id="blackInk" class="org.lanqiao.printer.BlackInk"/>
<bean id="b5" class="org.lanqiao.printer.B5Paper"/>
- default
表示默认采用上一级标签的自动装配的取值。如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。
XML 配置里的 Bean 自动装配的缺点:
1、在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性,然而,,若只希望装配个别属性时, autowire 属性就不够灵活了。
2、autowire 属性要么根据类型自动装配, 要么根据名称自动装配, 不能两者兼而有之。
3、一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些。