# Spring
容器
控制反转(IOC)
组件:具有一定功能的对象(名字,类型,对象,作用域)
容器:管理组件(创建,获取,保存,摧毁)
组件注册
添加组件到容器:
单个组件:@Bean
- @Configuration 配置类
- @Controller 控制层
- @Service 服务层
- @Repository 持久层
- @Component 组件(其他)
批量组件:@CompoentScan(basePackages = “com.ch.tool”) //只能扫描用了相关注解进入容器的组件
@Import(“aa.class”) // 引用的jar包中的类
获取容器中的组件
ioc.getBean() //名字,类型,名字+类型
注意:如果组件名重复了(只会放一个),有限按时间顺序,然后名称。
调整组件的作用域:@Scope
- @Scope(“prototype”):非单实例
- @Scope(“singleton”):单实例(默认值)
- @Scope(“request”):同一个请求单实例
- @Scope(“session”):同一次会话单实例
注意:容器创建完成前,就把所有的单实例对象创建完成,而不会创建非单实例对象。非单实例对象在什么时候获取什么时候创建
懒加载:@Lazy // 一般用于非单实例,在获取时创建
工厂制造复杂的Bean
@Component
public class BYDFactory implements FactoryBean<Car> {
// getObject(),制造的Bean
// getObjectType(),Bean的类型
// isSingleton(),是否单实例
}
BeanFactory的地层原理
三级缓存
条件注册@Conditional
判断不同环境,加载不同的Bean
1、写两个class进行Windows和Mac操作系统的判断
//MacCondition
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断环境变量是否包含 MacOS
// 获取环境变量
Environment environment = context.getEnvironment();
//获取操作系统名称
String os = environment.getProperty("OS");
return os.contains("MacOS");
}
}
//WindowsCondition
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断环境变量是否包含 Windows
// 获取环境变量
Environment environment = context.getEnvironment();
//获取操作系统名称
String os = environment.getProperty("OS");
return os.contains("Windows");
}
}
2、在Bean中添加@Conditional(),进行筛选
@Configuration
public class PersonConfig {
// 注册自己的组件
@Conditional(MacCondition.class)
@Bean("zs")
public Preson zs(){
Preson preson = new Preson();
preson.setName("张三");
preson.setAge("20");
preson.setGender("男");
return preson;
}
@Conditional(WindowsCondition.class)
@Bean("ls")
public Preson ls(){
Preson preson = new Preson();
preson.setName("李四");
preson.setAge("18");
preson.setGender("男");
return preson;
}
}
注意:@Conditional()不仅可以用在方法上,还可以用在类上。
组件注入
依赖注入(DI)
自动装配 @Autowired
- @Autowired //自动装配,底层:调用容器的getBean方法。
- @Resource //自动装配, 底层:javaSE
面试题:@Autowired和@Resource的区别:
1、来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于(Java)JSR-250;
2、依赖查找的顺序不同:@Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询;
3、支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;
4、依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入;
5、编译器 IDEA 的提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误。
其他
-
@Qualifier(“”) // 精确指定如果容器中这样的组件存在多个,则使用@Qualifier精确指定组件名
-
@Primary // 主组件(默认组件),如果容器中这样的组件存在多,在一个Bean上添加@Primary,则其为默认组件
-
构造器注入
-
setter方法注入
-
xxxAware // 感知接口,实现其接口,直接调用相关方法。
-
@Value // 1、给基本数据类型赋值;2、取出配置文件的某个值:@Value(“${aaa}”);3、编写Spring表达式语言:@Value(“#{10*20}”)
-
@PropertySource(“classpath:cat.properties”) // 把指定的文件导入容器中
-
ResourceUtils.getFile( resourceLocation:“classpath:abc.jpg”); // 导入资源
-
@Profile(“uat”) // 多环境。当这个环境被激活的时候,才会加入如下组件。
多环境 @Profile(“uat”)
1、通过@Profile标识环境
public class DataConfiger {
// 1、定义环境标识:dev、app、test
// 2、激活环境标识
// 明确告诉spring使用哪个环境
// 不说,默认就是default环境(报错)
@Profile({
"dev","default"})
@Bean
public void dev() {
System.out.println("init");
}
@Profile("app")
@Bean
public void app() {
System.out.println("init");
}
@Profile("test")
@Bean
public void test() {
System.out.println("init");
}
}
2、在项目配置文件(application.properties)中改就行:spring.profiles.active=test
xml注册组件和创建容器
注意:其实这些注解都来自于SpringBoot。在没有SpringBoot之前,是使用xml配置文件来管理Bean的(< bean id=" " class=" ">< /bean>),使用ClassPathXmlApplicationcontext ioc = new ClassPathXmlApplicationContext( configLocation: “classpath:ioc .xml”); 来创建容器的。
组件的生命周期
- 生命周期的回调感知(不能对对象进行修改)
- BeanPostProcessor(可以对对象进行修改)
@Autowired是如何实现的?
1、专门有一个处理@Autowired注解的AutowiredAnnotationBeanPostProcessor
3、每个Bean创建以后,会调用BeanPostProcessor的postProcessBeforelnitialization 方法
3、postProcessBeforelnitialization 里面就会利用反射,得到当前Bean的所有属性,利用反射得到Bean属性上标注的所有注解,看有没有Autowired 注解
4、如果有,去容器中找到这个属性对应的组件(按类型,按名字),找到。
AOP
面向切面编程(),
日志
- 1、硬编码:(不推荐),(通用逻辑+专用逻辑,耦合太多维护困难)
- 2、静态代理: 编码时期间就决定好了代理的关系
定义:代理对象,是目标对象的接口的子类型,代理对象本身并不是目标对象,而是将目标对象作为自己的属性。
优点:同一种类型的所有对象都能代理。
缺点:代理对象与目标对象实现相同的接口,当目标对象增加方法时,代理对象也要维护。 - 3、动态代理: 运行期间才决定好了代理关系
定义:目标对象在执行期间会被动态拦截,插入指定的逻辑。
优点:可以代理所有。
缺点:不好写(强制要求,目标对象必有接口。代理的也只是接口规定的方法。)。
所以Spring为了简化动态代理,发明了AOP
概念
实现
1、导入 AOP 依赖 (在pom文件中导入依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、编写切面 Aspect (使用@Aspect标注该类为切面类,+@Component注入到ioc中)
3、编写通知方法
4、指定切入点表达式
- 何时?
【@Before @AfterRetruning @AfterThrowing @After】 - 何地【写切入表达式】?
*(“execution()”)
全写法:【public int com.atguigu.spring.aop.calculator.MathCalculator.add(int,int) throws xxxx】
省略写法:【int add (int, int)】
通配符: * :任意字符 ; … :表示多个参数,任意类型
例如:【@Before(“execution(int * (int,int))”)】
注意:只要被AOP给切了。则该对象为Spring的代理对象,代理对象为SpringCGLIB,他可以代理万物
5、JoinPoint
参数,可以用于获取对象
例如:
6、@Pointcut
用于简化重复写写入点表达式
例如:
@Pointcut("execution(int * (int,int))")
public void pointCut1() {
}
@After("pointCut1()")
public void logEnd() {
System.out.println("【切面 - 日志】结束...");
}
底层原理:
1、Spring会为每个被切面切入的组件创建代理对象。
2、代理对象中保存了切面类里面所有通知方法构成的增强器链。
3、目标方法执行时,会先去执行增强器链中拿到需要提前执行的通知方法。
增强器链: 切面中的所有通知方法其实就是增强器。他们被组织成一个链路放到集合中。目标方法真正执行前后,会去增强器链中执行哪些需要提前执行的方法。
多个切面的顺序
可以使用@Order(1)进行排序,数字越小优先级越高,就在最外层
qa
工具类
TypeUtils
Reflectionutils
AnnotationUtils 注解相关的
ClassUtils 类相关的
环绕通知
@Around 修改目标方法的参数和执行结果
固定写法:
@Component
@Aspect
public class AroundAspect {
/**
* 环绕通知的固定写法如下:
* Object: 返回值
* ProceedingJoinPoint:切入点
*/
@Around("execution(int * (int,int))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();// 获取方法参数
System.out.println("环绕通知-前置通知");
Object proceed = null;
try {
// 执行目标方法
proceed = pjp.proceed();
} catch (Throwable e) {
System.out.println("环绕通知-异常通知");
e.getMessage();
throw e; //让别人继续感知
} finally {
System.out.println("环绕通知-后置通知");
}
System.out.println("环绕通知-返回通知");
return proceed;
}
}
注意:写环绕通知的时候,必须将异常抛给下面,防止直接抛出导致下面走正常流程,不走异常。
场景使用
日志记录,事务管理,**权限检查,**性能监控(有专业的框架),异常处理,缓存管理,安全审计,自动化测试。
事务
声明式:通过注解的方式,告诉框架,我要做什么,框架会帮助我做什么。
编程式:通过代码的方式,告诉虚拟机,我要做什么,需要自己写代码实现。
使用
1、导入pom
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
2、启动类加 @EnableTransactionManagement
3、方法前加 @Transactional
参数
注意:超时时间:方法进入到最后一次DAO操作完成的时间
- value 或 transactionManager
指定事务管理器的名称 - propagation
定义了事务的传播行为,即当前方法是否需要在一个新的事务中运行,或者是否可以加入到已存在的事务中。默认值为 Propagation.REQUIRED。- REQUIRED:如果当前存在事务,则加入该事务;如果没有,则创建一个新的事务
- SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则挂起当前事务。
- NESTED: 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
- NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,则暂停当前事务。
- NEVER: 以非事务方式执行,如果当前存在事务则抛出异常。
- isolation
设置事务的隔离级别,这影响了事务内的操作如何与其他事务中的操作相互作用。默认值为 Isolation.DEFAULT- READ_UNCOMMITTED: 允许读取尚未提交的数据变更。
- READ_COMMITTED: 只允许读取已经提交的数据。
- REPEATABLE_READ: 对同一数据的多次读取结果一致,即使有其他事务对数据进行了修改并提交。
- SERIALIZABLE: 最高的隔离级别,完全阻止了脏读、不可重复读和幻读的情况。
- timeout
定义了事务必须在多长时间内完成(以秒为单位) - readOnly
指示事务是否只读。 - rollbackFor
指定哪些异常触发事务回滚。 - noRollbackFor
指定哪些异常不会触发事务回滚。
事务默认回滚机制:运行时异常回滚,编译时异常不回滚。
注意: mysql默认级别是REPEATABLE_READ,而Oracle是READ_COMMITTED。
底层原理
1、事务管