Spring详解 --持续更新

x33g5p2x  于2022-03-06 转载在 其他  
字(15.7k)|赞(0)|评价(0)|浏览(465)

本文素材来自动力结点王妈妈的课程

什么是Spring

Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解 决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。通过 Spring 提供的 AOP 功能,方便进行面向切面的编程。

Spring官网

IoC控制反转

控制反转(IoC,Inversion of Control),是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。Spring 框架使用依赖注入(DI)实现 IoC。

初识Spring

1、创建maven项目

2、引入 maven 依赖

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.2.5.RELEASE</version>
  5. </dependency>
  6. <build>
  7. <plugins>
  8. <plugin>
  9. <artifactId>maven-compiler-plugin</artifactId>
  10. <version>3.1</version>
  11. <configuration>
  12. <source>1.8</source>
  13. <target>1.8</target>
  14. </configuration>
  15. </plugin>
  16. </plugins>
  17. </build>

3、定义接口与实体类

  1. public interface SomeService {
  2. void doSome();
  3. }
  4. public class SomeServiceImpl implements SomeService {
  5. public SomeServiceImpl() {
  6. super();
  7. System.out.println("SomeServiceImpl无参数构造方法");
  8. }
  9. @Override
  10. public void doSome() {
  11. System.out.println("doSome业务方法");
  12. }
  13. }

4、创建 Spring 配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但Spring 建议的名称为applicationContext.xml。注意要将resources文件将指定为资源文件夹(这个文件夹中的文件编译之后都会放在类路径下),如何指定?
右键文件夹 → Mark Directory as → Resources Root

  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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  5. <!--
  6. 注册bean对象
  7. id:自定义对象的名称
  8. class:类的全限定名称,不能是接口
  9. -->
  10. <bean id="someService" class="com.why.SomeServiceImpl"></bean>
  11. </beans>

5、定义测试类

  1. public class MyTest {
  2. @Test
  3. public void test01(){
  4. //指定spring配置文件的位置和名称
  5. String resource = "applicationContext.xml";
  6. //创建spring容器对象
  7. ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
  8. //使用id从spring容器中获取对象
  9. SomeService someService = (SomeService) ac.getBean("someService");
  10. //执行对象的方法
  11. someService.doSome();
  12. }
  13. }

基于XML依赖注入(DI)

set注入

  1. public class School {
  2. private String schoolName;
  3. private String addr;
  4. /*setter*/
  5. /*toString*/
  6. }
  7. public class Student {
  8. private String name;
  9. private int age;
  10. private School school;
  11. /*setter*/
  12. /*toString*/
  13. }
  1. <!--使用set方法注入参数-->
  2. <bean id="mySchool" class="com.why.School">
  3. <!--简单类型-->
  4. <property name="schoolName" value="浙江中医药大学"></property>
  5. <property name="addr" value="浙江杭州"></property>
  6. </bean>
  7. <bean id="zs" class="com.why.Student">
  8. <property name="name" value="张三"></property>
  9. <property name="age" value="20"></property>
  10. <!--引用类型-->
  11. <property name="school" ref="mySchool"></property>
  12. </bean>

构造注入

  1. //Student构造函数
  2. public Student(String stuName, int stuAge, School stuSchool) {
  3. this.name = stuName;
  4. this.age = stuAge;
  5. this.school = stuSchool;
  6. }
  1. <!--构造注入-->
  2. <bean id="ls" class="com.why.Student">
  3. <constructor-arg name="stuName" value="李四"></constructor-arg>
  4. <constructor-arg name="stuAge" value="20"></constructor-arg>
  5. <constructor-arg name="stuSchool" ref="mySchool"></constructor-arg>
  6. </bean>
  7. <!--index:指明该参数对应着构造器的第几个参数,从0开始(少用)-->
  8. <bean id="ww" class="com.why.Student">
  9. <constructor-arg index="0" value="王五"></constructor-arg>
  10. <constructor-arg index="1" value="20"></constructor-arg>
  11. <constructor-arg index="2" ref="mySchool"></constructor-arg>
  12. </bean>
  13. <!--index属性不要也行,但要注意:若参数类型相同或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致-->
  14. <bean id="zl" class="com.why.Student">
  15. <constructor-arg value="赵六"></constructor-arg>
  16. <constructor-arg value="20"></constructor-arg>
  17. <constructor-arg ref="mySchool"></constructor-arg>
  18. </bean>

