Spring 春天
一 Spring简介
1.1 为什么需要spring?
在软件设计中 ,有一种模式叫做 MVC 。称之为 Model 模型 View 视图 Controller 控制器。
但是我们的javaweb项目并没有完全实现mvc中所有的内容。因为我们没有事件驱动。
而javaweb在发展的时候 ,其实为玩 Model 1 、Model 1 二代 、Model 2
Model1 时期整个请求的流程:
Model 1 二代时期整个请求的流程:
Model2 时期整个请求的流程:
servlet负责 获取请求、处理业务、调用DAO、共享数据、选择视图
JAVAWeb由Model2 演变成 三层架构 :
在原始的三层架构中 ,我们都是使用正转进行操作的,例如:
当我们需要使用某一个类的对象时,直接通过new去创建该类的对象,这种操作就称之为 正转操作。
但是正转操作是一种高耦合(多个类之间关联比较密切,会出现如果一个类修改,会导致一系列的类都需要进行修改)的表现。以前java代码都是比较笨重的,称为诟病的就是因为都在进行正转操作。
此时我们试想一下,如果StudentServiceImpl这个类升级了,换成了StudentServiceImpl2,此时我们需要去所有使用StudentServiceImpl的地方将其换成StudentServiceImpl2。
所以正转操作不是很好,我们可以使用 工厂模式 来解决这个问题。
例如: People Man Women 都使用到了 Food这个类
我们如果直接正转操作使用 food 此时将来如果food变成food2 此时改起来不好改,所以我们使用简单工厂模式。
还有一个核心问题没有解决,当我们将工厂中的food换成food2的时候 ,此时并没有实现一个效果,所有的类都改完成, 而是全部报错
原因在于 工厂此时返回值为 Food2 而我们接受的类型为Food,此时类型不匹配。这个问题怎么解决?
可以创建一个接口,我们使用接口类型去接收实现类对象-----接口多态。
我们就解决了正转的问题;创建对象的时候不再是直接new对象 ----控制正转
而是将创建对象的操作放到工厂中,需要使用对象的时候去工厂中获取----控制反转 IoC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。使用对象的时候不再是直接new,而是将创建对象的权利交给框架中的核心容器,需要使用对象的时候直接从容器中获取。
IoC的思想早就出现了,但是没有一个具体的实现,大家都是各自根据这个思想去写代码(自己去创建工厂)。
后来有一个大师 Rod Johnson(Spring Framework创始人,著名作者。 Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位。),写了一个框架,将IoC思想具体的实现了,这个框架后来起了个名字叫做 Spring。(因为在这之前 大家都是用hibernate这个框架,这个框架中文名字叫做冬眠,并且这个框架比较笨重,耦合比较高。Rod Johnson写的框架就是用来解决类似于hibernate高耦合的问题 ,所以他将自己框架的名字命名为 Spring 告诉全世界 冬天过去了 春天来了)
1.2 spring产品
spring的官方网站:https://spring.io/
我们可以看到spring有很多产品 spring已经变成了一个生态圈。但是我们平时还是需要将spring理解成Spring Framework
我们看一下spring-framework组成
Test: spring自带的单元测试
Core Container: 核心容器。这是IoC思想的关键,Spring负责创建对象 并且存放到 核心容器中。
AOP:面向切面编程
Web:与javaweb相关的组件
Data Access : 与数据库操作/JDBC操作相关组件
1.3 spring项目的构建
A 创建maven工程
B 在maven工程中添加spring的pom依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
C src/main下创建一个文件夹 sources 并且将文件夹的格式改成resources root 。在里面添加spring的配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
D 创建一个测试类 测试spring的使用。
二 Spring的IoC操作
2.1 传统的正转操作
@Test
public void test1() throws Exception{
People people = new People();
people.eat();
}public class People {
public void eat(){
System.out.println("人类都要吃饭");
}
}
2.2 通过spring核心容器获取
A 在xml中配置当前类的信息(告诉spring 有哪些类的对象 需要spring核心容器去管理)
<bean id="p1" class="com.aaa.test.People"></bean>
B 使用spring的工厂去获取对应的bean 。 BeanFactory
@Test
public void test1(){
ClassPathResource resource = new ClassPathResource("application.xml");
BeanFactory factory = new XmlBeanFactory(resource);
People p1 = (People) factory.getBean("p1");
p1.eat();
}
原理思考:
2.3 ApplicationContext获取bean
@Test
public void test2() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
People p1 = (People) context.getBean("p1");
p1.eat();
}
面试题: ApplicationContext 和 BeanFactory的区别?
BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
1) 国际化(MessageSource)
2) 访问资源,如URL和文件(ResourceLoader)
3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
4) 消息发送、响应机制(ApplicationEventPublisher)
5) AOP
并且二者之间对bean的初始化时机(创建对象的时机)是不一样的。
BeanFactory是遵循懒加载模式创建对象,也就是说BeanFactory被创建的时候并没有初始化bean,只有getBean的时候才去创建对象。
ApplicationContext是遵循饿汉模式创建对象,因为ApplicationContext对象被创建的时候,同时会初始化bean。
我们可以通过lazy-init设置成懒加载模式。
2.4 getBean()的三种方式
A 通过 id 或者 name的值获取bean
Object p1 = context.getBean("p1");
B 通过类型去获取
People bean = context.getBean(People.class);
确保bean的类型是唯一的 不然会报错:org.springframework.beans.factory.NoUniqueBeanDefinitionException
C 通过 id + 类型去获取
People bean = context.getBean("p1",People.class);
2.5 bean的属性配置
id : 当前bean的唯一标识符
class:配置类的全限定名
name:配置当前bean的名字,并且可以配置多个。以前使用的比较多,在spring3之前 name可以使用符号例如 : /stu 而id不可以。
后来id也可以添加符号例如/ 所以通常使用id比较多。
lazy-init : 配置bean的初始化时机 是否是懒加载。设置成true代表懒加载。还可以全局设定在beans标签中设定default-lazy-init="true"
scope:作用域 默认是 singleton单例的 prototype多例的 request请求 session会话 global session 全局会话
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
People bean1 = context.getBean("p1",People.class);
People bean2 = context.getBean("p1",People.class);
System.out.println(bean1 == bean2);//true
有的类 对象被创建的时候需要调用初始化函数,对象被回收的时候需要调用销毁函数。现在对象时由spring核心容器去管理的。
所以这些函数的调用应该由spring负责。我们只需要通过配置告诉spring初始化函数是谁,销毁函数是谁。bean的生命周期函数
init-method:
destroy-method:
<bean id="p1" name="aa bb cc" class="com.aaa.test.People" lazy-init="true" scope="singleton" init-method="hehe" destroy-method="haha"></bean>
@Test
public void test2() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
People bean1 = context.getBean("p1",People.class);
context.close();
}
注意,此时scope不能是多例的 否则销毁函数不会调用
作业:A 理论记住 B 测试:创建工程 添加依赖 创建配置文件 配置bean 通过ApplicationContext完成调用
三 DI依赖注入
3.1 对象关系----依赖关系
面向对象设计 对象间关系:依赖、关联、聚合和组合。
关联关系: 例如 人类和电脑类
某个对象会长期的持有另一个对象的引用,而二者的关联往往也是相互的。
public class People {
int id;
String name;
String nickname;
String address;
Computer c;
}public class Computer {
int id;
String name;
String cpu;
}@Test
public void test1(){
People people = new People();
people.id = 20;
people.name = "张三0";
people.c.name = "外星人";
}
依赖关系: Servlet 和service
所谓依赖就是某个对象的功能依赖于另外的某个对象,而被依赖的对象只是作为一种工具在使用,而并不持有对它的引用。
public class StudentServlet {
StudentService service;
}
3.2 依赖注入---DI Dependency Injection
当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
依赖注入有两种:设值注入、构造注入
所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。
在我们开发中会出现对象依赖的情况 ,例如 StudentServlet 依赖于 StudentService。在以前的时候我们都是直接在Servlet中new业务层Service对象,这是一个正转操作,会有高耦合。我们使用Spring框架,要遵循IoC,让对象的创建都交给Spring的核心容器。此时所有的对象都是Spring管理,也就是说 StudentServlet 是Spring 管理的,StudentService也是Spring管理的。
通过依赖注入 我们在类中需要使用另一个类的对象时,完全不需要自己创建了,而是让spring去帮我们创建并注入
IoC是一种思想,在在Spring中 DI 是这个思想的具体实现方式。
3.3 依赖注入的两种方式
A set方法注入
public class StudentServlet {
StudentService haha;
public void setHaha(StudentService haha) {
this.haha = haha;
}
@Override
public String toString() {
return "StudentServlet{" +
"haha=" + haha +
'}';
}
}public class StudentService {
}xml配置DI依赖注入的关系
<bean id="servlet" class="com.aaa.test1.StudentServlet">
<property name="haha" ref="hehe" ></property>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>调用代码:
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Object servlet = context.getBean("servlet");
System.out.println(servlet);
}
B 构造函数注入
public class StudentServlet {
StudentService haha;
public StudentServlet(StudentService haha) {
this.haha = haha;
}
@Override
public String toString() {
return "StudentServlet{" +
"haha=" + haha +
'}';
}
}<bean id="servlet" class="com.aaa.test1.StudentServlet">
<constructor-arg name="haha" ref="hehe" ></constructor-arg>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>3.4 依赖注入的底层思考
反射是框架的灵魂
@Test
public void test2() throws Exception{
/*
<bean id="servlet" class="com.aaa.test1.StudentServlet">
<property name="haha" ref="hehe" ></property>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>
*/
Class<?> aClass = Class.forName("com.aaa.test1.StudentServlet");
Object o = aClass.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor p:descriptors) {
if(p.getName().equals("haha")){
Method writeMethod = p.getWriteMethod();
writeMethod.invoke( o , Class.forName("com.aaa.test1.StudentService").newInstance() );
}
}
System.out.println(o);
}3.5 注入值的形式
A 注入对象
<bean id="servlet" class="com.aaa.test1.StudentServlet">
<property name="haha" ref="hehe" ></property>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>注意 注入对象的时候 使用的是 ref
B 注入简单值
注意 注入对象的时候 使用的是 value
<bean id="dao" class="com.aaa.test1.BaseDAO">
<property name="className" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>public class BaseDAO {
private String className;
private String url;
private String username;
private String password;
public void setClassName(String className) {
this.className = className;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "BaseDAO{" +
"className='" + className + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}代码调用
@Test
public void test3(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Object servlet = context.getBean("dao");
System.out.println(servlet);
}C 注入集合框架
public class People {
List<String> tels;
Map<String,Object> friends;
public void setTels(List<String> tels) {
this.tels = tels;
}
public void setFriends(Map<String, Object> friends) {
this.friends = friends;
}
@Override
public String toString() {
return "People{" +
"tels=" + tels+"friends:"+ friends +
'}';
}
}<bean id="p1" class="com.aaa.test1.People">
<property name="tels">
<list>
<value>15966663333</value>
<value>15166333333</value>
<value>18966663333</value>
<value>15933336666</value>
</list>
</property>
<property name="friends">
<map>
<entry key="f1" value="张三"></entry>
<entry key="f2" value="李四"></entry>
<entry key="f3" value="王五"></entry>
<entry key="f4" value="赵六"></entry>
</map>
</property>
</bean>
3.6 自动注入的形式
<bean id="haha" class="com.aaa.test1.StudentServlet">
<property name="haha" ref="hehe"></property>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>这是我们以前注入的形式 ,是手动维护类和类之间的依赖关系,此时没有问题。但是不优秀,因为在一个项目中会有很多的类需要关系的维护,我们都是通过 propertity进行手动操作很麻烦,而是配置文件会很庞大。
所以spring提供了一套自动注入的形式
xml形式:
public class StudentServlet {
StudentService hehe;
public void setHehe(StudentService hehe) {
this.hehe = hehe;
}
@Override
public String toString() {
return "StudentServlet{" +
"haha=" + hehe +
'}';
}
}<bean id="haha" class="com.aaa.test1.StudentServlet" autowire="byName"></bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>autowire自动注入,此时设定的是byName按照名字去注入,此时在servlet中有一个成员变量名字是hehe,所以就会去所有的bean中找id为hehe的bean,找到了则注入。没找到则抛异常。
<bean id="haha" class="com.aaa.test1.StudentServlet" autowire="byType"></bean>
<bean id="heihei" class="com.aaa.test1.StudentService"></bean>autowire自动注入,此时设定的是byType按照类型去注入,此时servlet中成员变量的类型为 com.aaa.test1.StudentService, 所以就会去所有的bean中找class为com.aaa.test1.StudentService,找到了则注入,没有就抛异常,找到多个也抛异常。
注解形式:
A 配置当前spring需要管理哪些bean(类)
B 配置哪些依赖需要spring帮忙注入
######
第一步: 开启注解扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<!--注解配置-->
<context:annotation-config></context:annotation-config>
<!--注解包扫描-->
<context:component-scan base-package="com.aaa.test1" ></context:component-scan>
</beans>第二步:通过注解 @Component 配置 哪些类需要spring管理
@Component("haha")
/*<bean id="haha" class="com.aaa.test1.StudentServlet" />*/
public class StudentServlet {
StudentService hehe;
@Override
public String toString() {
return "StudentServlet{" +
"haha=" + hehe +
'}';
}
}@Component("heihei")
public class StudentService {
}
第三步:通过注解@AutoWired注解告诉spring需要进行依赖注入
@Autowired
StudentService hehe;
3.7 @Autowired 和 @Resource的注入原则/装配顺序
@Autowired:
当自动注入bean的时候 首先根据类型去匹配
如果一个都匹配不到,则查看是否配置(required = true) 如果没有配置或者配置的是true,就代表必须注入,此时则抛出异常。
如果配置的是false,代表不是必须注入,此时如果找不到没救不注入。(注意:如果设定为false,又没有注入成功 ,在使用当前成员变量的时候 会出现空指针异常。)
如果正常匹配到一个 ,则按照类型正常注入。
如果匹配到多个bean的类型都是同一类型。则根据当前变量的名字匹配对应的bean的名字。如果匹配到 则进行注入。
如果匹配不到 则 抛出异常
如果配置了@Qualifier("heihei") 则根据@Qualifier("heihei")的名字匹配对应的bean
@Resource
import javax.annotation.Resource;
是我们JDK自带的一个注解
a. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
b. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
c. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
d. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配(名字没有一样的),则回退为一个原始类(相当于按照类型取查找一个实现类,如果按照类型取查找的实现类有多个 则抛出异常)
3.8 注解配置的语义化注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
String value() default "";
}
我们需要在控制层添加 @Controller
在业务层添加 @Service
在数据持久层添加@Repository
在其他的地方都是用@Component