2021-08-01 02:49:16
Spring 是一个为简化企业级应用开发的开源框架,Spring 是一个 IOC(DI) 依赖注入和 AOP 面向切面容器框架。
具体描述 Spring:
轻量级:Spring 是非侵入性的,基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API
容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期。
框架:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象
一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了展现层的 SpringMVC 和持久层的 Spring JDBC)
注意:开源,(Open Source)全称为开放源代码。开源就是要用户利用源代码在其基础上修改和学习的,但开源系统同样也有版权,同样也受到法律保护,开源不等于免费。
Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa 等等。但他们的基础都是 Spring 的 ioc 和 aop。ioc 提供了依赖注入的容器。aop ,解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。
Spring MVC 是基于 Servlet 的一个 MVC 框架 主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种 XML、 JavaConfig、hin 处理起来比较繁琐。于是为了简化开发者的使用,从而创造性地推出了 Spring boot,约定优于配置,简化了 spring 的配置流程。
说得更简便一些:Spring 最初利用“工厂模式”(DI)和“代理模式”(AOP)解耦应用组件。大家觉得挺好用,于是按照这种模式搞了一个 MVC 框架(一些用 Spring 解耦的组件),用开发 web 应用( SpringMVC )。然后有发现每次开发都写很多样板代码,为了简化工作流程,于是开发出了一些“懒人整合包”(starter),这套就是 Spring Boot。
IOC(Inversion of Control) 控制反转
首先想说说 IOC(Inversion of Control)控制反转。这是 spring 的核心,贯穿始终。所谓 IOC,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。程序开发中,在一个对象中如果要使用另外的对象,就必须得到它常见的两种方式是自己 new 一个该对象,或者从 JNDI(Java 命名与目录接口(Java Naming and Directory Interface)中查询一个,使用完之后还要将对象销毁(比如 Connection 等),对象始终会和其他的接口或类藕合起来。
Spring 所倡导的开发方式是这样,所有的类都会在 spring 容器中登记,告诉 spring 该类是做什么用,需要依赖其他什么类,然后 spring 会在系统运行到适当的时候,把需要的类对象主动注入到标记的位置,同时也把该类交给其他需要的地方。所有的类的创建、销毁都由 spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 spring 控制,所以这叫控制反转。
DI(Dependency Injection) 依赖注入
IOC 的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过 DI(Dependency Injection,依赖注入)来实现的。比如对象A 需要操作数据库,以前我们总是要在 A 中自己编写代码来获得一个 Connection 对象,有了 spring 我们就只需要告诉 spring,A 中需要一个 Connection,至于这个 Connection 怎么构造,何时构造,A 不需要知道。在系统运行时,spring 会在适当的时候制造一个 Connection,然后像打针一样,注射到 A 当中,这样就完成了对各个对象之间关系的控制。A 需要依赖 Connection 才能正常运行,而这个 Connection 是由 spring 注入到 A 中的,依赖注入的名字就这么来的。那么 DI 是如何实现的呢? Java 1.3 之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring 就是通过反射来实现注入的。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency>
maven 会自动帮我们解决依赖之间的关联问题,如下为具体依赖关系图:
在项目中创建实体 User
public class User { private Integer id; private String name; }
配置 spring 容器的配置文件,在 resources 目录下点击 new -> XML Configuration File ->Spring Config 创建 spring-context.xml,并在其中配置一个 bean(被 spring 管理的对象)。
其中 bean 的 id 表示该对象在容器中的唯一标识。class 表明该对象所属的类型。property 相当于对象的属性设置,spring 通过调研对象的 set 方法完成属性设置。
<?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 id="user" class="cn.hxzy.User"> <property name="id" value="1"/> <property name="name" value="张三"/> </bean> </beans>
在主函数或测试类中获取 spring 的容器,并从容器中获取 bean 对象。
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml"); // 从 IOC 容器中获取 bean 的实例 User bean = ctx.getBean(User.class); System.out.println(bean);
总结:如上操作即使用 spring 的语法在 spring 容器中创建一个被管理的对象。
注意:ApplicationContext 是 spring 上下文容器,它是一个接口,常用的实现类有两个:
<bean id="user2" class="cn.hxzy.User"> <constructor-arg name="name" value="李四"/> <constructor-arg name="id" value="2"/> </bean>
注意:在 xml 中包含特殊字符,必须使用<![CDATA[*****]]> 包裹,且不能在写在 constructor-arg 节点的属性上。
<constructor-arg name="name" > <value><![CDATA[<李四]]></value> </constructor-arg>
在 Spring 中可以通过一组内置的 xml 标签(例如:<list>,<set> 或 <map>)来配置集合属性。配置 java.util.List 类型的属性,需要指定 <list> 标签,在标签里包含一些元素。这些标签可以通过 <value> 指定简单的常量值,通过 <ref> 指定对其他 Bean 的引用。通过<bean> 指定内置 Bean 定义。通过 <null/> 指定空元素。甚至可以内嵌其他集合。数组的定义和 List 一样,都使用 <list> 配置 java.util.Set 需要使用 <set> 标签,定义元素的方法与 List 一样。
在用户实体中加入 Car 集合
public class User { ...... private List<Car> cars; }
汽车实体
public class Car { private String company; private Integer maxSpeed; private Float price; }
级联时使用 list 表明该处级联类型为 list 集合,里面使用 ref 应用其他 bean 对象,级联 set 与 map 也与此类似。
<bean id="user1" class="cn.hxzy.User"> <property name="id" value="1"/> <property name="name" value="张三"/> <property name="cars"> <list> <ref bean="car1"/> </list> </property> </bean> <bean id="car1" class="cn.hxzy.Car"> <property name="company" value="大众"/> <property name="maxSpeed" value="280"/> <property name="price" value="30"/> </bean>
注意:设置为空使用下面的语法
<property name="company"><null/></property>
为了节省内存,spring 中的对象默认使用单例模式。即无论从容器中获取多少次对象,得到的都是同一个。但在实际开发中有时会用到多例,即希望每次从容器中获取对象都是新的对象。spring 提供 scop 属性来指定对象获取是单例还是多例。多例的 bean 又叫原型的。
prototype:原型的。每次调用 getBean 方法都会返回一个新的 bean。且在第一次调用 getBean 方法时才创建实例。
singleton:单例的。每次调用 getBean 方法都会返回同一个 bean。且在 IOC 容器初始化时即创建 bean 的实例。默认值
<bean id="user" class="cn.hxzy.User" scope="prototype"> <property name="id" value="1"/> <property name="name" value="张三"></property> </bean>
1.数据库连接 Connection 对象不能设计成单例,否则会出现多个线程使用同一个连接完成数据库的不同操作,也许上一个线程还没有查询完数据就会被下一个线程拿去修改数据库记录,非常容易出现错误。
2. service 层的对象就不需要设计成原型的,因为 service 层没有过多的参数,不容易导致线程安全问题,创建过多的对象反而耗用大量内存意义不大。
在项目开发中,有时需要使用工厂方法创建 bean,spring 支持常用的工厂方法如静态工厂方法创建和实例工厂方法创建 bean。
1.静态工厂方法创建 bean
public class CarFactory { static Map<String, Car> cars = new HashMap<>(); static { cars.put("car1", new Car("宝马", 120, 122222.0)); cars.put("car2", new Car("奥迪", 120, 122222.0)); } public static Car getCar(String name) { return cars.get(name); } }
获取 bean
<bean id="car1" class="cn.hxzy.CarFactory" factory-method="getCar"> <constructor-arg value="car2"/> </bean>
2.实例工厂方法获取 bean
public class CarFactory { Map<String, Car> cars = new HashMap<>(); public CarFactory() { cars.put("car1", new Car("宝马", 120, 122222.0)); cars.put("car2", new Car("奥迪", 120, 122222.0)); } public Car getCar(String name) { return cars.get(name); } }
获取bean
<bean id="carFactory" class="cn.hxzy.CarFactory"></bean> <bean id="car1" factory-bean="carFactory" factory-method="getCar"> <constructor-arg value="car1"></constructor-arg> </bean>
使用 xml 方式配置 bean 的依赖关系复杂且麻烦,为了解决对象在容器注入麻烦的问题,spring 推出了包扫描与声明类注解配合使用的方式,对于开发人员编写的类只需要在包扫描范围内,使用指定的声明类注解即可将对象加入 spring 容器中。
sping 使用 context:component-scan 将指定包下面的带有声明 bean 的注解的类加入到 spring 容器管理。
<context:component-scan base-package="cn.hxzy"/>
常用的声明类注解有如下几个,它们的功能和作用在 spring 中完全一模一样,都是将自己交给 spring 管理。唯一的区别就是它们所用的业务环节不同。
注入类注解:在 spring 容器管理的 bean 对象需要依赖其他 bean 对象时,就可以在对应对象属性或其 set 方法上使用注入类注解完成依赖注入。spring 容器中常用 @Autowired 和 @Resource 两个注解完成依赖注入。@Autowired 属于 Spring 的注解,@Resource 不属于 Spring 的注解,是 JDK1.6 支持的注解。
它们的区别是:
@Autowired 默认按类型装配, 依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false @Autowired(required = false) 也可以使用名称装配,配合 @Qualifier 注解
public class TestServiceImpl { @Autowired @Qualifier("userDao") private UserDao userDao; }
@Resource 默认按名称进行装配,通过 name 属性进行指定,name 为属性的名字时可以省略。
案例:
dao 层模拟从数据库查出数据,首先需要使用声明类注解将自己注册到容器。
@Repository public class PetDao { public List<Pet> getAll() { List<Pet> pets = new ArrayList<>(); pets.add(new Pet("花花")); return pets; } }
service 层除了将自己注册到容器,还依赖 dao 层的对象,使用注入类注解将容器内的对象注入到对应位置。
@Service public class PetService { @Autowired private PetDao dao; public List<Pet> getAll() { return dao.getAll(); } }
view 层与服务层原理相同。
@Controller public class PetView { @Resource private PetService petService; public void getAll() { List<Pet> list = petService.getAll(); System.out.println(list); } }
main 从容器中获取 view 层对象即可调用它的方法,得到 dao 层返回的结果。
public class MainTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); PetView bean = context.getBean(PetView.class); bean.getAll(); } }
总结:将除了实体以外的其他对象加入 spring 容器管理,并通过彼此的依赖自动注入。使程序开发更加简便灵活,在内存耗用,对象管理方面更加优秀。
在实际开发中,经常在 spring 的 xml 使用配置文件中的属性,spring 加载 properties 文件中一般使用 context:property-placeholder,加载完成后使用 ${} 获取配置文件中对应属性,花括号内是对应属性的键。通常配置文件在 resource 目录下。如 db.properties 文件,使用 context:property-placeholder 加载 properties 文件,${name} 获取配置文件中 name 的属性值。
内容如下:
<context:property-placeholder location="classpath:db.properties" file-encoding="utf-8"/> <bean id="car1"> <property name="company" value="${name}"/> <property name="maxSpeed" value="280"/> <property name="price" value="30"></property> </bean>
如果该属性需要的 spring 包扫描的类中使用可以使用 @Value("${name}") 获取配置文件内的属性。
@Service public class PropertyTest { @Value("${name}") private String id; public String getId() { return id; } }
在 spring 中加载配置文件也可以使用注解的方式 @PropertySource("classpath:db.properties") 它与 context:property-placeholder 是等效的。
spring 不仅仅可以获取配置文件里面的内容它还可以做一些简单的运算,如下案例:可以注入其他对象的属性、普通字符串、操作系统的信息、随机数、某个文件的内容等。如下代码在输出 Resource 时用到 commons-io 工具类。
依赖如下:
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
案例:
import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; @Service @PropertySource("classpath:db.properties") public class PropertyTest { @Value("${id}") private int id; //注入配置文件的属性 @Value("I love you") private String normal; //字符串原样注入 @Value("#{systemProperties['os.name']}") private String osName; //注入操作系统信息 @Value("#{pet.name}") private String otherName; //注入容器内其它 bean 的属性 @Value("#{T (java.lang.Math).random()*100}") private Integer random; //注入 100 以下的随机数 @Value("classpath:1.txt") private Resource resource; //注入classpath下文本文件里面内容 @Value("http://58.42.239.163:8888/") private Resource resourceUrl; //注入网站地址的响应内容 public String toString1() throws Exception { return "[normal=" + normal + ",id="+id+", osName=" + osName + ", otherName=" + otherName + ", random=" + random + ", resource=" + IOUtils.toString(resource.getInputStream()) + ", resourceUrl=" + IOUtils.toString(resourceUrl.getInputStream(), "UTF-8") + "]"; } }
注意:
1.在 spring 通过注解 @Value 中获取 properties 文件里面的值使用 $ 前缀。
2.获取系统信息、容器类其他对象的信息、方法调用后的结果等使用 # 前缀。
3.获取文件或网页内容不使用前缀,且使用 Resource 接收。
User user1 = .....; User user2 = .....; BeanUtils.copyProperties(user1,user2);
当在完成两个对象拷贝是需要负略某些属性可以使用对应的重载方法 BeanUtils.copyProperties(源头,目标,"忽略的属性"); 但在实际开发中拷贝非空属性是比较常用的。创建方法将对象的非空属性列出后传入 BeanUtils 即可拷贝非空属性。
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.beans.PropertyDescriptor; import java.util.HashSet; import java.util.Set; public class BeanUtil { private static String[] getNullPropertyNames(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set<String> emptyNames = new HashSet<>(); for (java.beans.PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue == null) emptyNames.add(pd.getName()); } String[] result = new String[emptyNames.size()]; return emptyNames.toArray(result); } public static void copyPropertiesIgnoreNull(Object source, Object target) { BeanUtils.copyProperties(source, target, getNullPropertyNames(source)); } }
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.12.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
引入依赖后在测试源码目录下创建如下类并添加相应的注解使其能
import cn.hxzy.spring.view.PetView; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(value = "classpath:spring-context.xml") public class AppTest { @Autowired private PetView petView; @Test public void sendSimpleEmail() throws InterruptedException { petView.show(); } }
spring 能够与常见框架整合,如 mybatis,hibernate,redis 等。这使得大部分框架中的对象都可以从 spring 中获取到。而且对象被 spring 管理后可以很方便的使用 spring 的依赖注入和面向切面。
使用 spring 与 mybatis 整合需要如下几步:
1.添加依赖:由于数据库连接相关对象都交给 spring 管理,所以依赖中多出了 spring-jdbc 依赖。同时 mybatis 为了和 spring 整合开发了 mybatis-spring 整合包。
<!--spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2.配置数据库连接信息,文件名通常为 db.properties
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///mydb jdbc.username=root jdbc.password=123456
3.spring 核心配置文件,由于数据库连接等对象都被 spring 管理,所以配置时不再需要 mybatis 的核心配置文件,如果在开发中确实需要用到 mybatis 的核心配置文件也可以配置 sqlsessionfactory 的 configlocation。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="cn.hxzy"/> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置数据库数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 创建 session 工厂--> <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!--告诉spring mybatis接口的位置--> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.hxzy.mapper"/> </bean> <!-- 配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 使用注解的方式完成事务控制--> <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/> </beans>
注意:
1.mapperScanner 配置主要配置 mybatis 的接口位置。
2.注解式事务是通过 @Transactional 注解即可完成该方法所有数据库操作要么一起成功,要么一起失败的业务逻辑。使用事务时数据库引擎必须使用 innoDB。
@Transactional public void update(){ User user1 = new User(); user1.setId(2); user1.setName("O"); userMapper.update(user1); System.out.println(1 / 0); User user2 = new User(); user2.setId(3); user2.setName("Y"); userMapper.update(user2); }
3.spring5 版本不再支持原来的 mybatis 章节的 log4j 版本,为了显示日志。通常我们使用 logback。导入如下依赖。
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
配置文件 logback.xml 放在 classpath 下。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!--控制台--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p --- [%t] %-40.40logger{39} : %m%n</pattern> </encoder> </appender> <!--根logger--> <root level="DEBUG" additivity="false"> <appender-ref ref="console"/> </root> </configuration>