引用类型属性自动注入

byName方式自动注入

容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的id 进行比较而实现自动注入的

  1. public class Student {
  2. private String name;
  3. private int age;
  4. private School schoolXXX;
  5. /*setter*/
  6. /*toString*/
  7. }

byName方式自动注入时Spring会根据用于类型的变量名去调用相应的setter方法
(比如这里我们要注入zy对象 Spring根据autowire的属性发现是根据byName方式自动注入。Spring会找到Student中的Schoo引用类型,根据schoolXXX属性名在容器中找到名为schoolXXX的对象(如果在找不到schoolXXX对象Spring就会去找引用类型名首字母小写的对象本例中为school 如果两个都找不到就会报错)然后去调用setSchoolXXX方法将引用类型注入)

  1. <bean id="schoolXXX" class="com.why.School">
  2. <property name="schoolName" value="温州大学"></property>
  3. <property name="addr" value="浙江温州"></property>
  4. </bean>
  5. <bean id="zy" class="com.why.Student" autowire="byName">
  6. <property name="name" value="张扬"></property>
  7. <property name="age" value="20"></property>
  8. </bean>

byType方式自动注入

使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类或是实现类)。

  1. <bean id="cb" class="com.why.Student" autowire="byType">
  2. <property name="name" value="陈八"></property>
  3. <property name="age" value="20"></property>
  4. </bean>

如果使用byType方式自动注入要求同源的被调用 bean只能有一个否则会报以下错误
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.why.School’ available: expected single matching bean but found 2: mySchool,school

为应用指定多个 Spring 配置文

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。

spring-school.xml

  1. <bean id="middleSchool" class="com.why.School">
  2. <property name="schoolName" value="温州中学"></property>
  3. <property name="addr" value="浙江温州"></property>
  4. </bean>

spring-student.xml

  1. <bean id="lh" class="com.why.Student">
  2. <property name="name" value="林浩"></property>
  3. <property name="age" value="20"></property>
  4. <!--引用类型-->
  5. <property name="school" ref="middleSchool"></property>
  6. </bean>
  7. <!--引入spring-school.xml-->
  8. <import resource="classpath:spring-school.xml"/>

applicationContext.xml(主配置文件)

  1. <import resource="classpath:spring-school.xml"/>
  2. <import resource="classpath:spring-student.xml"/>
  3. <!--也可使用通配符*-->
  4. <!-- <import resource="classpath*:spring-*.xml"/> -->

基于注解的 DI

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。

  1. <!--声明组件扫描器:指定注解所在的包-->
  2. <context:component-scan base-package="com.why"/>
  3. <!-- 指定多个包
  4. <context:component-scan base-package="com.why,com.ehy"/> 第1种方式:逗号分隔
  5. <context:component-scan base-package="com.why;com.ehy"/> 第2种方式:分号分隔
  6. <context:component-scan base-package="com.why com.ehy"/> 第3种方式:空格分隔
  7. <context:component-scan base-package="com"/> 第4种方式:指定到父包,也会扫描到子包下级的子包-->

定义bean的注解@Component

需要在类上使用注解@Component,该注解的 value 属性用于指定该bean 的 id 值

  1. //@Component 不指定value属性,bean的id是类名的首字母小写school
  2. @Component("collage")//类似xml中的<bean id="collage" class="com.why.School"></bean>
  3. public class School {
  4. private String schoolName;
  5. private String addr;
  6. }
  • @Repository 用于对 DAO 实现类进行注解
  • @Service 用于对 Service 实现类进行注解
  • @Controller 用于对 Controller 实现类进行注解
    这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。

简单类型属性注入@Value

使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

  1. @Component("collage")
  2. public class School {
  3. @Value("浙江大学")
  4. private String schoolName;
  5. @Value("浙江杭州")
  6. private String addr;
  7. /*toString()*/
  8. }

byType 自动注入@Autowired

  1. @Component("tq")
  2. public class Student {
  3. @Value("田七")
  4. private String name;
  5. @Value("20")
  6. private int age;
  7. @Autowired
  8. private School school;
  9. /*相当于
  10. <bean id="tq" class="com.why.Student" autowire="byType">
  11. <property name="name" value="田七"></property>
  12. <property name="age" value="20"></property>
  13. </bean>
  14. */
  15. }

