Spring笔记

Spring框架两大核心机制(IOC、AOP)

  • IOC(控制反转)/ DI (依赖注入)
  • AOP (面向切面编程)

Spring是一个企业级的开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。

MVC:Struts2、Spring MVC

ORMapping:Hibernate、Mybatis、Spring Data

什么是控制反转:对象由调用者主动new出来。而在Spring中创建对象由IOC容器创建,再推送给调用者,整个流程完成反转,就称作控制反转。

如何使用IOC

  • 创建Maven工程,添加pom.xml依赖
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.29</version>
    </dependency>
</dependencies>
  • 创建实体类Student

    package com.study.entity;
    
    import lombok.Data;
    
    @Data
    public class Student {
        private long id;
        private String name;
        private int age;
    }
  • 传统的开发方式 手动new

  • 通过IOC创建对象,在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"
        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">

    <bean id="student" class="com.study.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="wdcp"></property>
        <property name="age" value="22"></property>
    </bean>
</beans>
  • 从IOC中获取对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);

配置文件

  • 通过配置bean标签完成对象的管理

    • id : 对象名
    • class:对象的模版类(所有交给IOC构建的对象都要有无参构造,因为Spring底层是通过反射机制创建对象的,反射机制调用的是无参构造)
  • 成员变量通过property 标签完成赋值

    • name:成员变量名
    • value:成员变量的值(基本数据类型,Spring可以直接赋值,其他类型不能通过value赋值)
    • ref:将IOC中的另外一个bean赋值给当前变量(DI 依赖注入)
    <bean id="student" class="com.study.entity.Student">
        <property name="id" value="1"></property>
        <property name="name" value="wdcp"></property>
        <property name="age" value="22"></property>
        <property name="address" ref="address"></property>
    </bean>
      
    <bean id="address" class="com.study.entity.Address">
        <property name="id" value="123"></property>
        <property name="name" value="南京路"></property>
    </bean>

IOC底层原理

  • 读取配置文件,解析XML
  • 通过反射机制实例化配置文件中所配置的所有bean。

通过运行时类获取bean

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student st = applicationContext.getBean(Student.class);
System.out.println(st);

这种方式存在问题,配置文件中一个数据类型对象只能有一个实例,因为是通过Class中获取的,Class中数据类型是一样的。

通过有参构造创建对象

  • 在实体类中创建有参函数

  • 配置文件

    <bean id="student1" class="com.study.entity.Student">
        <constructor-arg name="id" value="1"></constructor-arg>
        <constructor-arg name="name" value="xiaoming"></constructor-arg>
        <constructor-arg name="age" value="15"></constructor-arg>
        <constructor-arg name="address" ref="address"></constructor-arg>
    </bean>

给bean注入集合

<bean id="student" class="com.study.entity.Student">
    <property name="id" value="1"></property>
    <property name="name" value="www"></property>
    <property name="age" value="22"></property>
    <property name="addresses" >
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
  
</bean>
<bean id="address" class="com.study.entity.Address">
    <property name="id" value="1"></property>
    <property name="name" value="南京路"></property>
</bean>

<bean id="address2" class="com.study.entity.Address">
    <property name="id" value="2"></property>
    <property name="name" value="北京路"></property>
</bean>

scope作用域

Spring管理的bean是根据bean来生成的,表示bean的作用域,共4种,默认是singleton。

  • singleton:单例,表示通过Spring容器获取的bean是唯一的。
  • prototype:原型,表示通过Spring容器获取的bean是不同的。
  • request:请求,表示在一次HTTP请求内有效。
  • session:会话,表示在一个用户会话内有效。

request 和 session 只适用于Web项目,大多数情况下,使用单例和原型较多。

request模式在加载配置文件时就创建了xml中所有的bean。

prototype模式当业务代码获取IOC容器中的bean时,Spring才去调用无参构造创建对应的bean。

Spring的继承

与JAVA中的继承不同,JAVA是类层面的继承,子类可以继承父类的结构和信息;Spring是对象层面的继承,子对象可以继承父对象的属性值。

<bean id="student2" class="com.study.entity.Student">
    <property name="id" value="3"></property>
    <property name="name" value="lala"></property>
    <property name="age" value="19"></property>
    <property name="addresses">
        <list>
            <ref bean="address"></ref>
            <ref bean="address2"></ref>
        </list>
    </property>
