spring知识整理

200 阅读13分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Spring

1. Spring简介

1.1 Spring是什么

Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。

提供了展现层 SpringMVC 和持久层 Spring JDBCTemplate 以及业务层事务管理等众多的企业级应用技术 ,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

1.2 Spring的优势

  • 方便解耦,简化开发

    通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度耦合。 用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  • AOP 编程的支持

    通过 Spring的 AOP 功能,方便进行面向切面编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松实现。

  • 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量。

  • 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  • 集成各种优秀框架

    Spring秀框架(Struts、Hibernate、Hessian、Quartz等)的支持。

  • 降低avaEE API 的使用难度

    Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。

2. Spring快速入门

2.1 Spring程序开发步骤

2.1.1 导入 Spring 开发的基本包坐标

<properties>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标,context依赖core、beans、expression-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>

2.1.2 编写 Dao 接口和实现类

public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save method running....");
}
}

2.1.3 创建 Spring 核心配置文件

在类路径下(resources)创建applicationContext.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 
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

2.1.4 在 Spring 配置文件中配置 UserDaoImpl

<?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="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
</beans>

2.1.5 使用 Spring 的 API 获得 Bean 实例

@Test
public void test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
}

3. Spring配置文件

3.1 Bean标签基本配置

用于配置对象交由Spring 来创建。

默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。

基本属性:

  • id:Bean实例在Spring容器中的唯一标识
  • class:Bean的全限定名

3.2Bean标签的配置范围

scope:指对象的配置范围

取值范围说明
singleton默认的
prototype多例的
requestWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
sessionWEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
global sessionWEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当 于 session
  • 当scope的取值为singleton时

    Bean的实例化个数:1个

    Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例

  • Bean的生命周期:

    • 对象创建:当应用加载,创建容器时,对象就被创建了
  • 对象运行:只要容器在,对象一直活着
  • 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
  • 当scope的取值为prototype时 Bean的实例化个数:多个 Bean的实例化时机:当调用getBean()方法时实例化Bean

    • 对象创建:当使用对象时,创建新的对象实例
    • 对象运行:只要对象在使用中,就一直活着
    • 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了

3.3 Bean生命周期配置

  • init-method:指定类中的初始化方法名称
  • destroy-method:指定类中销毁的方法名称
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="" destroy-method=""></bean>

3.4 Bean实例化的三种方式

  • 无参构造方法实例化

    它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败

        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="destory"></bean>
    
  • 工厂静态方法实例化

    public class DynamicFactoryBean {
      public static UserDao createUserDao(){
        return new UserDaoImpl();
      }
    }
    
        <bean id="userDao" class="com.itheima.factory.StaticFactory" factory-method="getUserDao"></bean>
    
  • 工厂实例方法实例化

    <bean id="factory" class="com.itheima.factory.DynamicFactory"></bean>
    <bean id="getUser" factory-bean="factory" factory-method="getUserDao"></bean>

3.5 Bean的依赖注入入门

  • 创建 UserService,UserService 内部在调用 UserDao的save() 方法
public class UserServiceImpl implements UserService {
@Override
public void save() {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  UserDao userDao = (UserDao) applicationContext.getBean("userDao");
  userDao.save();
  }
}
  • 将 UserServiceImpl 的创建权交给 Spring

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
    
  • 从 Spring 容器中获得 UserService 进行操作

    ApplicationContext applicationContext = new 
    ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) applicationContext.getBean("userService");
    userService.save();
    

3.6 Bean的依赖注入分析

目前UserService实例和UserDao实例都存在与Spring容器中,当前的做法是在容器外部获得UserService 实例和UserDao实例,然后在程序中进行结合。

因为UserService和UserDao都在Spring容器中,而最终程序直接使用的是UserService,所以可以在 Spring容器中,将UserDao设置到UserService内部。

3.7 Bean的依赖注入概念

  • 依赖注入(Dependency Injection):

    它是 Spring 框架核心 IOC 的具体实现。

  • 在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。 IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。

  • 那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

