Spring 框架两大核心机制(IoC、AOP)
IoC(控制反转)/ DI(依赖注入)
AOP(面向切面编程)
Spring 是一个企业级开发框架,是软件设计层面 的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
MVC:Struts2、Spring MVC
ORMapping(对象关系映射):Hibernate、MyBatis、Spring Data
什么是控制反转?
传统开发中,对象是由调用者主动new出来的,但在Spring框架中不再由调用者创建对象,而是交给IoC容器创建,再推送给调用者。整个流程完成反转,所以是控制反转。
如何使用 IoC
view code <?xml version="1.0" encoding="UTF-8"?>
<project xmlns ="http://maven.apache.org/POM/4.0.0"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion > 4.0.0</modelVersion >
<groupId > com.oeong</groupId >
<artifactId > aispringioc</artifactId >
<version > 1.0-SNAPSHOT</version >
<dependencies >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-context</artifactId >
<version > 5.0.11.RELEASE</version >
</dependency >
</dependencies >
</project >
view code package com.oeong.entity;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
view code Student student = new Student ();
student.setId(1L );
student.setName("张三" );
student.setAge(22 );
System.out.println(student);
通过 IoC 创建对象,在配置文件中添加需要管理的对象,XML 格式的配置文件,文件名可以自定义。
id可自定义,相当于别名
view code <?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:p ="http://www.springframework.org/schema/p"
xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
" >
<bean id ="student" class ="com.oeong.entity.Student" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="张三" > </property >
<property name ="age" value ="22" > </property >
</bean >
</beans >
view code
ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" );
Student student = (Student) applicationContext.getBean("student" );
System.out.println(student);
配置文件
通过配置 bean
标签来完成对象的管理。
对象的成员变量通过 property
标签完成赋值。
name
:成员变量名。
value
:成员变量值(基本数据类型,String 可以直接赋值,如果是其他引用类型,不能通过 value 赋值)
ref
:将 IoC 中的另外一个 bean 赋给当前的成员变量(DI)
view code <bean id ="student" class ="com.oeong.entity.Student" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="张三" > </property >
<property name ="age" value ="22" > </property >
<property name ="address" ref ="address" > </property >
</bean >
<bean id ="address" class ="com.oeong.entity.Address" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="科技路" > </property >
</bean >
IoC 底层原理
读取配置文件,解析 XML。
通过反射机制实例化配置文件中所配置所有的 bean。
view code package com.oeong.ioc;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map<String,Object> ioc = new HashMap <String, Object>();
public ClassPathXmlApplicationContext (String path) {
try {
SAXReader reader = new SAXReader ();
Document document = reader.read("./src/main/resources/" +path);
Element root = document.getRootElement();
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()){
Element element = iterator.next();
String id = element.attributeValue("id" );
String className = element.attributeValue("class" );
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Iterator<Element> beanIter = element.elementIterator();
while (beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name" );
String valueStr = property.attributeValue("value" );
String ref = property.attributeValue("ref" );
if (ref == null ){
String methodName = "set" +name.substring(0 ,1 ).toUpperCase()+name.substring(1 );
Field field = clazz.getDeclaredField(name);
Method method = clazz.getDeclaredMethod(methodName,field.getType());
Object value = null ;
if (field.getType().getName() == "long" ){
value = Long.parseLong(valueStr);
}
if (field.getType().getName() == "java.lang.String" ){
value = valueStr;
}
if (field.getType().getName() == "int" ){
value = Integer.parseInt(valueStr);
}
method.invoke(object,value);
}
ioc.put(id,object);
}
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
} catch (NoSuchMethodException e){
e.printStackTrace();
} catch (InstantiationException e){
e.printStackTrace();
} catch (IllegalAccessException e){
e.printStackTrace();
} catch (InvocationTargetException e){
e.printStackTrace();
} catch (NoSuchFieldException e){
e.printStackTrace();
}
}
public Object getBean (String id) {
return ioc.get(id);
}
}
通过运行时类获取 bean
view code ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" );
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的 bean。
通过有参构造创建 bean
view code <bean id ="student3" class ="com.oeong.entity.Student" >
<constructor-arg name ="id" value ="3" > </constructor-arg >
<constructor-arg name ="name" value ="小明" > </constructor-arg >
<constructor-arg name ="age" value ="18" > </constructor-arg >
<constructor-arg name ="address" ref ="address" > </constructor-arg >
</bean >
view code <bean id ="student3" class ="com.oeong.entity.Student" >
<constructor-arg index ="0" value ="3" > </constructor-arg >
<constructor-arg index ="2" value ="18" > </constructor-arg >
<constructor-arg index ="1" value ="小明" > </constructor-arg >
<constructor-arg index ="3" ref ="address" > </constructor-arg >
</bean >
给 bean 注入集合
List<address> addresses
view code <bean id ="student" class ="com.oeong.entity.Student" >
<property name ="id" value ="2" > </property >
<property name ="name" value ="李四" > </property >
<property name ="age" value ="33" > </property >
<property name ="addresses" >
<list >
<ref bean ="address" > </ref >
<ref bean ="address2" > </ref >
</list >
</property >
</bean >
<bean id ="address" class ="com.oeong.entity.Address" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="科技路" > </property >
</bean >
<bean id ="address2" class ="com.oeong.entity.Address" >
<property name ="id" value ="2" > </property >
<property name ="name" value ="高新区" > </property >
</bean >
scope 作用域
Spring 管理的 bean 是根据 scope 来生成的,表示 bean 的作用域,共4种,默认值是 singleton。
singleton:单例,表示通过 IoC 容器获取的 bean 是唯一的。getbean取到的对象地址相等==
prototype:原型,表示通过 IoC 容器获取的 bean 是不同的。
request:请求,表示在一次 HTTP 请求内有效。
session:回话,表示在一个用户会话内有效。
request 和 session 只适用于 Web 项目,大多数情况下,使用单例和原型较多。
singleton 模式无论业务代码是否获取 IoC 容器中的 bean,Spring 在加载 spring.xml 时就会创建 bean。即ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml")
时调用无参构造
prototype 模式当业务代码获取 IoC 容器中的 bean 时,Spring 才去调用无参构造创建对应的 bean。即getBean
时调用无参构造
singleton不管有无用到都会创建,prototype只会在用到时创建bean。
Spring 的继承
与 Java 的继承不同,Java 是类
层面的继承,子类可以继承父类的内部结构信息;Spring 是对象
层面的继承,子对象可以继承父对象的属性值。
parent="student2"
view code <bean id ="student2" class ="com.oeong.entity.Student" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="张三" > </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.oeong.entity.Address" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="科技路" > </property >
</bean >
<bean id ="address2" class ="com.oeong.entity.Address" >
<property name ="id" value ="2" > </property >
<property name ="name" value ="高新区" > </property >
</bean >
<bean id ="stu" class ="com.oeong.entity.Student" parent ="student2" >
<property name ="name" value ="李四" > </property >
</bean >
Spring 的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性 ,同时可以在此基础上添加其他的属性。
Spring 的依赖
与继承类似,依赖也是描述 bean 和 bean 之间的一种关系,配置依赖之后,被依赖的 bean 一定先创建,再创建依赖的 bean,A 依赖于 B,先创建 B,再创建 A。
depends-on="user"
view code <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
" >
<bean id ="student" class ="com.oeong.entity.Student" depends-on ="user" > </bean >
<bean id ="user" class ="com.oeong.entity.User" > </bean >
</beans >
Spring 的 p 命名空间
p 命名空间是对 IoC / DI 的简化操作,使用 p 命名空间可以更加方便的完成 bean 的配置以及 bean 之间的依赖注入。p=propety
view code <?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:p ="http://www.springframework.org/schema/p"
xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
" >
<bean id ="student" class ="com.oeong.entity.Student" p:id ="1" p:name ="张三" p:age ="22" p:address-ref ="address" > </bean >
<bean id ="address" class ="com.oeong.entity.Address" p:id ="2" p:name ="科技路" > </bean >
</beans >
Spring 的工厂方法
IoC 通过工厂模式创建 bean 的方式有两种:
静态工厂方法,不需要实例化,程序开始就加载到内存
view code package com.oeong.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private long id;
private String name;
}
view code package com.oeong.factory;
import com.oeong.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class StaticCarFactory {
private static Map<Long, Car> carMap;
static {
carMap = new HashMap <Long, Car>();
carMap.put(1L ,new Car (1L ,"宝马" ));
carMap.put(2L ,new Car (2L ,"奔驰" ));
}
public static Car getCar (long id) {
return carMap.get(id);
}
}
view code
<bean id ="car" class ="com.oeong.factory.StaticCarFactory" factory-method ="getCar" >
<constructor-arg value ="2" > </constructor-arg >
</bean >
实例工厂方法
view code package com.oeong.factory;
import com.oeong.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class InstanceCarFactory {
private Map<Long, Car> carMap;
public InstanceCarFactory () {
carMap = new HashMap <Long, Car>();
carMap.put(1L ,new Car (1L ,"宝马" ));
carMap.put(2L ,new Car (2L ,"奔驰" ));
}
public Car getCar (long id) {
return carMap.get(id);
}
}
view code
<bean id ="carFactory" class ="com.oeong.factory.InstanceCarFactory" > </bean >
<bean id ="car2" factory-bean ="carFactory" factory-method ="getCar" >
<constructor-arg value ="1" > </constructor-arg >
</bean >
IoC 自动装载(Autowire)
IoC 负责创建对象,DI 负责完成对象的依赖注入,通过配置 property 标签的 ref 属性来完成,同时 Spring 提供了另外一种更加简便的依赖注入方式:自动装载,不需要手动配置 property,IoC 容器会自动选择 bean 完成注入。
自动装载有两种方式:
byName:通过属性名自动装载
byType:通过属性的数据类型自动装载
byName
查找属性名和id名是否匹配,id不会重复
view code <bean id ="cars" class ="com.oeong.entity.Car" >
<property name ="id" value ="1" > </property >
<property name ="name" value ="宝马" > </property >
</bean >
<bean id ="person" class ="com.oeong.entity.Person" autowire ="byName" >
<property name ="id" value ="11" > </property >
<property name ="name" value ="张三" > </property >
</bean >
byType
view code <bean id ="car" class ="com.oeong.entity.Car" >
<property name ="id" value ="2" > </property >
<property name ="name" value ="奔驰" > </property >
</bean >
<bean id ="person" class ="com.oeong.entity.Person" autowire ="byType" >
<property name ="id" value ="11" > </property >
<property name ="name" value ="张三" > </property >
</bean >
byType 需要注意,如果同时存在两个及以上的符合条件的 bean 时,自动装载会抛出异常。
AOP
AOP:Aspect Oriented Programming 面向切面编程。
AOP 的优点:
低耦合 ,降低模块之间的耦合度。
使系统更容易扩展。
更好的代码复用 。
非业务代码更加集中,不分散,便于统一管理。
业务代码更加简洁存粹,不参杂其他代码的影响。
AOP 是对面向对象编程OOP的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP。
如何使用?
view code <dependencies >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-context</artifactId >
<version > 5.0.11.RELEASE</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-aop</artifactId >
<version > 5.0.11.RELEASE</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-aspects</artifactId >
<version > 5.0.11.RELEASE</version >
</dependency >
</dependencies >
view code package com.oeong.utils;
public interface Cal {
public int add (int num1,int num2) ;
public int sub (int num1,int num2) ;
public int mul (int num1,int num2) ;
public int div (int num1,int num2) ;
}
view code package com.oeong.utils.impl;
import com.oeong.utils.Cal;
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("add方法的结果是" +result);
return result;
}
public int sub (int num1, int num2) {
System.out.println("sub方法的参数是[" +num1+"," +num2+"]" );
int result = num1-num2;
System.out.println("sub方法的结果是" +result);
return result;
}
public int mul (int num1, int num2) {
System.out.println("mul方法的参数是[" +num1+"," +num2+"]" );
int result = num1*num2;
System.out.println("mul方法的结果是" +result);
return result;
}
public int div (int num1, int num2) {
System.out.println("div方法的参数是[" +num1+"," +num2+"]" );
int result = num1/num2;
System.out.println("div方法的结果是" +result);
return result;
}
}
上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用 AOP 可以进行优化,如何来实现 AOP?使用动态代理 的方 式来实现。
给业务代码找一个代理,打印日志信息的工作交个代理来做,这样的话业务代码就只需要关注自身的业务即可。
view code package com.oeong.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 result = method.invoke(this .object,args);
System.out.println(method.getName()+"的结果是" +result);
return result;
}
}
以上是通过动态代理实现 AOP 的过程,比较复杂,不好理解,Spring 框架对 AOP 进行了封装,使用 Spring 框架可以用面向对象的思想来实现 AOP。
Spring 框架中不需要创建 InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可,Spring 框架底层会自动根据切面类以及目标类生成一个代理对象。
LoggerAspect
view code package com.oeong.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggerAspect {
@Before(value = "execution(public int com.oeong.utils.impl.CalImpl.*(..))")
public void before (JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"方法的参数是:" + args);
}
@After(value = "execution(public int com.oeong.utils.impl.CalImpl.*(..))")
public void after (JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行完毕" );
}
@AfterReturning(value = "execution(public int com.oeong.utils.impl.CalImpl.*(..))",returning = "result")
public void afterReturning (JoinPoint joinPoint,Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法的结果是" +result);
}
@AfterThrowing(value = "execution(public int com.oeong.utils.impl.CalImpl.*(..))",throwing = "exception")
public void afterThrowing (JoinPoint joinPoint,Exception exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法抛出异常:" +exception);
}
}
LoggerAspect 类定义处添加的两个注解:
@Aspect
:表示该类是切面类。
@Component
:将该类的对象注入到 IoC 容器。
具体方法处添加的注解:
@Before
:表示方法执行的具体位置和时机。
@Before
@After
@AfterReturning
@AfterThrowing
CalImpl 也需要添加 @Component
,交给 IoC 容器来管理。
view code package com.oeong.utils.impl;
import com.oeong.utils.Cal;
import org.springframework.stereotype.Component;
@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。
view code <?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"
xmlns:p ="http://www.springframework.org/schema/p"
xsi:schemaLocation ="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
" >
<context:component-scan base-package ="com.oeong" > </context:component-scan >
<aop:aspectj-autoproxy > </aop:aspectj-autoproxy >
</beans >
context:component-scan
将 com.oeong
包中的所有类进行扫描,如果该类同时添加了 @Component
,则将该类扫描到 IoC 容器中,即 IoC 管理它的对象。
aop:aspectj-autoproxy
让 Spring 框架结合切面类和目标类自动生成动态代理对象。
view code
ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("spring.xml" );
Cal proxy = (Cal) applicationContext.getBean("CalImpl" );
proxy.add(1 , 1 );
切面:横切关注点被模块化的抽象对象。
通知:切面对象完成的工作。
目标:被通知的对象,即被横切的对象。
代理:切面、通知、目标混合之后的对象。
连接点:通知要插入业务代码的具体位置。
切点:AOP 通过切点定位到连接点。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!