byName 自动注入@Autowired 与@Qualifier

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到set 方法上。

  1. @Component("tq")
  2. public class Student {
  3. @Value("田七")
  4. private String name;
  5. @Value("20")
  6. private int age;
  7. @Autowired
  8. @Qualifier("middleSchool")
  9. private School school;

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

JDK 注解@Resource 自动注入

Spring 提供了对 jdk 中@Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean。默认是按名称注入,采用默认按名称的方式注入按名称不能注入 bean时,则会按照类型进行 Bean 的匹配注入。

  1. @Component("tq")
  2. public class Student {
  3. @Value("田七")
  4. private String name;
  5. @Value("20")
  6. private int age;
  7. @Resource //byType
  8. //@Resouces(name="middleSchool") byName
  9. private School school;
  10. }

AOP 面向切面编程

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 编程术语

  • 切面(Aspect)
    切面泛指交叉业务逻辑。如事务处理、日志处理就可以理解为切面
  • 连接点(JoinPoint)
    连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
  • 切入点(Pointcut)
    切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
  • 目标对象(Target)
    目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
  • 通知(Advice)
    通知表示切面的执行时间,Advice 也叫增强。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。

AspectJ

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。

AspectJ官网

AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After

AspectJ 的切入点表达式

  1. execution(访问权限类型? 返回值类型 包名类名? 方法名(参数类型和参数个数) 抛出异常类型?)
  2. ?表示可选部分
  3. 返回值类型和方法名(参数类型和参数数量)是必须要有的
符号意义
*0至多个任意字符
用在方法参数中表示任意多个参数<br>用在包名后面表示当前包以及其子包路径
+用在类名后面表示当前类及子类<br>用在接口后表示当前接口及其实现类

例:

  • execution(public * *(…))
    指定切入点为:任意公共方法。
  • execution(* set*(…))
    指定切入点为:任何一个以“set”开始的方法。
  • execution(* …service..*(…))
    指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

AspectJ 基于注解的 AOP 实现

1、创建maven工程

2、引入相关依赖
完整依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.why</groupId>
  6. <artifactId>ch08-Spring-aop</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <properties>
  9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  10. <maven.compiler.source>1.8</maven.compiler.source>
  11. <maven.compiler.target>1.8</maven.compiler.target>
  12. </properties>
  13. <dependencies>
  14. <!--测试单元-->
  15. <dependency>
  16. <groupId>junit</groupId>
  17. <artifactId>junit</artifactId>
  18. <version>4.11</version>
  19. <scope>test</scope>
  20. </dependency>
  21. <!--spring依赖-->
  22. <dependency>
  23. <groupId>org.springframework</groupId>
  24. <artifactId>spring-context</artifactId>
  25. <version>5.2.5.RELEASE</version>
  26. </dependency>
  27. <!--AspectJ依赖-->
  28. <dependency>
  29. <groupId>org.springframework</groupId>
  30. <artifactId>spring-aspects</artifactId>
  31. <version>5.2.5.RELEASE</version>
  32. </dependency>
  33. </dependencies>
  34. <build>
  35. <plugins>
  36. <plugin>
  37. <artifactId>maven-compiler-plugin</artifactId>
  38. <version>3.1</version>
  39. <configuration>
  40. <source>1.8</source>
  41. <target>1.8</target>
  42. </configuration>
  43. </plugin>
  44. </plugins>
  45. </build>
  46. </project>

3、定义业务接口和实现类

  1. public interface SomeService {
  2. void doSome();
  3. int doOther(int x,int y);
  4. }
  5. public class SomeServiceImpl implements SomeService {
  6. @Override
  7. public void doSome() {
  8. System.out.println("执行了doSome业务方法");
  9. }
  10. @Override
  11. public int doOther(int x, int y) {
  12. return x>y ? x : y;
  13. }
  14. }

4、定义切面类

  1. /**
  2. *@Aspect:是AspectJ框架的注解表示当前类是切面类
  3. */
  4. @Aspect
  5. public class MyAspect {
  6. @Before(value = "execution(* com.why.SomeServiceImpl.doSome(..))")
  7. public void before(){
  8. System.out.println("前置通知:在目标方法之前执行,例如输出日志");
  9. }
  10. }

5、声明目标对象切面类对象

  1. <!--声明目标类对象-->
  2. <bean id="target" class="com.why.SomeServiceImpl"></bean>
  3. <!--声明切面类对象-->
  4. <bean id="myAspect" class="com.why.MyAspect"></bean>

6、注册 AspectJ 的自动代理

  1. <!--声明自动代理生成器,创建代理-->
  2. <aop:aspectj-autoproxy/>

7、测试

  1. @Test
  2. public void test01(){
  3. String resource = "applicationContext.xml";
  4. ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
  5. SomeService target = (SomeService) ac.getBean("target");
  6. target.doSome();
  7. }

结果

  1. 前置通知:在目标方法之前执行,例如输出日志
  2. 执行了doSome业务方法
  3. Process finished with exit code 0

JoinPoint 参数

  1. @Before(value = "execution(* com.why.SomeServiceImpl.doOther(..))")
  2. public void beforeOther(JoinPoint joinPoint){
  3. /*不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。*/
  4. System.out.println("连接点的方法定义:"+joinPoint.getSignature());
  5. System.out.println("连接点方法的参数个数:"+joinPoint.getArgs().length);
  6. /*
  7. 方法参数信息
  8. Object[] args = joinPoint.getArgs();
  9. */
  10. }

调用doOther方法的执行结果

  1. 连接点的方法定义:int com.why.SomeService.doOther(int,int)
  2. 连接点方法的参数个数:2
  3. 执行了other业务方法

后置通知@AfterReturning注解的 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目
标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变
量名的。

  1. @AfterReturning(value = "execution(* com.why.SomeServiceImpl.doOther(..))",returning = "result")
  2. public void afterReturning(Object result){
  3. if (result != null) {
  4. Integer i = (Integer) result;
  5. result = i * 100;
  6. }
  7. System.out.println("后置通知:在目标方法执行后的功能增强,如事务的处理");
  8. System.out.println("较大数的100倍:"+result);
  9. }

调用doOther方法后的执行结果

  1. 执行了other业务方法
  2. 后置通知:在目标方法执行后的功能增强,如事务的处理
  3. 较大数的100倍:200
  4. Process finished with exit code 0

环绕通知@Around 方法有ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强
方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

  1. //接口方法
  2. String doFirst(String name,int age);
  3. //接口方法实现类
  4. @Override
  5. public String doFirst(String name,int age) {
  6. System.out.println("执行了doFirst业务方法");
  7. return "doFirst";
  8. }

定义切面

  1. @Around(value = "execution(* com.why.SomeServiceImpl.doFirst(..))")
  2. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  3. Object object;
  4. System.out.println("环绕通知:在目标方法之前执行,如输出日志");
  5. //执行目标方法
  6. object = pjp.proceed();
  7. System.out.println("环绕通知:在目标方法之后执行,如事务处理");
  8. System.out.println(object.toString());
  9. return object;
  10. }

