spring的初次接触

94 阅读14分钟

我正在参加「掘金·启航计划」

Spring⭐️

1. Spring(融合剂) 简介

Spring框架的目标是使j2EE开发变得更容易使用,通过启用基于POJO编程模型来促进良好的编程实践

spring理念:使现有的技术更加容易使用,解决企业应用开发的复杂性

  • 开源的免费框架,是一个容器,可以管理所有的组件(类);
  • 轻量级的、非入侵的框架,不依赖于Spring的API
  • 控制反转(IOC)和面向切面编程(AOP)
  • 支持事务处理,支持 对框架整合
  • 组件化、一站式

总结:Spring是一个轻量级的控制反转(IOC),面向切面编程(AOP)的框架!

1.1 SpringFramework

image.png spring主要由例如以下几个模块构成

  • Core container:IOC为核心层(spring-ioc
  • Resource:资源(spring-resource
  • Aop:面向切面编程(Spring AOP
  • Data Access:数据库訪问层
  • Web:spring mvc层
  • Test:spring測试框架
1.1.1 Spring Framework 特性
  • 非入侵式:使用Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏其原有结构,反而能将组件结构进一步简化。这就使得基于Spring Framework 开发应用程序时结构清晰、简洁优雅。
  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
  • 面向切面编程:AOP——Aspect Oriented Programming,在不断修改源代码的基础上增强代码功能。
  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和java注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不絮的搭建超大型复杂应用系统。
  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring旗下的项目已经覆盖了广泛的领域,很多方面的功能性需求可以在Spring Framework 的基础上全部使用Spring来实现。

总结:控制,其实就是由Spring来管理对象,创建对象。反转,其实就是从主动获取对象,变为被动获取对象的过程。

2. Spring 中pom.xml的配置

 <dependencies>
     <!-- 基于Maven的传递性 导入该包即可导入所有需要的jar包 -->
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-webmvc</artifactId>
         <version>5.3.22</version>
     </dependency>
 </dependencies>

3. IOC创建对象的方式

3.1 下标赋值

 在pojo包下创建一个user的类
 <bean id="user" class="com.cczj.pojo.user">
     <constructor-arg index="0" value="第一个属性的值"/>
 </bean>

3.2 类型

 <bean id="user" class="com.cczj.pojo.user">
     <constructor-arg type="java.lang.String" value="类型为String的值"/>
 </bean>

3.3 参数名

 <bean id="user" class="com.cczj.pojo.user">
     <constructor-arg name="name" value="name属性的值"/>
 </bean>

总结:在配置spring文件被加载的时候,该容器中的对象就已经被创建了,在之后调用的过程中,该对象不会重新创建。

4. Spring 配置

4.1 别名

将bean的ID进行起别名的操作,如果添加了别名,也可以通过别名来获取对象

 <bean id="user" class="com.cczj.pojo.user">
     <constructor-arg name="name" value="name属性的值"/>
 </bean>
 <alias name="user" alias="userNew"/>

getBean 操作时,可以使用别名来获取!

4.2 Bean的配置

id: bean的唯一标识符,也就是相当于我们学的对象名

class: bean对象所对应的全限定名:包名 + 类名

name: 别名,可以取多个别名

4.3 import

导入多个spring-xml文件,将这些文件进行合并。合并之后,在使用时只需要获取总的文件就可以

5. DI依赖注入

5.1 构造器注入

以上写过

5.2 Set方式注入【重点】

  • 依赖注入:Set注入

    • 依赖:bean对象的创建依赖于容器
    • bean对象中的所有属性,由容器来注入

在pojo中创建类,用来测试注入

 public class Student {
     private String name;
     private Address address;
     private String[] books;
     private List<String> hobbies;
     private Map<String, String> card;
     private Set<String> games;
     private String wife;
     private Properties info;
 }

各种数据类型用于注入的方式---> 以Student类为模板

     <bean id="address" class="com.cczj.spring.pojo.Address"/>
     <bean id="student" class="com.cczj.spring.pojo.Student">
         <!-- 第一种注入方法 String类型注入 -->
         <property name="name" value="名称"/>
         <!-- 第二种注入方法 类注入 -->
         <property name="address" ref="address"/>
         <!-- 第三种注入方法 数组注入 -->
         <property name="books">
             <array>
                 <value>红楼梦</value>
                 <value>三国演义</value>
                 <value>西游记</value>
                 <value>水浒传</value>
             </array>
         </property>
         <!-- 第四种注入方法 List注入 -->
         <property name="hobbies">
             <list>
                 <value>听歌</value>
                 <value>打游戏</value>
                 <value>学DP</value>
             </list>
         </property>
         <!-- 第五种注入方法 Map注入 -->
         <property name="card">
             <map>
                 <entry key="身份证" value="312344198310345573"/>
                 <entry key="银行卡" value="21312489779129321938912"/>
             </map>
         </property>
         <!-- 第六种注入方法 Set注入 -->
         <property name="games">
             <set>
                 <value>LOL</value>
                 <value>COC</value>
                 <value>BOB</value>
             </set>
         </property>
         <!-- 第七种注入方法 null注入 -->
         <property name="wife">
             <null/>
         </property>
         <!-- 第八种注入方法 配置类注入 -->
         <property name="info">
             <props>
                 <prop key="学号">314143252</prop>
                 <prop key="班级">java班</prop>
                 <prop key="性别"></prop>
             </props>
         </property>
     </bean>

5.3 拓展注入

两种命名空间的注入方式

     <!-- 使用p命名空间来赋值 p指的是 properties -->
     <bean id="address" class="com.cczj.spring.pojo.Address" p:name="张三" p:gender="男"/>
     <!-- 使用c命名空间来赋值 c指的是 construct-arg -->
     <bean id="address2" class="com.cczj.spring.pojo.Address" c:name="张三" c:gender="女"/>

注意点:p命名和c命名不能直接导入,需要引入约束 (IDE中可以自动导入)

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

6. Bean的作用域

6.1 单例模式

     <bean id="address" class="com.cczj.spring.pojo.Address" p:name="张三" p:gender="男" scope="singleton"/>

scope的默认为单例模式

6.2 原型模式

     <bean id="address" class="com.cczj.spring.pojo.Address" p:name="张三" p:gender="男" scope="prototype"/>

每次getBean的时候都会产生新对象

7. Bean的自动装配

  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中自动寻找,并且自动给bean装配属性~

在Spring 中有三种装配的方式

  1. 在xml中显示的配置
  2. 在java中显示的配置
  3. 隐式的自动装配bean 【重点】

7.1 byName 和 byType

byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的 bean id

byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean

总结:

  • 使用byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
  • 使用byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

提示:

  • 如果使用注解进行装配

    • 当注入容器存在多个类型一致的对象时,会使用byName进行装配
    • 当注入在IOC容器中该类型只存在一种时,就通过byType进行装配

7.2 使用注解实现自动装配

jdk 1.5版本支持注解、spring 2.5支持注解

要使用注解须知:

  1. 导入约束 context 约束

  2. 配置注解的支持 <context:annotation-config/>

  3. @Autowired 自动装配

    1. 可以直接在属性上使用,也可以在set方法上使用
    2. 使用Autowired 可以不用编写set方法,前提是这个自动装配在IOC(spring)容器中存在,且符合名字byName(唯一)
    3. 如果无法通过Autowired注解实现装配(有多个类型的时候,因为Autowired默认是通过byType来进行装配的)可以用Qualifier(value="name") 注解转换为使用byName来进行装配
  4. @Resource 相当于 @Autowired 和 @Qualifier 的组合,可以通过类型和名字进行装配

@Resource 和 @Autowired 的区别:

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired 通过byType的方式实现,而且必须要求这个对象存在!【常用】
  • @Resource 默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】 (注:JDK 11 之后将这个注解取消了)

8. 使用注解开发

在spring4之后,若使用注解开发,必须要保证aop的包导入了

image.png 使用注解需要导入 context 约束,增加注解的支持 <context:annotation-config/>

8.1 Bean

获取Bean的三种方式

  • 根据Bean的ID获取
  • 根据Bean的类型获取
  • 根据Bean的类型和ID获取 (常用)
 @Test
 public void testSpringIOC() {
     // -- 获取IOC容器
     ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
     // -- 根据Bean的Id获取Bean
     //        Student bean = (Student) ioc.getBean("studentOne");
     // -- 根据Bean的类型来获取Bean
     //        Student bean = ioc.getBean(Student.class);
     // -- 根据Bean的类型和ID来获取指定的Bean
     Student bean = ioc.getBean("studentOne", Student.class);
     System.out.println(bean);
 }

8.2属性如何注入

 @Component
 public class User {
     @Value("cczj")
     private String name;
 ​
     @Override
     public String toString() {
         return "Hello{" +
                 "name='" + name + ''' +
                 '}';
     }
 }

8.3 衍生的注解

  • @Component:将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Reponsitory:将类标识为持久层组件

@Component 有几个衍生注解,我们在web开发中,会按照mvc三层架构分层~

  • dao【@Responent】
  • service【@Service】
  • controller【@Controller】

这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

8.4 自动装配

  • @Autowired :自动装配通过名称或者类型,默认通过类型进行装配

    • 如果@Autowired 不能唯一自动装配属性,则需要通过@Qualifier(value="xxx")转换为名称进行装配
  • @Nullable :字段标记了这个注解,说明这个字段可以为空

  • @Resource :自动装配通过名字、类型,相当于 @Autowired + @Qualifier

8.5 作用域

@scope("prototype")

8.6 小结

xml 与 注解

  • xml更加万能,适用于任何场合!维护简单方便
  • 注解 不是自己的类使用不了,维护相对复杂

xml与注解最佳实践

  • xml用来管理Bean
  • 注解只负责完成属性的注入
  • 我们在使用过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
 <!-- 指定需要扫描的包 这个包下的注解就会生效 -->
 <context:component-scan base-package="com.cczj.spring"/>
 <context:annotation-config/>

9. 使用java的方式配置spring

我们可以完全不适用Spring的xml配置,全部交给java来做

javaConfig 是 Spring的一个子项目,在Spring4之后,他成为了一个核心功能!

9.1 实体类

 @Component
 public class User {
     private String name;
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public void show() {
         System.out.println("Hello" + name);
     }
 ​
     @Override
     public String toString() {
         return "Hello{" +
                 "name='" + name + ''' +
                 '}';
     }
 }

9.2 java配置文件

 // -- @Configuration 代表这是一个配置类,和我们之前配置的 beans.xml 相同
 @Configuration
 @ComponentScan("com.cczj.pojo") // --开启扫描
 public class JavaImplXml {
     // -- 注册一个bean 相当于之前写的一个bean标签
     // 这个方法的名字就相当于bean标签中的Id属性
     // 这个方法的返回值就相当于bean标签中的class属性
     @Bean
     public User getUser() {
         return new User(); // 返回需要注入的对象
     }
 }

9.3 测试类

 public class MyTest {
     @Test
     public void test() {
         ApplicationContext context = new AnnotationConfigApplicationContext(JavaImplXml.class);
         User user = context.getBean("getUser", User.class);
         System.out.println(user);
     }
 }

10 AOP

10.1 AOP概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

10.2 AOP相关术语 (引用代理模式进行讲解)

  • 横切关注点 (实现的一些业务,值得关注的地方)

    • 从每个方法中抽取出来的同一类非核心业务代码。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
  • 通知(切面里的一系列方法)

    • 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫做通知方法。
    • 前置通知:在被代理的目标方法之前执行
    • 返回通知:在被代理的目标方法成功结束后执行
    • 异常通知:在被代理的目标方法异常结束后执行
    • 后置通知:在被代理的目标方法最终结束后执行
    • 环绕通知:使用try……catch……finally结构环绕整个被代理的目标方法,包括以上四种通知所对应的位置
  • 切面(用于实现横切关注点的一些类)

    • 封装通知方法的类
  • 目标(通知谁去执行它,在哪个地方执行)

    • 被代理的目标对象
  • 代理(动态代理,Spring内部处理)

    • 向目标对象应用通知之后创建的代理对象
  • 连接点

    • 抽取横切关键点的位置就是一个连接点,比较抽象
  • 切入点(在哪个地方执行)

    • 定位连接点的方式
    • 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物

10.3 基于注解的AOP

  1. 先在pom中导入依赖
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-aspects</artifactId>
     <version>5.3.22</version>
 </dependency>
  1. 创建一个切面类
 @Aspect // -- 标注这个类是一个切面
 public class AnnotationPointCut {
     // -- 设置公共切入点
     @Pointcut("execution(* com.cczj.service.impl.*.*(..))")
     public void pointCut() {
     }
 ​
     @Before("pointCut()")
     public void before() {
         System.out.println("方法执行前~~~~");
     }
 ​
     @After("pointCut()")
     public void after() {
         System.out.println("方法执行后~~~~");
     }
 ​
     // -- 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
     @Around("pointCut()")
     public void around(ProceedingJoinPoint jp) throws Throwable {
         System.out.println("环绕前~~");
         // -- 获得方法签名
         Signature signature = jp.getSignature();
         System.out.println("signature = " + signature);
         // -- 执行方法
         Object proceed = jp.proceed();
         System.out.println("环绕后~~");
         System.out.println(proceed);
     }
 }
  1. 测试结果

image.png

  1. 拓展注解
 // -- 在切面中,需要通过指定的注解将方法标识为通知方法
 @Pointcut("execution(* com.cczj.spring.aop.*.*(..))")
 public void pointCut() {
 }
 @Before("pointCut()") // 等同于下方Before
 // @Before("execution(public double com.cczj.spring.aop.CalculatorImpl  .add(double, double))")
 @Before("execution(* com.cczj.spring.aop.*.*(..))")
 表示包下任何访问修饰符 任何返回值 任何的类中的所有方法的所有参数
 @AfterReturning(value="pointCut()", returning="result") 
 将接收方法的返回值设置为result
 @AfterThrowing(value="pointCut()", throwing="e") 
 接收目标方法发生的异常信息

10.4 基于xml的AOP

10.4.1 第一种方式 (默认代理为JDK)
 <aop:config>
     <!-- 切入点 -->
     <aop:pointCut id="pointCut" expression="expression(* com.cczj.spring.aop.*.*(..))">
     </aop:pointCut>
     <!-- 执行环绕 -->
     <aop:advisor advice-ref="log" pointCut-ref="pointCut"/>
     <aop:advisor advice-ref="afterlog" pointCut-ref="pointCut"/>
 </aop:config>
10.4.2 第二种方式 (自定义类)
 <bean id="diy" class="com.cczj.diy.DiyPointCut"/>
 <aop:config>
     <!-- 自定义切面, ref 要引用的类 -->
     <aop:aspect ref="diy">
         <!-- 切入点 -->
         <aop:pointCut id="point" expression="expression(* com.cczj.service.xxxServiceImpl.*(..))"/>
         <!-- 通知 -->
         <aop:before method="before" pointCut-ref="point"/>
         <aop:before method="after" pointCut-ref="point"/>
     </aop:aspect>
 </aop:config>

11. 整合Mybatis

步骤:

  1. 导入相关的jar包

    • junit
    • mybatis
    • mysql数据库 相关驱动
    • spring相关
    • aop织入
    • mybatis-spring
  2. 编写配置文件

  3. 测试

编写spring.xml,将mybatis部分配置整合在spring.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"
        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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
 ​
     <!-- 扫描组件 (除了控制层)(因为控制层已经被扫描过了)-->
     <context:component-scan base-package="com.cczj.ssm">
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
     </context:component-scan>
 ​
     <!-- 引入jdbc property -->
     <context:property-placeholder location="classpath:jdbc.properties"/>
     <!-- 配置数据源 -->
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
         <property name="driverClassName" value="${jdbc.driver}"/>
         <property name="url" value="${jdbc.url}"/>
         <property name="username" value="${jdbc.username}"/>
         <property name="password" value="${jdbc.password}"/>
     </bean>
 ​
     <!-- 配置事务管理器 -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="dataSource"/>
     </bean>
     <!-- 开启事务的注解驱动 将使用注解@TransactionManager标识的方法或类中所有的方法进行事务管理 -->
     <tx:annotation-driven transaction-manager="transactionManager"/>
     <!--配置SqlSessionFactoryBean,可以直接在Spring的IOC中获取SqlSessionFactory-->
     <!-- 此处不用设置ID,因为ID为指定ID -->
     <bean class="org.mybatis.spring.SqlSessionFactoryBean">  
         <!--设置MyBatis的核心配置文件的路径-->
         <property name="configLocation" value="classpath:mybatis-config.xml"/>
         <!--设置数据源-->
         <property name="dataSource" ref="dataSource"/>
         <!--设置类型别名所对应的包-->
         <property name="typeAliasesPackage" value="com.cczj.ssm.pojo"/>
         <!--设置映射文件的路径,只有映射文件的包和mapper接口的包不一致时需要设置-->
         <!--<property name="mapperLocations" value="classpath:mappers/*.xml"></property>-->
     </bean>
 ​
     <!--
         配置mapper接口的扫描,可以将指定包下所有的mapper接口通过SqlSession创建
         代理实现类对象,并将这些对象交给IOC容器管理
     -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
         <property name="basePackage" value="com.cczj.ssm.mapper"/>
     </bean>
 </beans>

12. 声明式事务

12.1 回顾事务

  • 把一组业务当成一个业务来做。要么都成功,要么都失败!
  • 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎~
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性:确保一件事要么都成功,要么都失败
  • 一致性:一旦事务完成,要么都被提交,要么就全部失败
  • 隔离性:多个业务可能操作同一个资源,防止业务损坏(避免发生错误)
  • 持久性:事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!

12.2 配置xml事务

 <tx:advice id="txAdvice" transcation-manager="transcationManager">
     <!-- 给哪些方法配置事务 -->
     <!-- 配置事务的传播特性 new propagation -->
     <tx:attributes>
         <tx:method name="add" propagation="REQUIRED"/>
         <tx:method name="delete" propagation="REQUIRED"/>
         <tx:method name="update" propagation="REQUIRED"/>
         <tx:method name="query" propagation="REQUIRED"/>
     </tx:attributes>
 </tx:advice>
 ​
 <!-- 配置事务切入 -->
 <aop:config>
     <aop:pointCut id="txPointCut" expression="expression(* com.cczj.mapper.*.*(..))"/>
     <aop:advisor advice-ref="txAdvice" pointCut-ref="txPointCut"/>
 </aop:config>

在声明式事务中,要配置一个切面,其中就用到了propagator,其中propagation一共有七种配置

  • REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 (默认)[常用]
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
  • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

12.3 注解配置事务

开启事务的注解驱动

 <tx:annotation-driven />

@Transactional 注解标识在类中,效果为全类开启事务。标识在方法中,效果为这个方法开启事务

事务属性:

  • 只读:@Transactional(readOnly = true) 只允许查看,不允许修改
  • 超时:@Transactional(timeout = 3) 避免程序运行bug浪费数据库资源,提前关闭并回滚
  • 回滚策略:@Transactional(noRollbackFor = ?) 选择回滚的异常情况。可以选择异常情况下进行回滚,也可以选择除了该异常情况下都回滚
  • 事务的隔离级别:@Transactional(isolation = Isolation.DEFAULT)

文章结束语

文章分析:

对于新接触框架的我是这样认为的。就像开头所说,spring像是一个融合剂,不仅仅是因为他可以作为容器,还在于他能够将其他框架或技术进行糅合。
spring的容器化管理带给人一种焕然一新的感觉,把编码变得更艺术化和简练化。通过注解开发不仅加快了编码的速度,更减少了臃肿的代码量,实在是强迫症的福音!

本人感悟:

本人是初学Spring框架。总共B站看过两个spring的视频教程,第一个视频是尚硅谷的,偏向于spring的源码部分,另一个是狂神说的,偏向于实践部分。如果你跟我一样也是一个初学者,那么我比较推荐先看实践操作部分然后再去深入源码部分。