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