3.7.1 Bean依赖注入方式(setting)—>使用较多

在UserServiceImpl中添加setUserDao方法

public class UserServiceImpl implements UserService {
  private UserDao userDao;
  public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
  }
    @Override
  public void save() {
    userDao.save();
  }
}
  • xml配置方式一:
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
  <property name="userDao" ref="userDao"/>
</bean>
  • xml配置方式二:

    P命名空间注入

    本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中。

    如下: 首先,需要引入P命名空间:

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

    其次,修改配置方式

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
    

3.7.2 Bean的依赖注入方式(构造方法)

创建有参构造和无参构造

public class UserServiceImpl implements UserService {
@Override
  public void save() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    userDao.save();
  }
}

xml配置

构造方法注入

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
  <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean> 

3.8 Bean的依赖注入的数据类型

上面的操作,都是注入的引用Bean,除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。

注入数据的三种数据类型

  • 普通数据类型
  • 引用数据类型
  • 集合数据类型

3.8.1 普通数据类注入

public class UserDaoImpl implements UserDao {
    private String company;
    private int age;
    public void setCompany(String company) {
      this.company = company;
    }
    public void setAge(int age) {
      this.age = age;
    }
    public void save() {
        System.out.println(company+"==="+age);
        System.out.println("UserDao save method running....");
    }
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
  <property name="company" value="传智播客"></property>
  <property name="age" value="15"></property>
</bean>

3.8.2 集合数据类型(List)的注入

public class UserDaoImpl implements UserDao {
    private List<String> strList;
    public void setStrList(List<String> strList) {
      this.strList = strList;
    }
    public void save() {
        System.out.println(strList);
        System.out.println("UserDao save method running....");
    }
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
    <property name="strList">
        <list>
            <value>aaa</value>
            <value>bbb</value>
            <value>ccc</value>
        </list>
    </property>
</bean>

3.8.3 集合数据类型( Map )的注入

public class UserDaoImpl implements UserDao {
    private Map<String,User> userMap;
    public void setUserMap(Map<String, User> userMap) {
      this.userMap = userMap;
    }
    public void save() {
        System.out.println(userMap);
        System.out.println("UserDao save method running....");
    }
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
   <property name="map">
       <map>
           <entry key="u1" value-ref="user1"/>
           <entry key="u2" value-ref="user2"/>
           <entry key="u3" value-ref="user3"/>
        </map>
   </property>
</bean>
<bean id="user1" class="com.itheima.domain.User">
   <property name="name" value="tom"/>
   <property name="age" value="18"/>
</bean>
<bean id="user2" class="com.itheima.domain.User">
    <property name="name" value="jack"/>
    <property name="age" value="19"/>
</bean>
<bean id="user3" class="com.itheima.domain.User">
   <property name="name" value="jerry"/>
   <property name="age" value="20"/>
</bean>

3..8.4 集合数据类型(Properties)的注入

public class UserDaoImpl implements UserDao {
    private Properties properties;
    public void setProperties(Properties properties) {
      this.properties = properties;
    }
    public void save() {
        System.out.println(properties);
        System.out.println("UserDao save method running....");
    }
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
    <property name="properties">
        <props>
            <prop key="p1">aaa</prop>
            <prop key="p2">bbb</prop>
            <prop key="p3">ccc</prop>
        </props>
    </property>
</bean>

3.9 引入其他配置文件(分模块开发)

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他 配置文件中,而在Spring主配置文件通过import标签进行加载。

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

3.10 总结

<bean>标签
    id属性:在容器中Bean实例的唯一标识,不允许重复
    class属性:要实例化的Bean的全限定名
    scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
    <property>标签:属性注入
        name属性:属性名称
        value属性:注入的普通属性值
        ref属性:注入的对象引用值
        <list>标签
        <map>标签
        <properties>标签
    <constructor-arg>标签
<import>标签:导入其他的Spring的分文件

4. Spring相关api

4.1 ApplicationContext的继承体系

applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象

4.2 ApplicationContext的实现类

  • ClassPathXmlApplicationContext

    它是从类的根路径下加载配置文件 推荐使用这种

  • FileSystemXmlApplicationContext

    它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

  • AnnotationConfigApplicationContext

    当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解

4.3 getBean()方法的使用

源码

public Object getBean(String name) throws BeansException {    assertBeanFactoryActive();    return getBeanFactory().getBean(name);}public <T> T getBean(Class<T> requiredType) throws BeansException {    assertBeanFactoryActive();    return getBeanFactory().getBean(requiredType);}

其中,当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。

当参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时, 则此方法会报错。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService1 = (UserService) applicationContext.getBean("userService");UserService userService2 = applicationContext.getBean(UserService.class);

4.4 重点api

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService1 = (UserService) applicationContext.getBean("userService");UserService userService2 = applicationContext.getBean(UserService.class);

5. Spring配置数据源

常用数据源(连接池):DBCP /C3P0 /BoneCP /Driud等

5.1 数据源的开发步骤

  • maven中导入数据源的坐标和驱动
  • 创建数据源对象
  • 设置数据源的基本连接数据
  • 使用数据源获取连接资源和归还连接资源

5.1.1 数据源的手动创建

  • 在maven中导入数据源
<dependency>
     <groupId>c3p0</groupId>
     <artifactId>c3p0</artifactId>
     <version>0.9.1.1</version>
</dependency>
<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid</artifactId>
     <version>1.1.10</version>
</dependency>
  • 导入数据库驱动

    <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.6</version>
    </dependency>
    
  • 创建c3p0连接池

    //    测试创建c3p0数据源
        @Test
        public void test01() throws Exception {
    //      创建德鲁伊连接池同理操作,只是把ComboPooledDataSource修改为德鲁伊连接池所对应的
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis");
            dataSource.setUser("root");
            dataSource.setPassword("123456");
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        }
    

5.1.2 配置文件创建数据源

  • 编写配置文件

    db.properties

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username=root
    password=123456
    
  • 读取配置文件创建数据源

        @Test
        public void test02() throws Exception{
    //        读取配置文件
            ResourceBundle rb = ResourceBundle.getBundle("db");
            String driver = rb.getString("driver");
            String url = rb.getString("url");
            String username = rb.getString("username");
            String password = rb.getString("password");
    //        创建数据源对象
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(username);
            dataSource.setPassword(password);
    ​
            Connection connection = dataSource.getConnection();
            System.out.println(connection);
            connection.close();
        }
    

    5.1.3 Spring配置数据源

    可以将DataSource的创建权交由Spring容器去完成

    • DataSource有无参构造方法,而Spring默认就是通过无参构造方法实例化对象的
    • DataSource要想使用需要通过set方法设置数据库连接信息,而Spring可以通过set方法进行字符串注入
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>

测试从容器当中获取数据源

ApplicationContext applicationContext = new 
ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) 
applicationContext.getBean("dataSource");
Connection connection = dataSource.getConnection();
System.out.println(connection);

5.1.3 抽取jdbc配置文件

applicationContext.xml加载jdbc,properties配置文件获取连接信息。

首先要引入context命名空间和约束路径:

  • 命名空间:

    xmlns:context="http://www.springframework.org/schema/context"
    
  • 约束路径:

