spring
文章目录
一、spring是什么
spring是分层的javaSE/EE应用的全栈轻量级开源框架,以及ioc(控制反转)和aop(面向切面编程:提供公共的经常用的类。例如日志)为内核,提供了展现层springMVC和持久层SpringJDBC以及业务层事务管理等众多的企业级应用技术。还能整合开源的许多第三方框架和类库。
二、spring优势
1.方便解耦,简化开发
通过spring提供的ioc容器,可以将对象间的依赖关系交由spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必在为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.aop编程的支持
1.通过AOP(面向切面编程)功能,方便进行面向切面的编程,许多不容易用传统OOP(面向对象编程)实现的功能可以通过AOP轻松应付。(例如:编写日志)。
2.OOP(面向对象编程)引入了继承、多态、封装,将系统的业务功能按照模块划分,每个模块用一个或多个类来表示。这样做的好处是降低了代码的复杂程度,使类可重用。
3.AOP和OOP的关系:3.1AOP是对OOP的一种补充。
3.2AOP即为面向切面编程,它把系统需求按照功能分门归类,把它们封装在一个个切面中,然后再指定这些系统功能往业务功能中织入的规则。最后由第三方机构根据你指定的织入规则,将系统功能整合到业务功能中。(注:OOP是对系统功能的封装到切面中)
3.声明式事务的支持
通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖(结合junit)进行测试工作。
5.方便集成各种优秀框架
spring可以降低各种框架的使用难度,提供了对各种优秀的框架的支持。
6.降低javaEE API的使用难度
spring对javaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
二、解决耦合问题
Bean:在计算机英语中,有可重用组件的含义。
JavaBean:用Java语言编写的可重用组件。
解决耦合中类依赖(反复通过new来获取类的代码依赖)的办法:
1.Bean工厂解决耦合
1.定义一个Bean工厂来创建这些类对象。(方法:第一步:创建一个配置文件来配置我们的类对象的内容,唯一表示=全限定类名–>“key=value”;
第二步:通过读取配置文件中的配置的内容,反射创建对象。)
第一步:写配置文件(将需要通过反射创建对象的全限定类名写入配置文件中)代码如下:
//文件名:bean.properties
accountService=com.fjut.service.impl.AccountServiceImpl
accountDao=com.fjut.dao.impl.AccountDaoImpl
第二步:(多例对象。)创建工厂类,读取配置文件,通过反射机制(通过全限定类名创建类):代码如下:
package com.fjut.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
private static Properties properties;
static{
try {
properties = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = properties.getProperty(beanName);
bean = Class.forName(beanPath).newInstance(); //反射创建对象(调用默认构造创建对象)
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return bean;
}
}
上面代码是创建多例对象,问题:多例对象效率低、占用内存多,当多个线程同时操作时不能实现对同一对象中的成员变量的改变(它会创建新的对象,初始化新的成员变量)。
结果:
package com.fjut.service.impl;
import com.fjut.dao.IAccountDao;
import com.fjut.factory.BeanFactory;
import com.fjut.service.IAccountService;
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
private int i = 1;
public void saveAccount() {
accountDao.saveAccount();
System.out.println(i);
i++;
}
}
解决方法:将反射创建对象代码放入static(静态代码块中)。代码如下:
package com.fjut.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
private static Properties properties;
private static Map<String,Object> beans;
static{
try {
//实例化对象
properties = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件所有的key
Enumeration keys = properties.keys();
//遍历枚举取出每一个key
while(keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = properties.getProperty(key);
//通过反射创建对象
Object value = Class.forName(beanPath).newInstance();
beans.put(key,value);
//
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
创建的是单例对象
结果:
若变量在方法中:可以实现变量值不变。
public void saveAccount() {
int i = 1;
accountDao.saveAccount();
System.out.println(i);
i++;
}
若变量为成员变量,则变量会改变。
结果:
private int i = 1;
public void saveAccount() {
accountDao.saveAccount();
System.out.println(i);
i++;
}
第三步:解耦。(在对应需要用到new来获取类对象的地方通过工厂中的getBean()方法来反射创建);代码如下:
//例如:servlet---调用--->service
IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");
accountService.saveAccount();
//service---调用--->dao
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount() {
accountDao.saveAccount();
}
注:工厂模式解耦。如果删除了bean.properties对应的全限定类名下的类文件,编译期错误不会出现(就是可以通过编译),但运行时会出现ClassNotFoundException(类找不到异常)。
2.ioc(控制反转)容器解决
ioc作用:降低耦合(减少类依赖和方法依赖)。若自己写代码来达到ioc的效果就采用工厂模式来解决,如上的Bean工厂。
使用spring中的ioc准备:
1.准备spring开发包
在pom.xml文件中添加依赖,让它自动下载。若做拷贝有可能出现错误。(此操作要修改maven的下载路径后才会不是很慢。要在连网状态下)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.写bean.xml
<?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">
<!--把对象的创建交给spring来管理-->
<!--id:是获取时的唯一标志;class:是反射要创建的全限定类名;-->
<bean id="accountService" class="com.fjut.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.fjut.dao.impl.AccountDaoImpl"></bean>
</beans>
3.创建对象
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据唯一标识id获取对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
as.saveAccount();
测试创建的对象是单例还是多例?
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据唯一标识id获取对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
for (int i = 0; i < 5; i++) {
System.out.println(as);
as.saveAccount();
}
结果:创建的对象是单例的。
利用BeanFactory(org.springframework.beans.factory.BeanFactory)创建对象:代码如下: --创建的是多例对象–
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService ias = (IAccountService) factory.getBean("accountService");
ias.saveAccount();
三、核心容器的两个接口引发的问题:
ApplicationContext: 单例对象下适用 (开发中经常使用此方法)
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。(只有一读取完配置文件中的配置对象就会创建对象);也就是说执行了ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);就会创建对象。
BeanFatory: 多例对象下适用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。(什么时候根据id获取对象了,什么时候才创建对象);也就是说执行了IAccountService ias = (IAccountService) factory.getBean(“accountService”);才会创建对象。
四、创建bean(可重用组件)的三种方式:
第一种方式:使用默认构造函数创建。
<bean id="accountService" class="com.fjut.service.impl.AccountServiceImpl"></bean>
第二种方式:使用普通工厂中的创建对象的方法(返回值是bean(可重用组件)对象的方法)或jar包中的对象。
<bean id="instanceFactory" class="工厂的全限定类名"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="工厂中返回值是所需bean对象的方法名"></bean>
第三种方式:使用工厂中的静态方法创建对象。
<bean id="accountService" class="静态创建对象工厂的全限定类名" factory-method="静态创建对象的方法名"></bean>
五、xml中bean的作用范围
通过bean标签的scope属性:prototype(多例创建)常用
singleton(单例创建,也是默认值)常用
request(作用于web应用的请求范围)
session(作用于web应用的会话范围)
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session。
bean对象的声明周期:singleton(单例的),读取配置文件创建容器时出生,容器(ClassPathXmlApplicationContext)消亡时消亡。
prototype(多例的),使用对象时创建(IAccountService ias = (IAccountService) factory.getBean(“accountService”);),当长时间不用被系统认为是垃圾时,垃圾回收机制执行的时候被销毁。
六、依赖注入
1.构造函数注入。
一般不用(原因:若只想构造部分变量,则其他变量也必须注入值)-----
使用标签:constructor-arg
标签中出现的位置:bean标签的内部。
标签中的属性:type:用于指定要注入构造函数中的数据类型,要对应上构造函数的的类型。
index:用于指定要注入的数据给狗子函数中指定索引位置的参数赋值(从0开始)。
name(常用):用于给构造函数中指定名称的参数赋值。
value:用于提高基本类型和String类型的数据。
ref:用于指定其他的bean类型数据,它值的技术在spring的ioc的核心容器中出现过的bean对象(也就是在xml文件中定义过的bean标签的关联对象)。
<bean id="domain对象的唯一标识" class="实体类全限定类名">
<constructor-arg name="有参构造的参数名1" value="想要注入来初始化的字符串或基本数据类型的数据"></constructor-arg>
<constructor-arg name="有参构造的参数名2" value="想要注入来初始化的字符串或基本数据类型的数据"></constructor-arg>
<constructor-arg name="有参构造的参数名3" value="ioc的核心容器内的bean对象的唯一标识"></constructor-arg>
</bean>
<!--参数3例子:日期格式转换-->
<bean id="ioc的核心容器内的bean对象的唯一标识" class="java.util.Date"></bean>
2.用set方法注入
常用。
注意事项:创建对象的类中要有get和set方法(通过快捷键生成的方法)。
get和set方法
public String getXxx1() {
return name;
}
public void setXxx1(String name) {
this.name = name;
}
public String getXxx2() {
return name;
}
public void setXxx2(String name) {
this.name = name;
}
public String getXxx3() {
return name;
}
public void setXxx3(String name) {
this.name = name;
}
<bean id="domain对象的唯一标识" class="实体类全限定类名">
<property name="xxx1" value="想要注入来初始化的字符串或基本数据类型的数据"></property>
<property name="xxx2" value="想要注入来初始化的字符串或基本数据类型的数据"></property>
<property name="xxx3" value="ioc的核心容器内的bean对象的唯一标识"></property>
</bean>
<!--参数3例子:日期格式转换-->
<bean id="ioc的核心容器内的bean对象的唯一标识" class="java.util.Date"></bean>
3.复杂类型的注入
<bean id="domain对象的唯一标识" class="实体类全限定类名">
<!--下面这些name都是set方法注入的,要有对应的set方法才能注入-->
<property name="xxxArray">
<array>
<value>想要注入来初始化的字符串或基本数据类型的数据1</value>
<value>想要注入来初始化的字符串或基本数据类型的数据2</value>
<value>想要注入来初始化的字符串或基本数据类型的数据3</value>
</array>
</property>
<property name="xxxMap">
<map>
<entry key="testA" value="想要注入来初始化的字符串或基本数据类型的数据1"></entry>
<entry key="testB" value="想要注入来初始化的字符串或基本数据类型的数据2"></entry>
</map>
</property>
<property name="xxxProps">
<props>
<prop key="testC">想要注入来初始化的字符串或基本数据类型的数据1</prop>
<prop key="testD">想要注入来初始化的字符串或基本数据类型的数据2</prop>
</props>
</property>
</bean>
总结
1.了解spring的ioc可以解决依赖注入的问题:即需要在一个类中调用另一个类时需要进行new的方式,出现的类依赖问题。(解决方法:使用ioc方式解决,见本文章中”2.ioc(控制反转)容器解决“部分)。
2.如何搭建出spring的基于xml的ioc的开发环境。(”2.ioc(控制反转)容器解决“中的”使用spring中的ioc准备“部分)
3.如何实现类之间的依赖注入关系。(常用set方法注入)
springIoc——控制反转
控制:通过spring核心容器去控制对象的创建
反转:程序不主动去创建对象,而是被动的去spring核心容器中接收对象
依赖注入:
通过set方法注入核心容器中的对象
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
@Autowired与@Resource异同:@Autowired先byType注入,再通过byName注入;
@Resource先byName注入,再通过byType注入。
singleton与prototype:异同:singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
XML与注解比较:
XML可以适用任何场景 ,结构清晰,维护方便
注解不是自己提供的类使用不了,开发简单方便
纵向开发和横向编程的对应的模块图: