简介

  1. Spring是分层的 Java SE/EE应用** full-stack(全栈式) **轻量级开源框架。 <br />全栈是说整合了其它主流框架,并且支持三层开发规范(spring mvc + spring + spring jdbc template)

两大核心:以 IOC(Inverse Of Control:控制反转)和 AOP(Aspect Oriented Programming:面向 切面编程)为内核。
控制反转指容器来创建对象。AOP指面向切面编程,在不修改源码的情况下对方法进行增强,底层实现是动态代理。

Spring的优点

1)方便解耦,简化开发 Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理 什么是耦合度?对象之间的关系,通常说当一个模块(对象)更改时也需要更改其他模块(对象),这就是 耦合,耦合度过高会使代码的维护成本增加。要尽量解耦
2)AOP编程的支持 Spring提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
3)声明式事务的支持 通过配置完成事务的管理,无需手动编程
4)方便测试,降低JavaEE API的使用 Spring对Junit4支持,可以使用注解测试
5)方便集成各种优秀框架 不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持

其实最重要的就是第一条。解耦是最核心的。一条解耦的基本思路就是降低编译期依赖,尽可能的消除new关键字。很常见的解决手段就是使用配置文件+反射的组合来解耦。

IOC简介

把对象的创建和销毁交给容器

自己写个最最最简单的IOC容器

第一步 搞个xml存我们要托管的bean

  1. <beans>
  2. <bean id="userDao" class="com.ning.dao.impl.UserDaoImpl"></bean>
  3. </beans>