</bean>

<bean id="stu" class="com.study.entity.Student" parent="student2">
    <property name="name" value="lisi"></property>
</bean>
  • 不同类型的对象某些情况也是可以继承的,需要子对象的属性包含父对象的属性。

Spring的依赖

与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖后,被依赖的bean先创建,再创建依赖的bean。如A依赖于B,则先创建B,再创建A。

<bean id="user" class="com.study.entity.User" depends-on="student"></bean>

<bean id="student" class="com.study.entity.Student"></bean>

Spring的 p 命名空间

p 命名空间是对IOC / DI的简化操作,使用 p 命名空间可以更加方便的完成bean的配置以及bean之间的依赖注入。

Spring的工厂方法

IOC通过工厂模式创建 bean 的方式有两种:

  • 静态工厂模式
  • 实例工厂方法

静态工厂方法:

package com.study.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
    private long id;
    private String name;
}
package com.study.factory;

import com.study.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class StaticCarFactory {
    private static Map<Long, Car> carMap;
    static {
        carMap = new HashMap<>();
        carMap.put(1L, new Car(1L, "大G"));
        carMap.put(2L, new Car(2L, "宝马"));
    }
    public static Car getCar(long id){
        return carMap.get(id);
    }
}
<!--配置静态工厂创建bean-->
    <bean id="car" class="com.study.factory.StaticCarFactory" factory-method="getCar">
        <constructor-arg value="1"></constructor-arg>
    </bean>

实例工厂方法:

package com.study.factory;

import com.study.entity.Car;

import java.util.HashMap;
import java.util.Map;

public class InstanceCarFactory {
    private Map<Long, Car> map;

    public InstanceCarFactory() {
        map = new HashMap<>();
        map.put(1L, new Car(1L, "大奔"));
        map.put(2L, new Car(2L, "沃尔沃"));
    }

    public Car getCar(long id){
        return map.get(id);
    }
}
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
Car car = (Car) applicationContext.getBean("car2");
System.out.println(car);
<!--实例工厂创建bean-->
<bean id="carFactory" class="com.study.factory.InstanceCarFactory"></bean>

<bean id="car2" class="com.study.entity.Car" factory-bean="carFactory" factory-method="getCar">
    <constructor-arg value="2"></constructor-arg>
</bean>

IOC自动装载(Autowire)

IOC负责创建对象,DI完成对象的依赖注入,通过配置property标签的 ref 属性完成,同时Spring提供了更加简便的依赖注入方式:自动装载,不需要手动配置property,IOC容器会自动选择 bean 完成注入。

自动装载有两种方式:

  • byName:通过属性名自动装载
<bean id="cars" class="com.study.entity.Car" p:id="1" p:name="大奔"></bean>

<bean id="person" class="com.study.entity.Person" p:id="1" p:name="wdcp" autowire="byName"></bean>
  • byType:通过属性的类型自动装载
<bean id="cars" class="com.study.entity.Car" p:id="1" p:name="大奔"></bean>

<bean id="person" class="com.study.entity.Person" p:id="1" p:name="wdcp" autowire="byType"></bean>

byType需要注意,如果同时存在两个及以上的bean时,会抛出异常。

AOP

AOP:Aspect Oriented Programming 面向切面编程

AOP的优点:

  • 降低模块的耦合度
  • 使系统更容易扩展
  • 更好的代码服用
  • 非业务代码更加集中,便于统一管理
  • 业务代码更加简洁,不掺杂其他代码影响

AOP是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定对象上的编程思想就是面向切面编程。将不同方法的同一位置抽象成一个切面对象,对该切面对象进行编程就是AOP。

如何使用?

  • 创建Maven工程,pom.xml添加
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.24</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.24</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>

</dependencies>
  • 创建一个计算器接口,创建加减乘除方法
public class CalImpl implements Cal {