    http://www.springframework.org/schema/context/spring-context.xsd
    
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

Spring容器加载properties文件

<context:property-placeholder location="xx.properties"/>
<property name="" value="${key}"/>
采用了el表达式

6. Spring注解开发

6.1 Spring原始注解

spring原始注解主要是为了替代的配置

注解说明
@Component使用在类上用于实例化Bean
@Controller使用在web层类上用于实例化Bean
@Service使用在service层类上用于实例化Bean
@Repository使用在dao层类上用于实例化Bean
@Autowired使用在字段上用于根据类型依赖注入
@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入
@Resource相当于@Autowired+@Qualifier,按照名称进行注入
@Value注入普通属性
@Scope标注Bean的作用范围
@PostConstruct使用在方法上标注该方法是Bean的初始化方法
@PreDestroy使用在方法上标注该方法是Bean的销毁方法

当使用注解开发时,需要在xml中配置组件扫描

<!--    组件扫描-->
<context:component-scan base-package="com.itheima"/>

6.1.1 @Compont

使用 @Compont 或 @Repoistory标识UserDaoImpl需要Spring进行实例化

//    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
@Component("userDao")
public class UserDaoImpl implements UserDao {
​
    @Override
    public void save() {
        System.out.println("save方法实现了");
    }
}

6.1.2 @Autowired @Qulifer 进行注入

使用@Autowired @Qulifer 进行userDao的注入

//    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
@Component("userService")
public class UserServiceImpl implements UserService {
​
//  <property name="userDao" ref="userDao"></property>
    @Autowired  // 按照数据类型从Spring容器中进行匹配
    @Qualifier("userDao")   // 是按照id从容器中进行匹配的,但是必须结合 @Autowired 一起使用
    @Resource(name = "userDao") // 可以替代上面两个注解 相当于 @Autowired + @Qualifier("userDao")
    private UserDao userDao;
// 注意 使用注解配置的话 不在需要set方法
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
​
    @Override
    public void save() {
        userDao.save();
    }
}

6.1.3 使用@Value进行字符串的注入

@Component("userDao")
public class UserDaoImpl implements UserDao {
​
    @Value("注入普通数据")
    private String str;
​
    @Value("${driver}")
    private String driver;
​
    @Override
    public void save() {
        System.out.println(str);
        System.out.println(driver);
        System.out.println("save方法实现了");
    }
}

6.1.4 使用@Scope标注Bean的范围

@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
  //此处省略代码
} 

6.1.5 @PostConstruct 标注初始化方法和 @PreDestroy 标注销毁方法

@PostConstruct
public void init(){
  System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
  System.out.println("销毁方法.....");
} 

6.2 Spring新注解

使用Spring原始注解还不能代替所有的xml配置文件。

还需要使用注解代替的配置如下。

非自定义的bean配置

加载配置文件properties的配置 context:property-placeholder

组件扫描配置 context:component-scan

引入其他文件

注解说明
@ComponentScan用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 一样
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@Bean使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource用于加载.properties 文件中的配置
@Import用于导入其他配置类
//加载配置文件
//<context:property-placeholder location="db.properties"/>
@PropertySource("classpath:db.properties")
public class DataSourceConfiguration {
    @Value("${driver}")
    private String driver;
    @Value("${url}")
    private String url;
    @Value("${username}")
    private String username;
    @Value("${password}")
    private String password;
    @Bean("dataSource") // Spring 会将当前方法的返回值以指定名字存储到Spring容器中
    public DataSource getDataSource() throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
​
//标志该类是Sping的核心配置类
@Configuration
//<!--    组件扫描-->
//<context:component-scan base-package="com.itheima"/>
@ComponentScan("com.itheima")
@Import({DataSourceConfiguration.class})
public class SpringCofiguration {
  //....... 
}
​

测试加载核心配置类创建Spring容器

public class UserController {
    public static void main(String[] args) throws SQLException {
//        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        ApplicationContext applicationContext = new
                AnnotationConfigApplicationContext(SpringCofiguration.class);
        UserService userService = (UserService)
                applicationContext.getBean("userService");
        userService.save();
        DataSource dataSource = (DataSource)
                applicationContext.getBean("dataSource");
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

6.3 Spring集成Junit

① 导入spring集成Junit的坐标

② 使用@Runwith注解替换原来的运行期

③ 使用@ContextConfiguration指定配置文件或配置类

④ 使用@Autowired注入需要测试的对象

⑤ 创建测试方法进行测试

  • 导入spring集成Junit的坐标
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency
  • 使用@Runwith注解替换原来的运行期
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
  • 使用@ContextConfiguration指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
  • 使用@Autowired注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
    @Autowired
    private UserService userService;
}
  • 创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
    @Autowired
    private UserService userService;
    @Test
    public void testUserService(){
      userService.save();
    }
}

7. Spring集成web环境

7.1 ApplicationContext应用上下文获取方式