第二步 做个类把这些(个)bean装载到Map里。

  1. public class BeanFactory {
  2. private static Map<String, Object> iocMap = new HashMap<>();
  3. static {
  4. try {
  5. final InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
  6. final Document doc = new SAXReader().read(resourceAsStream);
  7. String xpath = "//bean";
  8. final List<Element> list = doc.selectNodes(xpath);
  9. for (Element element : list) {
  10. final String id = element.attributeValue("id");
  11. final String aClass = element.attributeValue("class");
  12. final Object o = Class.forName(aClass).getDeclaredConstructor().newInstance();
  13. iocMap.put(id, o);
  14. }
  15. } catch (DocumentException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. public static Object getBean(String beanId) {
  20. return iocMap.get(beanId);
  21. }
  22. }

Spring快速入门

先引入pom

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.1.5.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>junit</groupId>
  9. <artifactId>junit</artifactId>
  10. <version>4.12</version>
  11. </dependency>
  12. </dependencies>

再写一个配置文件,约定俗成地,大家都叫它applicationContext.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="userDao" class="com.ning.dao.impl.UserDaoImpl"></bean>
  7. </beans>

然后写个测试类充当Service层

  1. @Test
  2. public void test2() {
  3. final ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  4. final IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
  5. userDao.save();
  6. }

可以看出,和我们写的玩具一模一样

Spring相关API

image.png
最重要的是BeanFactory和ApplicationContext

BeanFactory Vs ApplicationContext

BeanFactory是 IOC 容器的核心接口,它定义了IOC的基本功能。
特点:在第一次调用getBean()方法时,创建指定对象的实例

  1. BeanFactory beanFactory
  2. = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
  1. applicationContext代表应用上下文对象,可以获得springIOC容器的Bean对象。 <br /> 特点:在spring容器启动时,加载并创建所有对象的实例
  1. ApplicationContext app =
  2. new ClassPathXmlApplicationContext("applicationContext.xml");

常用实现类

  1. 1. ClassPathXmlApplicationContext
  2. 它是从类的根路径下加载配置文件 推荐使用这种。
  3. 2. FileSystemXmlApplicationContext
  4. 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
  5. 3. AnnotationConfigApplicationContext
  6. 当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
  1. 常用方法
  1. 1. Object getBean(String name);
  2. 根据Beanid从容器中获得Bean实例,返回是Object,需要强转。
  3. 2. <T> T getBean(Class<T> requiredType);
  4. 根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
  5. 3. <T> T getBean(String name,Class<T> requiredType);
  6. 根据Beanid和类型获得Bean实例,解决容器中相同类型Bean有多个情况。

配置文件详解

其实主要就是说Bean

  1. <bean id="" class="" scope="" init-method="" destroy-method=""></bean>
  2. * 用于配置对象交由Spring来创建。
  3. * 基本属性:
  4. idBean实例在Spring容器中的唯一标识
  5. classBean的全限定名
  6. * 默认情况下它调用的是类中的 无参构造函数,如果没有无参构造函数则不能创建成功。
  7. init-method:指定类中的初始化方法名称
  8. destroy-method:指定类中销毁方法名称

scope
默认是单例的。其它值如下所示:
image.png

  1. 1. scope的取值为singleton
  2. Bean的实例化个数:1
  3. Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
  4. Bean的生命周期:
  5. 对象创建:当应用加载,创建容器时,对象就被创建了
  6. 对象运行:只要容器在,对象一直活着
  7. 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
  8. 2. scope的取值为prototype
  9. Bean的实例化个数:多个
  10. Bean的实例化时机:当调用getBean()方法时实例化Bean
  11. Bean的生命周期:
  12. 对象创建:当使用对象时,创建新的对象实例
  13. 对象运行:只要对象在使用中,就一直活着
  14. 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

Bean实例化三种方式

无参构造方法实例化
工厂静态方法实例化
工厂普通方法实例化

工厂静态方法实例化

应用场景
依赖的jar包中有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得。

  1. public class StaticFactoryBean {
  2. public static UserDao createUserDao(){
  3. return new UserDaoImpl();
  4. }
  5. }
  1. <bean id="userDao" class="com.lagou.factory.StaticFactoryBean"
  2. factory-method="createUserDao" />

工厂普通方法实例化

应用场景
依赖的jar包中有个A类,A类中有个普通方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1 方法,直接从IOC容器获得。

  1. public class DynamicFactoryBean {
  2. public UserDao createUserDao(){
  3. return new UserDaoImpl();
  4. }
  5. }
  1. <bean id="dynamicFactoryBean" class="com.lagou.factory.DynamicFactoryBean"/>
  2. <bean id="userDao" factory-bean="dynamicFactoryBean" factorymethod="createUserDao"/>

依赖注入

它是 Spring 框架核心 IOC 的具体实现。 在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。
例如:业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用Spring 之后,就让 Spring 来维护了。
简单的说,就是通 过框架把持久层对象传入业务层,而不用我们自己去获取。

构造函数依赖注入

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao;
  3. public UserServiceImpl(UserDao userDao) {
  4. this.userDao = userDao;
  5. }
  6. @Override
  7. public void save() {
  8. userDao.save();
  9. }
  10. }
  1. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
  2. <bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
  3. <!--<constructor-arg index="0" type="com.lagou.dao.UserDao" ref="userDao"/>-->
  4. <constructor-arg name="userDao" ref="userDao"/>
  5. </bean>

Setter依赖注入

  1. public class UserServiceImpl implements UserService {
  2. private UserDao userDao;
  3. public void setUserDao(UserDao userDao) {
  4. this.userDao = userDao;
  5. }
  6. @Override
  7. public void save() {
  8. userDao.save();
  9. }
  10. }
  1. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
  2. <bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
  3. <property name="userDao" ref="userDao"/>
  4. </bean>
  1. 如果想注入数字/字符串,把ref变成value

P命名空间注入

本质也是set方法注入

  1. xmlns:p="http://www.springframework.org/schema/p"

先得引用p命名空间

  1. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
  2. <bean id="userService"
  3. class="com.lagou.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

普通数据类型注入

  1. public class User {
  2. private String username;
  3. private String age;
  4. public void setUsername(String username) {
  5. this.username = username;
  6. }
  7. public void setAge(String age) {
  8. this.age = age;
  9. }
  10. }
  1. <bean id="user" class="com.lagou.domain.User">
  2. <property name="username" value="jack"/>
  3. <property name="age" value="18"/>
  4. </bean>

集合依赖注入

List

  1. public class UserDaoImpl implements UserDao {
  2. private List<Object> list;
  3. public void save() {
  4. System.out.println(list);
  5. System.out.println("保存成功了...");
  6. }
  7. }
  1. <bean id="user" class="com.lagou.domain.User">
  2. <property name="username" value="jack"/>
  3. <property name="age" value="18"/>
  4. </bean>
  5. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
  6. <property name="list">
  7. <list>
  8. <value>aaa</value>
  9. <ref bean="user"></ref>
  10. </list>
  11. </property>
  12. </bean>

Set

  1. public class UserDaoImpl implements UserDao {
  2. private Set<Object> set;
  3. public void setSet(Set<Object> set) {
  4. this.set = set;
  5. }
  6. public void save() {
  7. System.out.println(set);
  8. System.out.println("保存成功了...");
  9. }
  10. }
  1. <bean id="user" class="com.lagou.domain.User">
  2. <property name="username" value="jack"/>
  3. <property name="age" value="18"/>
  4. </bean>
  5. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
  6. <property name="set">
  7. <set>
  8. <value>bbb</value>
  9. <ref bean="user"></ref>
  10. </set>
  11. </property>
  12. </bean>

Array

  1. public class UserDaoImpl implements UserDao {
  2. private Object[] array;
  3. public void setArray(Object[] array) {
  4. this.array = array;
  5. }
  6. public void save() {
  7. System.out.println(Arrays.toString(array));
  8. System.out.println("保存成功了...");
  9. }
  10. }
  1. <bean id="user" class="com.lagou.domain.User">
  2. <property name="username" value="jack"/>
  3. <property name="age" value="18"/>
  4. </bean>
  5. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
  6. <property name="array">
  7. <array>
  8. <value>ccc</value>
  9. <ref bean="user"></ref>
  10. </array>
  11. </property>
  12. </bean>

Map

  1. public class UserDaoImpl implements UserDao {
  2. private Map<String, Object> map;
  3. public void setMap(Map<String, Object> map) {
  4. this.map = map;
  5. }
  6. public void save() {
  7. System.out.println(map);
  8. System.out.println("保存成功了...");
  9. }
  10. }
  1. <bean id="user" class="com.lagou.domain.User">
  2. <property name="username" value="jack"/>
  3. <property name="age" value="18"/>
  4. </bean>
  5. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
  6. <property name="map">
  7. <map>
  8. <entry key="k1" value="ddd"/>
  9. <entry key="k2" value-ref="user"></entry>
  10. </map>
  11. </property>
  12. </bean>

Properties

  1. public class UserDaoImpl implements UserDao {
  2. private Properties properties;
  3. public void setProperties(Properties properties) {
  4. this.properties = properties;
  5. }
  6. public void save() {
  7. System.out.println(properties);
  8. System.out.println("保存成功了...");
  9. }
  10. }
  1. <bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
  2. <property name="properties">
  3. <props>
  4. <prop key="k1">v1</prop>
  5. <prop key="k2">v2</prop>
  6. <prop key="k3">v3</prop>
  7. </props>
  8. </property>
  9. </bean>

配置文件模块化

可以拆解配置文件。拆分要么按层,要么按模块。
同层就这样

  1. ApplicationContext act =
  2. new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");

主从就这样(更常用)

  1. <import resource="applicationContext-xxx.xml"/>

注意:
同一个xml中不能出现相同id的bean,如果出现会报错
多个xml如果出现相同id的bean,不会报错,但是后加载的会覆盖前加载的bean

使得spring能引入jdbc.properties

观察这个文件即可

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd"
  10. >
  11. <context:property-placeholder location="classpath:jdbc.properties"/>
  12. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  13. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  14. <property name="url" value="${jdbc.url}"/>
  15. <property name="username" value="${jdbc.username}"/>
  16. <property name="password" value="${jdbc.password}"/>
  17. </bean>
  18. <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
  19. <constructor-arg name="ds" ref="dataSource"/>
  20. </bean>
  21. <bean id="accountDao" class="com.ning.dao.impl.AccountDaoImpl">
  22. <property name="queryRunner" ref="queryRunner"/>
  23. </bean>
  24. <bean id="accountService" class="com.ning.service.impl.AccountServiceImpl">
  25. <property name="accountDao" ref="accountDao"/>
  26. </bean>
  27. </beans>

注解开发

image.png
Java11以后移除了javax扩展,要是非得用就需要

  1. <dependency>
  2. <groupId>javax.annotation</groupId>
  3. <artifactId>javax.annotation-api</artifactId>
  4. <version>1.3.2</version>
  5. </dependency>
  1. 如果使用注解,需要指定一个路径让spring去扫描
  1. <!--注解的组件扫描-->
  2. <context:component-scan base-package="com.lagou"></context:component-scan>

新注解

使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:

  1. * 非自定义的Bean的配置:<bean>
  2. * 加载properties文件的配置:<context:property-placeholder>
  3. * 组件扫描的配置:<context:component-scan>
  4. * 引入其他文件:<import>
  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12718322/1634893271356-fa119a26-7619-46e0-b20f-45ceb2c6515c.png#clientId=u45f5b138-5fcf-4&from=paste&height=249&id=u664a3313&margin=%5Bobject%20Object%5D&name=image.png&originHeight=497&originWidth=1486&originalType=binary&ratio=1&size=139996&status=done&style=none&taskId=u3f8821ad-57fd-4a3a-b5d8-7bc242705f7&width=743)

示例

  1. @PropertySource(value = "classpath:jdbc.properties")
  2. public class DataSourceConfig {
  3. @Value("${jdbc.driverClassName}")
  4. private String driverName;
  5. @Value("${jdbc.url}")
  6. private String url;
  7. @Value("${jdbc.username}")
  8. private String username;
  9. @Value("${jdbc.password}")
  10. private String password;
  11. @Bean(value = "dataSource")
  12. public DataSource getDataSource() {
  13. DruidDataSource dataSource = new DruidDataSource();
  14. dataSource.setDriverClassName(driverName);
  15. dataSource.setUrl(url);
  16. dataSource.setUsername(username);
  17. dataSource.setPassword(password);
  18. return dataSource;
  19. }
  20. }
  1. @Configuration
  2. @ComponentScan(value = "com.ning")
  3. @Import(value = DataSourceConfig.class)
  4. public class SpringConfig {
  5. @Bean(value = "queryRunner")
  6. public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
  7. return new QueryRunner(dataSource);
  8. }
  9. }