    @Override
    public int add(int num1, int num2) {
        System.out.println("add方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 + num2;
        System.out.println("结果是" + result);
        return result;
    }

    @Override
    public int sub(int num1, int num2) {
        System.out.println("sub方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 - num2;
        System.out.println("结果是" + result);
        return result;
    }

    @Override
    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 * num2;
        System.out.println("结果是" + result);
        return result;
    }

    @Override
    public int div(int num1, int num2) {
        System.out.println("div方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 / num2;
        System.out.println("结果是" + result);
        return result;
    }
}

上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化,如何实现AOP?使用动态代理的方式来实现。

给业务代码找一个代理,打印日志信息的工作交给代理来做,这样的话业务代码只需关注自身业务即可。

package com.study.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MyInvocationHandler implements InvocationHandler {
  // 这不是代理类,这个类帮助我们去创建动态代理类
  // 接收委托对象
  private Object object = null;

  // 返回代理对象
  public Object bind(Object object) {
      this.object = object;
      return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 这里将业务代码和日志代码进行解耦
      System.out.println(method.getName() + "方法的参数是" + Arrays.toString(args));
      Object res = method.invoke(this.object, args);  // 业务代码
      System.out.println(method.getName() + "的结果是" + res);
      return res;
  }
}

以上是通过动态代理实现AOP的过程,不好理解,Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想实现AOP。

Spring 框架中不需要创建 InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成,Spring 框架底层会根据切面类以及目标类生成一个代理对象。

LoggerAspect

package com.study.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LoggerAspect {
    // 非业务代码
    @Before("execution(public int com.study.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        // 获取方法名
        String name = joinPoint.getSignature().getName();
        // 获取参数
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println(name + "方法的参数是" + args);
    }
}

LoggerAspect两个注解:

  • @Aspect 表示该类是切面类
  • @Component 将该类的对象注入到 IOC 容器

具体方法处添加的注解:

  • @Before 表示方法执行的具体位置和时机

CalImpl 也需要添加 @Component,交给 IOC 管理

package com.study.impl;

import com.study.utils.Cal;
import org.springframework.stereotype.Component;

@Component
public class CalImpl implements Cal {

    @Override
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }

    @Override
    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }

    @Override
    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }

    @Override
    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}

Spring.xml 也需要配置 AOP

<?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:aop="http://www.springframework.org/schema/aop"
        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">

        <!-- 自动扫描 -->
        <context:component-scan base-package="com.study"></context:component-scan>

    <!-- 使Aspect注解生效 为目标类自动生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

context:component-scancom.study 包中所有类进行扫描,如果该类同时添加了 @Component,则将该类添加到 IOC 容器中,让 IOC 管理它的对象。

aop:aspectj-autoproxy 让Spring框架结合切面类和目标类自动生成动态代理对象。

  • 切面:横切关注点被模块化的抽象对象。
  • 通知:切面对象完成的工作。(非业务代码)
  • 目标:被通知的对象,即被横切的对象。
  • 代理:切面、通知、目标混合之后的对象。
  • 连接点:通知要插入代码的具体位置。
  • 切点:AOP 通过切点定位到连接点。

原理

@Bean的作用域

最常用的有prototype 和 singleton。

singleton:IoC容器中唯一的 bean 实例。

prototype:每次获取都会创建唯一的 bean。

事物

事物传播类型 特性
REQUIRED 当前方法存在事物,子类方法加入事物,这时父子共用同一个事物,此时无论父子哪个发生回滚,整个事物都会一起回滚。即使父类捕捉了子类的异常,也会回滚。而当前方法不存在事务时,子方法新建一个事务。
REQUIRED_NEW 无论当前方法是否存在事物,子方法都新建一个事物,此时父子方法的事物是独立的,不会互相影响。但父方法需要注意子类抛出的异常,如果没有处理异常也会导致回滚。
NESTED 当前事物存在事物时,子方法加入嵌套事物执行,当父方法回滚时,子方法也跟着回滚。当子方法回滚时,父方法如果没有处理异常也会回滚。

事物失效

  1. 同一个类中没有 @Transactional 注解的方法调用有 @Transactional 注解的方法。这是因为直接调用不会经过代理,事物管理不会被触发。或者可以通过 AopContext.currentProxy() 去获取当前bean的代理对象。
  2. 只有应用在 public 方法上才生效。
  3. 底层数据库的必须支持事物。

深入理解:https://cloud.tencent.com/developer/article/2119692


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1216271933@qq.com

×

喜欢就点赞,疼爱就打赏