  • 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从 容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置 文件加载多次,应用上下文对象创建多次。
  • 在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加 载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域 中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>applicationContext.xml</param-value>
</context-param>
​
@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
//        读取web.xml中的参数
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        //        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
//        将spring的应用上下文存储到ServletContext的域中
        sce.getServletContext().setAttribute("app",app);
        System.out.println("Spring容器创建完毕");
    }
}
​
​
@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
        UserService userService = app.getBean(UserService.class);
        userService.save();
    }
}

7.2 Spring提供获取应用上下文的工具

上面的7.1分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工 具WebApplicationContextUtils供使用者获得应用上下文对象。

  • web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
  • 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
<!--在pom.xml中配置-->
<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-web</artifactId>
     <version>5.2.12.RELEASE</version>
</dependency>
<!--在web.xml中配置-->
<context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath:applicationContext.xml</param-value>
</context-param><listener>
    <listener-class>org.springframework.web.context.ContextLoader</listener-class>
</listener>
@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        WebApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService userService = app.getBean(UserService.class);
        userService.save();
    }
}

6、Spring 的 AOP 简介

6.1 什么是 AOP

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理 实现程序功能的统一维护的一种技术。 AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍 生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序 的可重用性,同时提高了开发的效率。

AOP术语

  • 连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强。连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
  • 切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件。切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
  • 增强(Advice) 增强是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息 通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:
  • 前置通知(before):在执行业务代码前做些操作,比如获取连接对象
  • 后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
  • 异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
  • 返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
  • 环绕通知(around),这个目前跟我们谈论的事务没有对应的操作,所以暂时不谈
  • 目标对象(Target) 需要被加强的业务对象
  • 织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
  • 代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
  • 切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。比如上文讨论的数据库事务,这个数据库事务代码贯穿了我们的整个代码,我们就可以这个叫做切面。 SpringAOP将切面定义的内容织入到我们的代码中,从而实现前后的控制逻辑。 比如我们常写的拦截器Interceptor,这就是一个切面类。

6.2 AOP的作用及其优势

  • 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
  • 优势:减少重复代码,提高开发效率,并且便于维护

6.3 AOP 的底层实现

实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态 的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

6.4 AOP动态代理技术

常用的动态代理技术

  • JDK 代理 : 基于接口的动态代理技术
  • cglib 代理:基于父类的动态代理技术

image-20220108201601556

6.5 基于 XML 的 AOP 开发

快速入门

① 导入 AOP 相关坐标

② 创建目标接口和目标类(内部有切点)

③ 创建切面类(内部有增强方法)

④ 将目标类和切面类的对象创建权交给 spring

⑤ 在 applicationContext.xml 中配置织入关系

⑥ 测试代码

  • ① 导入 AOP 相关坐标
<!--导入spring的context坐标,context依赖aop-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

② 创建目标接口和目标类(内部有切点)

public interface TargetInterface {
  public void method();
}
​
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③ 创建切面类(内部有增强方法)

public class MyAspect {
    //前置增强方法
    public void before(){
        System.out.println("前置代码增强.....");
    }
}
​
//Proceeding JoinPoint:  正在执行的连接点===切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强....");
        Object proceed = pjp.proceed();//切点方法
        System.out.println("环绕后增强....");
        return proceed;
    }

④ 将目标类和切面类的对象创建权交给 spring

<!--配置目标类-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<!--配置切面类-->
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>