Spring整合Junit

在普通的测试类中,需要开发者手动加载配置文件并创建Spring容器,然后通过Spring相关API获得 Bean实例;如果不这么做,那么无法从容器中获得对象。

  1. ApplicationContext applicationContext =
  2. new ClassPathXmlApplicationContext("applicationContext.xml");
  3. AccountService accountService =
  4. applicationContext.getBean(AccountService.class);

我们可以让SpringJunit负责创建Spring容器来简化这个操作,开发者可以直接在测试类注入Bean实 例;但是需要将配置文件的名称告诉它。

具体步骤

  1. 导入spring集成Junit的坐标
  2. 使用@Runwith注解替换原来的运行器
  3. 使用@ContextConfiguration指定配置文件或配置类
  4. 使用@Autowired注入需要测试的对象
  5. 创建测试方法进行测试
  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-test</artifactId>
  4. <version>5.1.5.RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>junit</groupId>
  8. <artifactId>junit</artifactId>
  9. <version>4.12</version>
  10. <scope>test</scope>
  11. </dependency>
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. public class SpringJunitTest {
  3. }
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. //@ContextConfiguration(value = {"classpath:applicationContext.xml"}) 加载spring
  3. 核心配置文件
  4. @ContextConfiguration(classes = {SpringConfig.class}) // 加载spring核心配置类
  5. public class SpringJunitTest {
  6. }
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = {SpringConfig.class})
  3. public class SpringJunitTest {
  4. @Autowired
  5. private AccountService accountService;
  6. }
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = {SpringConfig.class})
  3. public class SpringJunitTest {
  4. @Autowired
  5. private AccountService accountService;
  6. //测试查询
  7. @Test
  8. public void testFindById() {
  9. Account account = accountService.findById(3);
  10. System.out.println(account);
  11. }
  12. }