☰
Spring进阶
2026/10/1 9:49:36 网站建设 项目流程

1.Spring容器

定义:

组件/Bean:具有一定功能的对象(有名字,类型,对象,作用域),组件一定被交给IOC容器管理

组件注册到IOC容器中(组件本身是一个普通类,加上注解后被托管给IOC容器)

容器:管理组件

IOC:

  • 控制:资源的控制权(IOC里的组件是单例的)
  • 反转:代理

DI:

  • 依赖:组件的依赖关系
  • 注入:自动注入,自动赋值

注册注解

①@bean

用于方法级别的注解,作用是将方法返回的对象,注册到IOC容器中

组件默认的id就是方法名,默认是单实例的,bean("重命名")

bean方法所在的类必须有相关注解才能被扫描到

获取IOC容器中的组件

//1.根据组件类型获取容器中的组件(该类型组件存在且唯一,否则报错) Person person = ioc.getBean( Person.class); System.out.println(person); //2.根据组件类型获取所有组件 Map<String, Person> type=ioc.getBeansOfType(Person.class); System.out.println(type); //3.按照组件名字获取组件 Person person1=(Person) ioc.getBean("ABC"); System.out.println(person);

注册组件

@Bean("ABC") public Person zhangSan(){ Person person = new Person(); person.setName("张三"); person.setAge(18); person.setGender("男"); return person; }

②@Configuration

告诉IOC容器,这是一个配置类,确保组件的单实例化,

配置类也是容器中的组件

③@Component

标记在类上,该类会被注册到IOC容器中

注解对应层级职责(岗位描述)
@Controller表现层 (Presentation Layer)负责接收 HTTP 请求,解析参数,调用业务逻辑,最后返回响应(JSON 或 页面)。
@Service业务逻辑层 (Business Layer)负责核心业务规则、计算、事务控制。它调用 DAO 层,处理数据后返回给 Controller。
@Repository数据访问层 (Data Access Layer)负责与数据库交互,进行增删改查(CRUD)。

分层注解能起作用的前提是都在主程序所在的包里面

④ComponentScan(basepakage=“包”)

标记在主程序中,主程序扫描对应包下的注解类并注册到IOC容器中(不推荐:包范围过大要扫描很多文件)

⑤@Import(类型.class)

一般用于把外部类放在IOC容器中,标记位置随便,推荐放在专门的配置类上

⑥@Scope(“作用域”)

一般用于组件类和方法上,来说明这个组件的作用域

作用域说明应用场景
singleton(默认,大多数)

容器内同名称的bean只有一个实例(单例)

默认会在项目启动时创建对象(不管是否会被调用)

可在该类上加入@Lazy注解来延迟到调用该类时,再创建bean对象

对于无状态的bean(即不保存数据,只调用一些方法)
prototype

每次使用该bean时会创建新的实例(非单例)

在类上加入@Scope(“prototype”)注解,可实现该作用

有状态的bean
request每个请求范围内会创建新的实例(web环境中,了解)
session每个会话范围内会创建新的实例(web环境中,了解)

⑦Lazy

一般用于单实例下,放在组件上,在组件被调用时才会被创建和加载

⑧FactoryBean(接口) 了解

如果制造某些对象比较复杂

这个组件的类型是泛型中填写的类型(Car),组件的名称是工厂的名称(ByDFactory)

public class ByDFactory implements FactoryBean<Car> { // 调用该方法给容器注册一个组件 @Override public @Nullable Car getObject() throws Exception { Car car=new Car(); return car; } // 说明该组件类型 @Override public @Nullable Class<?> getObjectType() { return Car.class; } // 是单例吗 @Override public boolean isSingleton() { return true; } }

⑨Conditional(实现Condition接口的Class对象)

满足某个条件的组件才能被注册到IOC容器中

派生注解

  • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

注入注解:

①Autowired

寻找方式:1.按照类型寻找(容器里只有一个类型),名字无所谓

2.有多个,可以将变量名设置为组件名

// 有多个,可以放在集合中,集合的key是bean的id @Autowired List<Person> personList; @Autowired Map<String,Person> personMap;

②primary+qualifier(“名字”)

对于该类型有多个组件,在一个组件上加上primary注解,则被设置为默认组件,默认调用该组件(名字不会被考虑)

如果有默认组件的情况下想调用别的组件,在调用时加上qualifier注解来指定要调用的组件

③Resource

是javaee包下的,通用性更强,功能和Autowired差不多,根据名称来寻找组件

④构造器注入+Setter方法注入

对于Component类,在被放置到IOC容器时,要实现构造方法来创建完整的对象,这时候IOC容器会优先从自己容器中寻找合适的组件注入,这就是构造器注入(在创建对象时实现)官方推荐(安全)

@Repository public class UserDao { Car car; public UserDao(Car car){ this.car=car; } }

同样,通过Set方法来实现上述功能(在创建对象完成后实现),一般要加上Autowired注解,这样IOC容器会自动调用Set方法完成初始化

⑤感知接口

组件继承该接口的子接口,spring容器会自动注入这些资源

⑥@Value

Value注解主要用于将外部配置的值注入到 Spring 管理的 Bean 的字段、方法参数或构造函数参数

public class Dog { // 直接赋值 @Value("xiaogou") private String name; // 可以获取配置文件中的值 @Value("${dog.age}") private Integer age; // 直接写SPEL表达式来获取值 @Value("#{T(java.util.UUID).randomUUID().toString()}}") private String color; public Dog(){ System.out.println("Dog构造器。。。"); }

⑦PropertySource

当配置文件不是系统默认的文件,可通过该注解将这个配置文件放到容器中

//把该配置文件添加到容器中,位置无所谓 //在自己的项目下寻找配置类 @PropertySource("classpath:cat.properties") //@PropertySource("classpath*:cat.properties")在所有包寻找配置类 @Data @Component public class Cat { @Value("${cat.name:Jerry}") private String name; @Value("${cat.age:6}") private Integer age; }

⑧profile(“环境标识(可以写多个)”)

和Condition的作用相似,就是为了特定条件下使用对应组件

1.定义环境标识(如果不定义,会有默认标识default)

2.在配置类上定义所要激活的环境

组件生命周期

属性注入完成才说明初始化成功

1.@Bean(initMethod ="initUsr" ,destroyMethod = "destroyUsr")在bean注解中会有一些属性,就是在组件某个生命周期中去实现某个方法

2.3.这是两个接口,让组件去实现

4.5.这是两个注解,写在组件类中的方法上

6.具有拦截和修改功能,这是一个接口,实现类会对所有的组件进行拦截,实现类放在单独的包下

2.springAOP

静态代理:

  • 是在编码期间确定好的关系
  • 代理对象是把目标对象当做自己的属性,在实现目标对象的同时增加额外的功能
  • 静态范围太小,(可以动态代理)

动态代理:

