[springboot]
关于IOC容器(控制反转)
注册bean
(注册bean,放进IOC容器中,交给spring管理方便解耦)
@Bean : 默认类名为首字母小写,可用 Value 属性指定bean的名字
@Component : 声明bean的基础注解
@Configuration : 声明配置类
由此@Component衍生的注解:
@Controller : 标注在控制器类(Controller)上 -> 注:在springboot中,控制类上只能用这个
@Service : 标注在业务类(Service)上
@Repository : 标注在数据访问类(Dao/Mapper)上 -> 注:[少用,后续用@Mapper代替]
关于bean对象的管理
获取bean
Spring容器中提供了一些方法,可以主动从IOC容器中获取到bean对象,下面介绍3种常用方式:
-
根据name获取bean
Object getBean(String name) -
根据类型获取bean
<T> T getBean(Class<T> requiredType) -
根据name获取bean(带类型转换)
<T> T getBean(String name, Class<T> requiredType)
bean的作用域
| 作用域 | 说明 |
|---|---|
| * singleton | 容器内同名称的bean只有一个实例(单例)(默认) |
| * prototype | 每次使用该bean时会创建新的实例(非单例) |
| request | 每个请求范围内会创建新的实例(web环境中,了解) |
| session | 每个会话范围内会创建新的实例(web环境中,了解) |
| application | 每个应用范围内会创建新的实例(web环境中,了解) |
可以借助Spring中的@Scope注解来进行配置作用域
@Scope : 配置作用域(与bean配套使用)
第三方bean
- 如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到**@Bean**注解。
解决方案
方案1 : 在启动类上添加@Bean标识的方法 (不建议使用)
方案2 : 在配置类中定义@Bean标识的方法
注意:
- 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
- 如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
例如:
@Configuration //配置类 (在配置类当中对第三方bean进行集中的配置管理)
public class CommonConfig {
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader reader(DeptService deptService){
System.out.println(deptService);
return new SAXReader();
}
}
关于DI(依赖注入)
使用bean
@Autowired : 进行依赖注入(使用Bean)[常用]
注意:
如果存在多个相同类型的bean,解决方案如下:
@Primary : 在类上标注
@Autowired + @Qualifiler: 在有Autowired的地方标注
@Resource : 在依赖注入的地方标注(无Autowired),属性为name
* @Autowired 和 @Resource的区别:
@Autowired : 是spring框架提供的注解,默认是按照类型注入
@Resource : 是JDK提供的注解,默认是按照名称注入
关于自动配置
-
在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。
-
SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。
解决方案
方案一:
@ComponentScan : 扫描组件
例如 : @ComponentScan({"com.zHan.Dao","com.zHan"})
方案二:
@Import : 导入(使用@Import导入的类会被Spring加载到IOC容器中)
导入形式主要有以下几种:
- 导入普通类
- 导入配置类
- 导入ImportSelector接口实现类
- 使用第三方依赖提供的 @EnableXxxxx注解
配置文件参数的注入
@Value : @Value("${配置文件中的key}")
@ConfigurationProperties : @ConfigurationProperties(perfix = "配置参数项的前缀")
区别:
相同点 : 都是用来注入外部配置的属性的。
不同点 :
@Value注解 : 只能一个一个的进行外部属性的注入。
ConfigurationProperties : 可以批量的将外部的属性配置注入到bean对象的属性中。
Spring提供的简化方式套路:
1. 需要创建一个实现类,且实体类中的属性名和配置文件当中key的名字必须要一致。
另外,实体类当中的属性还需要提供 getter / setter方法
2. 需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象
3. 在实体类上添加`@ConfigurationProperties`注解,并通过perfix属性来指定配置参数项的前缀
关于AOP(面向切面编程)
实现步骤
-
导入依赖:在pom.xml中导入AOP的依赖
-
编写AOP程序:针对于特定方法根据业务需要进行编程
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
涉及注解
@Aspect : 表示当前类为切面类
@Pointcut : 指定切入点表达式
例如 : @Pointcut("@annotation(com.zHan.anno.Log)")
通知类型:
- * @Around (重点): 环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before : 前置通知,此注解标注的通知方法在目标方法前被执行
- @After : 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning (了解): 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing (了解): 异常后通知,此注解标注的通知方法发生异常后执行
属性:
* execution : 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配
格式 : execution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 异常])
(注意: 带 [] 的表示可以省略的部分)
# 使用通配符描述切入点:
- * : 单个独立的任意符号
- 可以通配任意返回值、包名、类名、方法名、任意类型的一个参数
- 可以通配包、类、方法名的一部分
- .. : 多个连续的任意符号
- 可以通配任意层级的包,或任意类型、任意个数的参数
/ 例如:
@Around("execution(* com.*.service.*.*(..))")
* @annotation :
实现步骤 : 1. 编写自定义注解 2.在业务类要做为连接点的方法上添加自定义注解
/ 例如 :
@Target(ElementType.METHOD) //编译时执行
@Retention(RetentionPolicy.RUNTIME) //添加在方法上
public @interface MyLog {
}
核心概念(5个)
1. 连接点:JoinPoint
可以被AOP控制的方法(暗含方法执行时的相关信息)
连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的方法。
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
-
对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
-
对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型
@Slf4j
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.itheima.anno.MyLog)")
private void pt(){}
//前置通知
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect -> before ...");
}
//后置通知
@Before("pt()")
public void after(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect -> after ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取目标类名
String name = joinPoint.getTarget().getClass().getName();
log.info("目标类名:{}",name);
//目标方法名
String methodName = joinPoint.getSignature().getName();
log.info("目标方法名:{}",methodName);
//获取方法执行时需要的参数
Object[] args = joinPoint.getArgs();
log.info("目标方法参数:{}", Arrays.toString(args));
//执行原始方法
Object object = joinPoint.proceed();
return object;
}
}
2. 通知:Advice
指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
在入门程序中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。
但是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。
3. 切入点:PointCut
匹配连接点的条件,通知仅会在切入点方法执行时被应用
在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。
在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。
假如:切入点表达式改为DeptServiceImpl.list(),此时就代表仅仅只有list这一个方法是切入点。只有list()方法在运行的时候才会应用通知。
4. 切面:Aspect
描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。 切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)
5. 目标对象:Target
通知所应用的对象 目标对象指的就是通知所应用的对象,我们就称之为目标对象。
关于请求参数
@RequestParam
@RequestParam : 用于简单参数、数组集合参数
方法形参名称与请求名称不匹配,通过该注解完成映射
属性:
defaultValue : 设置默认值
例如 : http://localhost:8080?name=zHan&age=21
@RequestParam(name="name")
(required)属性默认为true,代表请求参数必须传递
注意:如果封装list集合,必须要加
例如 : http://localhost:8080?hobby=java&hobby=python
@DataTimeFormat
@DataTimeFormat
参数 : pattern
例如 : @DataTimeFormat(pattern='yyyy-MM-dd HH:mm:ss')
@RequestBody
@DataTimeFormat : 用于接收json参数时使用
注意:json数据键名和形参属性名保持一致,定义pojo类型形参即可
@PathVariable
@PathVariable : 用于接收路径参数
例如 : http://localhost:8080/id/name
注意:
要与在@RequestMapping中的参数相对应、
顺序要一一对应
例如 : @RequestMapping("/{id}/{name}")
关于响应参数
@ResponseBody
@ResponseBody : 作用在Controller类/方法上
作用 : 将方法返回值直接响应,若返回值类型是 实体对象/集合,转json格式响应
注意:
可用@RestController代替 : 使用rest风格
过滤器Filter(*属于javaweb)
使用步骤:
- 第1步 : 定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
- 第2步 : 配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加
Filter接口的方法:
- init方法 : 过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。
- doFilter方法 (*): 这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。
- destroy方法 : 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。
涉及的注解:
@ServletComponentScan 开启Servlet组件支持。
@WebFilter : 在Filter类上添加
- 属性 : urlPatterns ---> 通过这个属性指定过滤器要拦截哪些请求
@ServletComponentScan : 在启动类上面加上一个注解,通过这个注解来开启SpringBoot项目对于Servlet组件的支持
拦截器Interceptor
使用步骤:
- 第1步 : 自定义拦截器。// 实现HandlerInterceptor接口,并重写其所有方法
- 第2步 : 注册配置拦截器。// 实现WebMvcConfigurer接口,并重写addInterceptors方法
(第2步)例如:
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}
HandlerInterceptor接口的方法:
- preHandle方法 (*): 目标资源方法执行前执行。 返回true:放行; 返回false:不放行
- postHandle方法 : 目标资源方法执行后执行
- afterCompletion方法 : 视图渲染完毕后执行,最后执行
涉及的注解:
@Component : 在自定义拦截器类上添加
@Configuration : 在配置类上添加
过滤器和拦截器的区别:
接口规范不同:
- 过滤器需要实现Filter接口
- 拦截器需要实现HandlerInterceptor接口。
拦截范围不同:
- 过滤器Filter会拦截所有的资源
- 拦截器Interceptor只会拦截Spring环境中的资源。
异常处理
使用步骤:
- 第1步 : 定义一个类,在类上加上一个注解@RestControllerAdvice,代表我们定义了一个全局异常处理器。
- 第2步 : 在全局异常处理器当中,定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
涉及的注解:
@RestControllerAdvice // 添加在异常处理器类上,表示当前类为全局异常处理器
@ExceptionHandler // 添加在方法上,指定可以捕获哪种类型的异常进行处理
例如:
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler(Exception.class) //指定能够处理的异常类型
public Result ex(Exception e){
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error("对不起,操作失败,请联系管理员");
}
}
- 当有全局异常处理器和特定异常处理器时,只执行特定异常处理器的内容
事务
@Transactional
书写位置:
- 方法 : 当前方法交给spring进行事务管理
- 类 : 当前类中所有的方法都交由spring进行事务管理
- 接口 : 接口下所有的实现类当中所有的方法都交给spring 进行事务管理
- 注意:
在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。
* 属性:
- rollbackFor : 指定出现何种异常类型回滚事务
例如 : @Transactional(rollbackFor=Exception.class)
- propagation : 指定传播行为
例如 : @Transactional(propagation = Propagation.REQUIRES_NEW)
常见的事务传播行为:
| 属性值 | 含义 |
|---|---|
| * REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
| * REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
| SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
| NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 |
| MANDATORY | 必须有事务,否则抛异常 |
| NEVER | 必须没事务,否则抛异常 |
| … |
对于这些事务传播行为,我们只需要关注以下两个就可以了:
- REQUIRED(默认值)
- REQUIRES_NEW
事务相关yml配置:
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
CORS跨域问题
-
使用@CrossOriginzhu注解(可以添加在Controller类上 或 在支持跨域的方法上)
-
使用WebMvcConfigurer的addCorsMappings方法配置(创建配置类实现WebMvcConfigurer接口)
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路径 registry.addMapping("/**") // 设置允许跨域请求的域名 .allowedOriginPatterns("*") // 是否允许cookie .allowCredentials(true) // 设置允许的请求方式 .allowedMethods("GET", "POST", "DELETE", "PUT") // 设置允许的header属性 .allowedHeaders("*") // 跨域允许时间 .maxAge(3600); } }
[lombok]
引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
@Data : 更综合代码包含(@Getter、@Setter、@ToString、@EqualsAndHashCode)
@NoArgsConstructor : 为实体类生成无参的构造方法
@AllArgsConstructor : 为实体类生成了static修饰的字段之外带有各参数的构造方法
[mybatis]
引入依赖
<!--mybatis起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
常用注解
@Insert : 新增
@Delete : 删除
@Update : 修改
@Select : 查询
@Options : 获取返回的主键,参数为 keyProperty,useGeneratedKeys为true
例如 : @Options(keyProperty="id",useGeneratedKeys=true)
@Results + @Result : 手动结果映射
* 预编译sql(可防止sql注入) : #{}
* 模糊查询中,推荐使用 : concat('%',#{},'%')
(不推荐使用,存在sql注入) : ${}
注意 : springboot1.x中,要是有@Param注解,与字段名一一对应,springboot2.x则不需要
注意:如果遇到别名和实体类属性名不一样,如果字段名与属性名符合驼峰命名规则,则配置文件须打开通过驼峰命名规则映射
例如:
mybatis.
configuration.
map-underscore-to-camel-case : true
XML映射文件
规范:
* XML映射文件的名称与Mapping接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。
* XML映射文件的namespace属性为Mapper接口全限定名一致。
* XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致。
格式:
<mapper namespace="com.zHan.mapper.EmpMapper">
<select id="list" resultType="com.itheima.pojo.Emp">
<where>
<if 条件>
</if>
</where>
</select>
</mapper>
涉及的标签:
<INsert> : 新增
<Delete> : 删除
<Update> : 修改
<Select> : 查询
其他标签:
* <if>
用于判断条件是否成立,如果条件为true,则拼接SQL。
形式 : <if test="name != null">...</if>
* <where>
where 元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的and或or
* <set>
动态地在行首插入 SET 关键字,并会删掉额外的逗号。 (用在update语句中)
* <foreach>
属性:
collection : 集合名称
0item : 集合遍历出来的元素/项
separator : 每一次遍历使用的分隔符
open : 遍历开始前拼接的片段
close : 遍历结束后拼接的片段
例如 :
<delete id="deleteBylds">
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
* <sql>: 定义可重用的 SOL 片段
* <include>: 通过属性refid,指定包含的sql片段
(两者配套使用)
例如:
<sql id="commonSelect">
select id, username, password, name, gender, image, job, entrydatedept_id, create_time, update_time from emp
</sql>
<include lefid="commonSelect"/>
注意:
除include外,都是双标签!
分页查询
* 前端传递给后端的参数
当前页码 : page
每页展示记录数 : pageSize
* 后端给前端返回的数据 及 sql语句:
获得列表数据 : List --> select * from 表名 limit 起始索引,查询返回记录数;
获得总记录数 : total --> select count(*) from 表名;
* 需要参数:
1. 起始索引=(页码 - 1)*每页展示记录数
2. 查询返回记录数 = 每页展示记录数
[maven]高级
继承
继承关系
-
概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。
-
作用:简化依赖配置、统一管理依赖
-
实现:
<parent> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <relativePath>....</relativePath> </parent>
注意:
- 在子工程中,配置了继承关系之后,坐标中的groupId是可以省略的,因为会自动继承父工程的 。
- relativePath指定父工程的pom文件的相对位置(如果不指定,将从本地仓库/远程仓库查找该工程)。
- ../ 代表的上一级目录
版本锁定(包含属性配置)
在maven中,可以在父工程的pom文件中通过 <dependencyManagement> 来统一管理依赖版本。
父工程:
<properties>
<xxx.version>...</xxx.version>
</properties>
<!--统一管理依赖版本-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>${xxx.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
子工程:
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
注意:
在父工程中所配置的
<dependencyManagement>只能统一管理依赖版本,并不会将这个依赖直接引入进来。 这点和<dependencies>是不同的。子工程要使用这个依赖,还是需要引入的,只是此时就无需指定
<version>版本号了,父工程统一管理。变更依赖版本,只需在父工程中统一变更。
面试题:<dependencyManagement> 与 <dependencies> 的区别是什么?
<dependencies>是直接依赖,在父工程配置了依赖,子工程会直接继承下来。<dependencyManagement>是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
聚合
- 聚合:将多个模块组织成一个整体,同时进行项目的构建。
- 聚合工程:一个不具有业务功能的“空”工程(有且仅有一个pom文件) 【PS:一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个】
- 作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
- 实现: 在父类中,添加如下配置,来指定当前聚合工程,需要聚合的模块:
<!--聚合其他模块-->
<modules>
<module>...</module>
<module>...</module>
<module>...</module>
</modules>
在项目中所聚合的其他模块全部都会执行 package 指令,这就是通过聚合实现项目的一键构建(一键清理clean、一键编译compile、一键测试test、一键打包package、一键安装install等)。
继承和聚合的对比
-
作用
-
聚合用于快速构建项目
-
继承用于简化依赖配置、统一管理依赖
-
-
相同点:
-
聚合与继承的pom.xml文件打包方式均为pom,通常将两种关系制作到同一个pom文件中
-
聚合与继承均属于设计型模块,并无实际的模块内容
-
-
不同点:
-
聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
-
继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
-
Management>与` 的区别是什么?**
<dependencies>是直接依赖,在父工程配置了依赖,子工程会直接继承下来。<dependencyManagement>是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
聚合
- 聚合:将多个模块组织成一个整体,同时进行项目的构建。
- 聚合工程:一个不具有业务功能的“空”工程(有且仅有一个pom文件) 【PS:一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个】
- 作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
- 实现:
在父类中,添加如下配置,来指定当前聚合工程,需要聚合的模块:
<!--聚合其他模块-->
<modules>
<module>...</module>
<module>...</module>
<module>...</module>
</modules>
在项目中所聚合的其他模块全部都会执行 package 指令,这就是通过聚合实现项目的一键构建(一键清理clean、一键编译compile、一键测试test、一键打包package、一键安装install等)。
继承和聚合的对比
-
作用
-
聚合用于快速构建项目
-
继承用于简化依赖配置、统一管理依赖
-
-
相同点:
-
聚合与继承的pom.xml文件打包方式均为pom,通常将两种关系制作到同一个pom文件中
-
聚合与继承均属于设计型模块,并无实际的模块内容
-
-
不同点:
-
聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
-
继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
-