异常通知@AfterThrowing注解的 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

  1. @AfterThrowing(value = "execution(public * com.why.SomeServiceImpl.doSecond(..))",throwing = "ex")
  2. public void afterThrowing(Throwable ex){
  3. /*
  4. 异常通知可以做什么?
  5. 把异常发生的时间、地点、原因记录到数据库,日志文件等等
  6. 可以在异常发生时,把异常信息通过邮件、短信发送给开发人员
  7. */
  8. System.out.println("异常通知:在目标方法抛出异常执行,异常原因:"+ex.getMessage());
  9. }
  1. 异常通知:在目标方法抛出异常执行,异常原因:/ by zero
  2. java.lang.ArithmeticException: / by zero
  3. Process finished with exit code -1

最终通知@After

无论目标方法是否抛出异常,该增强均会被执行。

  1. @After("execution(public * com.why.SomeServiceImpl.doSecond())")
  2. public void after(){
  3. System.out.println("最终通知,总是会执行");
  4. }

@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

  1. @After("myPoint()")
  2. public void after(){
  3. System.out.println("最终通知,总是会执行");
  4. }
  5. /**
  6. * @Pointcut:用来定义和管理切面点,简化切入点的定义
  7. */
  8. @Pointcut(value = "execution(public * com.why.SomeServiceImpl.doSecond())")
  9. public void myPoint(){
  10. //无需写代码
  11. }

相关文章