⑤ 在 applicationContext.xml 中配置织入关系

导入aop命名空间

<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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

配置切点表达式和前置增强的织入关系

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
        <aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method())"></aop:before>
    </aop:aspect>
</aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

6.5.1切点表达式的写法

语法:

execution([修饰符] 返回值类型 包名.类名.方法名(参数))

访问修饰符可以省略

返回值类型、包名、类名、方法名可以使用星号* 代表任意

包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类

参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

例如:

execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..)) —————>{常用方法}
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))

6.5.2 通知的类型

通知的配置语法

<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>

image-20220108210507163

6.5.3 切点表达式的抽取

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>

6.6 基于注解的开发

① 创建目标接口和目标类(内部有切点)

② 创建切面类(内部有增强方法)

同上

③ 将目标类和切面类的对象创建权交给 spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
    System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

④ 在切面类中使用注解配置织入关系

@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("execution(* com.itheima.aop.*.*(..))")
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

⑤ 在配置文件中开启组件扫描和 AOP 的自动代理

<!--组件扫描-->
<context:component-scan base-package="com.itheima.aop"/>
<!--aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • 注解通知的类型

    image-20220108211515201

  • 切点表达式的抽取

    同 xml 配置 aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut 注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

    @Component("myAspect")
    @Aspect
    public class MyAspect {
        @Before("MyAspect.myPoint()")
        public void before(){
            System.out.println("前置代码增强.....");
            }
           @Pointcut("execution(* com.itheima.aop.*.*(..))")
        public void myPoint(){}
    }
    

8. 声明式事务控制

8.1 编程式事务控制相关对象

8.1.1 PlatformTransactionManager

PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

方法说明
TransactionStatus getTransaction(TransactionDefination defination)获取事务的状态信息
void commit(TransactionStatus status)提交事务
void rollback(TransactionStatus status)回滚事务

8.1.2 TransactionDefinition

TransactionDefinition 是事务的定义信息对象,里面有如下方法:

方法声明
int getIsolationLevel()获得事务的隔离级别
int getIsolationLevel()获得事务的传播行为
int getTimeout()获得超时时间
boolean isReadOnly()是否只读
  • 事务的隔离级别

    设置事务的隔离级别,可以解决事务并发产生的问题,比如脏读,不可重复读和虚读。

    • ISOLATION_DEFAULT
    • SOLATION_READ_UNCOMMITTED
    • ISOLATION_READ_COMMITTED
    • ISOLATION_REPEATABLE_READ
    • ISOLATION_SERIALIZABLE
  • 事务传播行为

    • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
    • MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
    • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    • NEVER:以非事务方式运行,如果当前存在事务,抛出异常  NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
    • 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
    • 是否只读:建议查询时设置为只读

8.1.3 TransactionStatus

TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

方法说明
boolean hasSavepoint()是否存储回滚点
boolean isCompleted()事务是否完成
boolean isNewTransaction()是否是新事务
boolean isRollbackOnly()事务是否回滚

8.2 基于xml的声明式事务控制

8.2.1 简介

Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明 ,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。

声明式事务处理的作用

  • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如 此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话, 也只需要在定义文件中重新配置即可
  • 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译 ,这样维护起来极其方便

注意:Spring 声明式事务控制底层就是AOP。

8.2.2 声明式事务控制的实现

  • 谁是切点
  • 谁是通知
  • 配置切面

① 引入tx命名空间

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx.xsd

② 配置事务增强

    <!--配置平台事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
​
    <!--通知  事务的增强-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--设置事务的属性信息的-->
        <tx:attributes>
            <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
            <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

③ 配置事务 AOP 织入

    <!--配置事务的aop织入-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

④ 测试事务控制转账业务代码

@Override
public void transfer(String outMan, String inMan, double money) {
    accountDao.out(outMan,money);
    int i = 1/0;
    accountDao.in(inMan,money);
}

\