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; public class Student { private long id; private String name; private int age; }
- 传统的开发方式 手动new 
- 通过IOC创建对象,在XML配置文件中添加需要管理的对象 
<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;
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 {
    
    public int add(int num1, int num2) {
        System.out.println("add方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 + num2;
        System.out.println("结果是" + result);
        return result;
    }
    
    public int sub(int num1, int num2) {
        System.out.println("sub方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 - num2;
        System.out.println("结果是" + result);
        return result;
    }
    
    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数是[" + num1 +"," + num2+"]" );
        int result = num1 * num2;
        System.out.println("结果是" + result);
        return result;
    }
    
    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);
  }
  
  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;
public class LoggerAspect {
    // 非业务代码
    
    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;
public class CalImpl implements Cal {
    
    public int add(int num1, int num2) {
        int result = num1 + num2;
        return result;
    }
    
    public int sub(int num1, int num2) {
        int result = num1 - num2;
        return result;
    }
    
    public int mul(int num1, int num2) {
        int result = num1 * num2;
        return result;
    }
    
    public int div(int num1, int num2) {
        int result = num1 / num2;
        return result;
    }
}Spring.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/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-scan 将 com.study 包中所有类进行扫描,如果该类同时添加了 @Component,则将该类添加到 IOC 容器中,让 IOC 管理它的对象。
aop:aspectj-autoproxy 让Spring框架结合切面类和目标类自动生成动态代理对象。
- 切面:横切关注点被模块化的抽象对象。
- 通知:切面对象完成的工作。(非业务代码)
- 目标:被通知的对象,即被横切的对象。
- 代理:切面、通知、目标混合之后的对象。
- 连接点:通知要插入代码的具体位置。
- 切点:AOP 通过切点定位到连接点。
原理
@Bean的作用域
最常用的有prototype 和 singleton。
singleton:IoC容器中唯一的 bean 实例。
prototype:每次获取都会创建唯一的 bean。
事物
| 事物传播类型 | 特性 | 
|---|---|
| REQUIRED | 当前方法存在事物,子类方法加入事物,这时父子共用同一个事物,此时无论父子哪个发生回滚,整个事物都会一起回滚。即使父类捕捉了子类的异常,也会回滚。而当前方法不存在事务时,子方法新建一个事务。 | 
| REQUIRED_NEW | 无论当前方法是否存在事物,子方法都新建一个事物,此时父子方法的事物是独立的,不会互相影响。但父方法需要注意子类抛出的异常,如果没有处理异常也会导致回滚。 | 
| NESTED | 当前事物存在事物时,子方法加入嵌套事物执行,当父方法回滚时,子方法也跟着回滚。当子方法回滚时,父方法如果没有处理异常也会回滚。 | 
事物失效
- 同一个类中没有 @Transactional 注解的方法调用有 @Transactional 注解的方法。这是因为直接调用不会经过代理,事物管理不会被触发。或者可以通过 AopContext.currentProxy() 去获取当前bean的代理对象。
- 只有应用在 public 方法上才生效。
- 底层数据库的必须支持事物。
深入理解:https://cloud.tencent.com/developer/article/2119692
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1216271933@qq.com
 
            