秋招10月底结束后,自己就又恢复之前的堕落了,没有了秋招的那股劲和上进心,最近也开始要毕设了,自己还是一样的渣,决定站起来,马上都快就业的人什么都不会有点说不过去了!站起来,我还能学!于是乎为毕设做点准备,开始着手学习框架,首先让我们从万能的spring开始着手学起。
——>spring设计的领域有:
移动开发、社交API集成、NoSQL数据库、云计算以及大数据都是spring正在涉足和创新的领域
——>spring的常用术语:
1、框架:是能够完成一定功能的半成品
2、非侵入式设计:从框架的角度理解,就是无需继承提供的任何类
3、轻量级和重量级:轻量级相对于重量级而言的,轻量级一般就是非侵入性的、所 依赖的东西非常少、资源占用非常 少、部署简单等等;即比较容易使用。
4、JavaBean:符合javaBean规范的类
5、pojo:(Plain Old Java Object),简单老式java对象
6、容器:装对象的对象,并且管理对象的生命周期
一、首先我们来了解一下什么是spring?
1、spring是一个轻量级的DI/IOC和AOP容器的开源框架
2、spring提倡以“最少侵入“的方式来管理应用中的代码
3、spring的根本使命:简化java开发
二、让我们对spring的整体框架有个了解:
- Data Access/Integration层包含:JDBC、ORM、OXM、JMS和Transaction模块
- Web层包含:Web、Web-Servlet、WebSocket、Web-Porlet模块
- AOP模块:提供了一个符合AOP联盟标准的面向切面编程的实现
- Core Container(核心容器) :包含Beans、Core、Context和SpEL模块
- Test模块:支持使用Junit和TestING对Spring组件进行测试
三、Spring的优势有哪些?
- 低侵入/低耦合:降低组件之间的耦合度,实现软件各层之间的解耦
- 声明式事务管理:基于切面和惯例
- 方便集成其他框架:如mybatis、Hibernate
- 降低java开发难度
- Spring框架包括了J2EE三层的每一层的解决方案(一站式)
四、Spring能帮我们做什么?
1、能够帮助我们根据配置文件创建及组装对象之间的依赖关系
2、能够帮助我们无耦合的实现日志记录,性能统计,安全控制
3、能非常简单的帮我们管理数据库事务
4、能提供第三方数据访问框架无缝集成,而且自己也提供了一套JDBC访问模块来方便数据库访问
5、提供与第三方Web框架无缝集成,而且自己也提供了一套Spring MVC框架,来方便web层的搭建
6、spring能方便的与java EE(如Java Mail、任务调度)整合,与更多技术整合比如缓存框架
接下来我们步入Spring的正式学习阶段:
Spring IOC和DI简介
IOC:Inverse of Control,控制反转,就是原来在程序中我们采取手动创建对象,现在是交给Spring来创建对象,我们只需要获取对象,而不过问出处。也就是将对象的控制权交给了Spring框架
Spring IOC容器的设计主要是基于以下两个接口的:
- BeanFactory
- ApplicationContext
ApplicationContext是BeanFactory的子接口之一,BeanFactory是Spring IOC容器所定义的最底层接口,大部分情况下都会使用ApplicationContext作为Spring IOC的容器,接下来我们来看一下BeanFactory的一些常见方法:
1》【getBean】对应了多个方法来获取Spring IOC容器的Bean
①按照类型拿bean:bean =(Bean)factory.getBean(Bean.class);//要求实例唯一,否则无法确定
②按照bean的名字拿bean:bean =(Bean)factory.getBean("BeanName");//不太安全,IDE不会检查其安全性
③按照bean的名字和类型拿:bean =(Bean)factory.getBean("beanName",Bean.class);//推荐使用
2》 【isSingleton】用于判断是否是单例,如果判断为真,其意思是该Bean在容器中作为唯一一个单例存在的,而【isPrototype】则相反,如果判断为真,意思是当你从容器中获取bean时,容器会为你重新生成一个新的实例
3》【getAliases】方法是获取别名的方法
接下来我们来认识一个 ApplicationContext 的一些常见实现类
1、ClassPathXmlApplicationContext——读取classpath中的资源
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
2、FileSystemXmlApplicationContext——读取指定路径的资源
ApplicationContext ac = new FileSystemXmlApplicationContext("c:/applicationContext.xml");
3、XmlWebApplicationContext——需要在Web的环境下才可以运行
XmlWebApplicationContext ac = new XmlWebApplicationContext();//初始化容器
ac.setService(servletContext) ;//需要指定ServletContext对象
ac.setConfigLocation("/WEB-INF/applicationContext.xml");//指定配置文件路径,开头斜线表示web应用的根目录
ac.refresh();//初始化容器
现在,马上,立刻——我们举例实战喽(我采用的是IDEA进行操作),学习一个ApplicationContext的子类ClassPathXmlApplicationContext
1、编写spring框架的首要任务是将需要用到的jar包导入项目,设置依赖
1.1、在Package,【src】->【pojo】下新建一个【Source】类:
package pojo;
public class Source {
private String fruit; // 类型
private String sugar; // 糖分描述
private String size; // 大小杯
/* setter and getter */
}
1.2、再在【src】目录下新建一个【applicationContext.xml】文件,用于配置Spring,通过xml文件配置的方式装配我们的bean
<?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">
<bean name="source" class="pojo.Source">
<property name="fruit" value="橙子"/>
<property name="sugar" value="多糖"/>
<property name="size" value="超大杯"/>
</bean>
</beans>
1.3、再在【test】下新建一个【TestSpring】类:
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Source;
public class TestSpring {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"applicationContext.xml"}
);
Source source = (Source) context.getBean("source");
System.out.println(source.getFruit());
System.out.println(source.getSugar());
System.out.println(source.getSize());
}
}
1.4、测试运行:即可以正常拿到通过xml配置的bean,Source
运行结果如下:
橙子
多糖
超大杯
Spring IOC的容器的初始化和依赖注入:
Bean的定义和初始化在Spring IOC容器是两大步骤
Bean的定义分为3步:
1、Resource定位,通过xml配置或者注解的方式
2、BeanDefinition的载入,此时还不会创建Bean的实例
3、BeanDefinition的注册,这个过程就是将BeanDefinition的信息发布到Spring IOC容器中
这个时候还只是被定义,没有对应的实例,没有完成依赖注入,此时还不能够完全使用,Spring Bean还有一个配置选项【lazy-init】,其含义就是是否初始化Spring Bean,在没有任何配置的情况下,他的默认值为default,实际值为false,也就是Spring IOC默认会自动初始化Bean。如果将其设置为true,那么只有当我们使用Spring IOC容器的getBean()方法获取它时,才会进行Bean的初始化,完成依赖注入
装配Spring Bean详解
Spring中提供了3中方法进行配置:
- 在xml文件中显式配置(最后,简单易懂,当时用第三方类的时候,就只能采取这种方式了)
- 在java的接口和类中实现配置(其次采取,避免了xml配置的泛滥,也较为容易)
- 隐式Bean、的发现机制和自动装配原则(优先使用,减少程序开发者的决定权,简单而不失灵活)
1》通过XML、配置装配Bean
使用xml装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件,这些文件会定义配置Spring Bean的一些元素,当我们在IDEA中创建XML文件时,会有友好的提示
先来一个最简单的装配:
<bean id="c" class="pojo.Category">
<property name="name" value="测试" />
</bean>
【id
】
属性是 Spring 能找到当前 Bean 的一个依赖的编号,遵守 XML 语法的 ID 唯一性约束。必须以字母开头,可以使用字母、数字、连字符、下划线、句号、冒号,不能以/
开头。
不过id
属性不是一个必需的属性,name
属性也可以定义 bean 元素的名称,能以逗号或空格隔开起多个别名,并且可以使用很多的特殊字符,比如在 Spring 和 Spring MVC 的整合中,就得使用name
属性来定义 bean 的名称,并且使用/
开头。
注意: 从 Spring 3.1 开始,id
属性也可以是 String 类型了,也就是说id
属性也可以使用/
开头,而 bean 元素的 id 的唯一性由容器负责检查。
如果id
和name
属性都没有声明的话,那么 Spring 将会采用 “全限定名#{number}” 的格式生成编号。 例如这里,如果没有声明 “id="c"
” 的话,那么 Spring 为其生成的编号就是 “pojo.Category#0
”,当它第二次声明没有id
属性的 Bean 时,编号就是 “pojo.Category#1
”,以此类推。【class】
属性显然就是一个类的全限定名【property】
元素是定义类的属性,其中的name
属性定义的是属性的名称,而value
是它的值。【ref】
属性注入对象,引用自己定义的类对应的bean
当然也可以装配集合,例如:Set,Map,List,Array和Properties等,具体我们举例看一下
package pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class ComplexAssembly {
private Long id;
private List<String> list;
private Map<String, String> map;
private Properties properties;
private Set<String> set;
private String[] array;
/* setter and getter */
}
<bean id="complexAssembly" class="pojo.ComplexAssembly">
<!-- 装配Long类型的id -->
<property name="id" value="1"/>
<!-- 装配List类型的list -->
<property name="list">
<list>
<value>value-list-1</value>
<value>value-list-2</value>
<value>value-list-3</value>
</list>
</property>
<!-- 装配Map类型的map -->
<property name="map">
<map>
<entry key="key1" value="value-key-1"/>
<entry key="key2" value="value-key-2"/>
<entry key="key3" value="value-key-2"/>
</map>
</property>
<!-- 装配Properties类型的properties -->
<property name="properties">
<props>
<prop key="prop1">value-prop-1</prop>
<prop key="prop2">value-prop-2</prop>
<prop key="prop3">value-prop-3</prop>
</props>
</property>
<!-- 装配Set类型的set -->
<property name="set">
<set>
<value>value-set-1</value>
<value>value-set-2</value>
<value>value-set-3</value>
</set>
</property>
<!-- 装配String[]类型的array -->
<property name="array">
<array>
<value>value-array-1</value>
<value>value-array-2</value>
<value>value-array-3</value>
</array>
</property>
</bean>
总结:
- List属性为对应的<list>元素进行装配,然后通过多个<value>元素设值
- Map属性为对应的<map>元素进行装配,然后通过多个<entry>元素设值,只是<entry>包含一个键值对(key-vlue)的设置
- Properties属性为对应的<properties>元素进行装配,通过多个<properties>元素设指,只是<properties>元素有一个必填的属性【key】,然后可以设置值
- Set属性对应的<set>元素进行装配,然后通过多个<value>元素设值
- 对于数组而言,可以使用<array>设置值,然后通过多个<value>元素设置值
命名空间装配
Spring还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件
1》【c-命名空间】(是通过构造器参数的方式)
是在Spring3.0中引入的,它在XML中更为简洁的描述构造器参数的方式,要使用它的话,必须在XML的顶部生命其模式
假设现在有这样一个类:
package pojo;
public class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// setter and getter
}
在c-命名空间和模式声明之后,我们就可以使用他来声明构造器参数了
<!-- 引入 c-命名空间之前 -->
<bean name="student1" class="pojo.Student">
<constructor-arg name="id" value="1" />
<constructor-arg name="name" value="学生1"/>
</bean>
<!-- 引入 c-命名空间之后 -->
<bean name="student2" class="pojo.Student"
c:id="2" c:name="学生2"/>
c-命名空间属性名以 “c:
” 开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后如果需要注入对象的话则要跟上 -ref
(如c:card-ref="idCard1"
,则对 card 这个构造器参数注入之前配置的名为 idCard1 的 bean)
很显然,使用 c-命名空间属性要比使用 <constructor-arg>
元素精简,并且会直接引用构造器之中参数的名称,这有利于我们使用的安全性。
我们有另外一种替代方式:(索引的方式)
<bean name="student2" class="pojo.Student"
c:_0="3" c:_1="学生3"/>
2》【p-命名空间】(采用setter的注入方式)
再引入声明之后,我们就可以通过p-命名空间来设置属性(这个例子需要先删除构造函数,不然要配置<constructor-arg>元素)
<!-- 引入p-命名空间之前 -->
<bean name="student1" class="pojo.Student">
<property name="id" value="1" />
<property name="name" value="学生1"/>
</bean>
<!-- 引入p-命名空间之后 -->
<bean name="student2" class="pojo.Student"
p:id="2" p:name="学生2"/>
<!--如果属性需要注入其他Bean的话,也可以在后面跟上-ref-->
<bean name="student2" class="pojo.Student"
p:id="2" p:name="学生2" p:cdCard-ref="cdCard1"/>
3》【util-命名空间】
我们来看一下引入前后的变化:
<!-- 引入util-命名空间之前 -->
<property name="list">
<list>
<ref bean="bean1"/>
<ref bean="bean2"/>
</list>
</property>
<!-- 引入util-命名空间之后 -->
<util:list id="list">
<ref bean="bean1"/>
<ref bean="bean2"/>
</util:list>
4》【import】(引入其他配置文件)
<import resource="bean.xml" />
2》通过注解装配Bean
上面我们学习了通过XML文件的方式来装配Bean,接下来我们来学使用注解(annotation)的方式装配Bean
在Spring中提供了两种方式让Spring IOC容器发现Bean:
- 组件扫描:通过定义资源的方式,让Spring IOC容器扫描对应的包,从而把bean装配进来
- 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成
【使用@Component装配Bean】
package pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "student1")
public class Student {
@Value("1")
int id;
@Value("student_name_1")
String name;
// getter and setter
}
@Component注解:表示Spring IOC会把这个类扫描成一个bean实例,而其中的value属性代表这个类在Spring中的id
<bean id="student1" class="pojo.Student" />
相当于
@Component("student1"),甚至直接写成@Component,Spring IOC容器默认以类名来命名,作为id,只不过首字母小写
@Value注解:表示值的注入,和XML中写的value属性是一致的
这个时候我们声明了这个类,但是Spring IOC并不知道有这个Bean,所以我们就可以使用一个StudentConfig类去告诉Spring IOC:
package pojo;//该类和Student类位于同一个包名下
import org.springframework.context.annotation.ComponentScan;
@ComponentScan//代表进行扫描,默认是扫描当前包的路径,扫描所有带有@Component注解的POJO
public class StudentConfig {
}
接下来我们通过Spring IOC容器的实现类——AnnotationConfigApplicationContext去生成IOC容器:
ApplicationContext context = new
AnnotationConfigApplicationContext(StudentConfig.class);//使用AnnotationConfigApplicationContext类去初始化Spring IOC容器,他的配置项是StudentConfig类,
Student student = (Student) context.getBean("student1",
Student.class);
student.printInformation();
【弊端】:
- 对于@Component注解,他只是扫描所在包的java类,但是更多的时候我们希望的是可以扫描我们指定的类
- 通过@Value不能够注入对象
关于@Component的两个配置项:basePackages、basePackageClasses,均是为其包和子包进行扫描装配对应配置的Bean
【自动装配——@Autowired】
自动装配技术是一种由Spring自己发现对应的Bean,自动完成装配工作的方式,通过@Autowired,这个时候spring会根据类型去寻找定义的Bean然后将其注入,接下来看实例:
1、在Package【service】下创建一个StudentService接口:
package service;
public interface StudentService {
public void printStudentInfo();
}
2、为上面的接口创建一个StudentServiceImpl实现类
package service;
import org.springframework.beans.factory.annotation.Autowired;
import pojo.Student;
@Component("studentService")
public class StudentServiceImp implements StudentService {
@Autowired//表示在Spring IOC定位所有的Bean后,这个字段需要按类型注入,这样IOC容器就会寻找资源,然后注入
private Student student = null;
// getter and setter
public void printStudentInfo() {
System.out.println("学生的 id 为:" + student.getName());
System.out.println("学生的 name 为:" + student.getName());
}
}
3、编写测试类
// 第一步:修改 StudentConfig 类,告诉 Spring IoC 在哪里去扫描它:
package pojo;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"pojo", "service"})
public class StudentConfig {
}
// 或者也可以在 XML 文件中声明去哪里做扫描
<context:component-scan base-package="pojo" />
<context:component-scan base-package="service" />
// 第二步:编写测试类:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import pojo.StudentConfig;
import service.StudentService;
import service.StudentServiceImp;
public class TestSpring {
public static void main(String[] args) {
// 通过注解的方式初始化 Spring IoC 容器
ApplicationContext context = new AnnotationConfigApplicationContext(StudentConfig.class);
StudentService studentService = context.getBean("studentService", StudentServiceImp.class);
studentService.printStudentInfo();
}
}
过程:定义Bean——>初始化Bean,扫描——>根据属性需要从Spring IOC容器中搜寻满足要求的Bean——>满足要求则注入
但是自动装配也有一定的歧义,当我们同时有两个实例,那么Spring IOC就会不知所措,该引入哪一个Bean,为了消除歧义,Spring提供了两个注解:
@Primary注解:代表首要的,会优先注入使用该注解的类
@Qualifier注解:指定注入名称Bean的资源
3》【使用@Bean装配Bean】
当引入第三方包的(jar文件),往往没有这些包的资源,就无法通过@Component注解,这时候就需要使用@Bean注解,注解到方法上,使之成为Spring中返回对象为Spring的Bean资源
首先我们在Package【pojo】下新建一个类,用来测试@Bean注解
package pojo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration//该注解相当于XML文件的根元素,必须要有才能够解析@Bean注解
public class BeanTester {
@Bean(name = "testBean")
public String test() {
String str = "测试@Bean注解";
return str;
}
}
然后在测试类中编写代码,从Spring IOC容器中获取这个Bean
// 在 pojo 包下扫描
ApplicationContext context = new AnnotationConfigApplicationContext("pojo");
// 因为这里获取到的 Bean 就是 String 类型所以直接输出
System.out.println(context.getBean("testBean"));
@Bean
的配置项中包含 4 个配置项:
- name: 是一个字符串数组,允许配置多个 BeanName
- autowire: 标志是否是一个引用的 Bean 对象,默认值是 Autowire.NO
- initMethod: 自定义初始化方法
- destroyMethod: 自定义销毁方法
使用@Bean注解的好处就是能够动态获取一个Bean对象,能够根据环境不同得到不同的Bean对象,或者说将Spring与其他组件分离
【Bean的作用域】
通过这些例子,想必我们已经认识Bean了,那么Bean的作用域范围究竟多大呢?
在默认情况下,Spring IOC容器只会对一个Bean创建实例,但是有时候我们希望可以获取多个实例,这个时候我们就可以通过@Scope注解或者<bean>元素中的scope属性来设置,例如:
// XML 中设置作用域
<bean id="" class="" scope="prototype" />
// 使用注解设置作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring提供了5种作用域,他会根据情况来决定是否生成新的对象:
作用域类别 | 描述 |
singleton(单例) | 在Spring IoC容器中仅存在一个Bean实例 (默认的scope) |
prototype(多例) | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,相当于执行new XxxBean():不会在容器启动时创建对象 |
request(请求) | 用于web开发,将Bean放入request范围 ,request.setAttribute("xxx") , 在同一个request 获得同一个Bean |
session(会话) | 用于web开发,将Bean 放入Session范围,在同一个Session 获得同一个Bean |
globalSession(全局会话) | 一般用于 Porlet 应用环境 , 分布式系统存在全局 session 概念(单点登录),如果不是 porlet 环境,globalSession 等同于 Session |
注:在开发中主要使用scope=“singlen”、scope=“prototype“,对于MVC中的Action使用prototype类型,其他使用singleton,Spring容器会管理Action对象的创建,此时把Action的作用域设置为prototype
【Spring表达式语言简要说明】
Spring还提供了更加灵活的·注入方式,那就是Spring表达式,实际上,Spring EL远比以上注入方式要强大,他拥有很多功能
- 使用Bean的id来引用Bean
- 调用指定对象的方法和访问对象的属性
- 进行运算
- 提供正则表达式进行匹配
- 集合配置
我们来看一个Spring表达式的例子
package pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("elBean")
public class ElBean {
// 通过 beanName 获取 bean,然后注入
@Value("#{role}")
private Role role;
// 获取 bean 的属性 id
@Value("#{role.id}")
private Long id;
// 调用 bean 的 getNote 方法
@Value("#{role.getNote().toString()}")
private String note;
/* getter and setter */
}
DI:Dependency Injection,依赖注入,指Spring创建对象的过程,将对象依赖属性(简单值,集合,对象)通过配置设值给对象,即通过另外一种方式为对象设置属性
2.1、在Package【pojo】下新建一个【JuiceMaker】类:
package pojo;
public class JuiceMaker {
// 唯一关联了一个 Source 对象
private Source source = null;
/* setter and getter */
public String makeJuice(){
String juice = "xxx用户点了一杯" + source.getFruit() + source.getSugar() + source.getSize();
return juice;
}
}
2.2在xml文件中配置JuiceMaker对象,这里我们通过ref来注入另一个对象
<?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">
<bean name="source" class="pojo.Source">
<property name="fruit" value="橙子"/>
<property name="sugar" value="多糖"/>
<property name="size" value="超大杯"/>
</bean>
<bean name="juickMaker" class="pojo.JuiceMaker">
<property name="source" ref="source" />
</bean>
</beans>
2.3在【TestSpring】中添加如下代码:
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.JuiceMaker;
import pojo.Source;
public class TestSpring {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"applicationContext.xml"}
);
Source source = (Source) context.getBean("source");
System.out.println(source.getFruit());
System.out.println(source.getSugar());
System.out.println(source.getSize());
JuiceMaker juiceMaker = (JuiceMaker) context.getBean("juickMaker");
System.out.println(juiceMaker.makeJuice());
}
}
2.4运行结果 如下:
橙子
多糖
超大杯
xxx用户点了一杯橙子多糖超大杯
总结:IOC和DI其实是同一个概念的不同角度的描述,DI相对于IOC而言,
明确描述了”被注入对象依赖IOC容器,IOC容器配置依赖对象“
IOC如何实现的?
设想一下,我们如果自己来实现这个依赖注入功能,该怎么做?
1、读取标注或者配置文件,看看JuiceMaker依赖的是哪个Source,拿到类名
2、使用反射的API,基于类名实例化对应的对象实例
3、将对象实例,通过构造函数或者setter,传递给JuiceMaker
可以看出来,IOC其实就是一个工厂模式的升级版。
与传统方式对比:
我们之前创建对象都是通过new关键字主动创建一个对象,上面我们是通过IOC方式,对象的生命周期由Spring来管理,直接从Spring那里获取一个对象,IOC(控制反转),将控制权转交给Spring