  • 运行期间实现代理关系,本质是拦截
  • 目标对象在运行被拦截并实现对应操作,可以拦截所有对象
  • 不好编写(下面是一个例子)
  • @Test void contextLoads() { //1.定义目标对象 MathCalculator target = new MathCalculatorImpl(); //2.定义拦截器:new InvocationHandler()类似于一个拦截器,proxy是代理对象,method是目标对象方法,args是目标对象方法参数 InvocationHandler h=new InvocationHandler() { //这是匿名内部类,实现拦截器接口,拦截目标对象方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("canshu"+ Arrays.asList(args)); args[0]=1; System.out.println("改后参数"+Arrays.asList(args)); //这行代码的作用是调用目标对象方法 Object result=method.invoke(target,args); return result; } }; //3.定义代理对象:实现动态代理,getClassLoader()是得到目标对象类加载器,getInterfaces()是得到目标对象接口, //Proxy是一个静态类,newProxyInstance()是创建代理对象,里面传递参数是类加载器,接口,调用方法 MathCalculator proxy=(MathCalculator) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), h); proxy.add(1,2); }

    当把代理对象单独拿出来创建,就可以实现所有目标对象的动态代理,把记录日志摘出,实现解耦

  • 缺点:目标对象必须要有接口,否则会报错

AOP:

面向切面编程

实现步骤:
  • 导入AOP依赖(aspectj4.0版本后依赖改名了)
  • 编写切面aspect+写通知方法+切入点表达式
@Component @Aspect public class LogAspect { @Before("execution(int *(int,int))") public void logStart(){ System.out.println("日志开始记录"); } @After("execution(int *(int,int))") public void logEnd(){ System.out.println("日志结束记录"); } @AfterThrowing("execution(int *(int,int))") public void logException(){ System.out.println("日志异常抛出"); } @AfterReturning("execution(int *(int,int))") public void logReturn(){ System.out.println("日志返回"); } }
  • 测试(直接调用切入方法,jvm会自动识别并拦截)
切入点表达式:

args(方法的参数a)(如果b是a的子类,则也能匹配上)

@Args(参数的注解)

这个是关注方法上有没有注解

@PointCut(“公共的路径”)其他注解可以直接调用这个方法

public class LogAspect { @Pointcut("execution(int *(int,int))") public void pointCut(){ } @Before("pointCut()") public void logStart(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature()+"Signature"); }
方法的参数值:

前提:通知方法不知道连接点信息,且传递进的参数不能随意设置。

①Joinpoint

// result的值的传递是spring框架根据注解中的returning属性来判断的 @AfterReturning(value = "execution(int *(int,int))",returning = "result") public void logReturn(JoinPoint joinPoint, Object result){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String name=signature.getName(); System.out.println(name+"方法返回结果是:"+result); }
多切面:

类名字母越小,越在外边,可以用注解@Order(数字)来确定顺序

在获取异常时,要把异常抛出,让外边的切面也能感知到异常

细节(增强器):

增强器链:切面的所有通知方法都是增强器,他们被组织成链路放到集合中,目标方法真正执行前后,会去增强器链中执行通知方法。

AOP实现逻辑:创建代理对象----设置增强器链-----调用通知方法

源码:

56,57节(雷神ssm)

@Around
/* 环绕通知固定写法 object:返回值 proceedingJoinPoint:连接点对象 */ @Around("execution(int *(int,int))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕通知"); //得到参数,可以修改,再传入目标对象 Object[] args = joinPoint.getArgs(); Object proceed=null; try { proceed=joinPoint.proceed(args); System.out.println("返回通知"+proceed); } catch (Throwable e) { System.out.println("异常通知"+e.getMessage()); } finally { System.out.println("后置通知"); } //可以修改返回值 return proceed; } }

3.spring声明式事务

声明式:代码量小;封装太多,排错难

实现底层:

  • 事务管理器:进行事务的提交或回滚
  • 事务拦截器:管理事务,控制事务提交或回滚的时机

transactional的属性

1.timeout=#{int}s:超时,是从事务的开始到最后一个dao层方法结束的时长,超时后,事务会回滚

2.readOnly=true 只读

3.隔离级别(对于读写并发问题)

补充:

1.对于service,通常注入容器的是实现类,调用的是接口,这是为了防止实现类经常改变引起大量修改。

2.AnnotationUtils,ClassUtils,typeutils,reflectutils包中可以查询注解或类的信息(Ctrl+N查询类)

3.声明式或编程式:

  • 声明式:通过注解告诉框架自己要做什么,实现靠框架完成
  • 编程式:自己通过编程实现对应功能

4.idea中数据库连接:

idea中右侧的数据库是idea database工具,可以实现和datagrip一样的功能

Springboot中配置数据库再连接数据库是相当于idea识别这个数据库通过这个工具又连接了数据库

5.构造器注入和字段注入的区别:

字段注入是先注册组件的基本信息,再根据注入构造组件,然后再new空对象,再把组件地址赋值给空对象

1. Spring 开始处理 UserServiceImpl 的创建。
2. Spring 立即扫描 UserServiceImpl 的所有 @Autowired,发现需要 UserDao。
3. Spring 立刻递归调用 getBean(UserDao.class):
a. 创建 UserDao 之前,先扫描发现需要 DataSource。
b. 递归调用 getBean(DataSource.class):
- DataSource 没有依赖,直接 new 出来(完整对象)。
c. DataSource 造好了,回传给 UserDao。
d. 现在 UserDao 依赖齐全,调用构造器 new UserDao(DataSource) 出来(完整对象)。
4. UserDao 造好了,回传给 UserServiceImpl。
5. 现在 UserServiceImpl 的依赖齐全,调用无参构造器 new UserServiceImpl()。
6. 紧接着,Spring 通过反射将 UserDao 的地址塞进 UserServiceImpl 的空壳里。

构造器注入,直接new一个有参对象,在构造时直接把相关参数传递进去,安全

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询