JavaWeb第二天
tlias案例实践
- 如果请求路径都包含同一个
前缀
,可以将其单独提取,在类外注解
- 使用
@RequestParam(defaultValue = "1")
注解参数可为形参设置默认值
/**
* 分页查询
* @param page 页号
* @param pageSize 页记录数
* @return
*/
@GetMapping("/emps")
public Result list(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize){
PageBean pageBean = empService.list(page,pageSize);
return Result.success(pageBean);
}
- 在类上使用注解
@Slf4j
可以直接调用log对象来进行日志记录
log.info("hello");
log.info("hello {},{}", value1, value2); //通过{}占位符输出变量
- 使用pagehelper插件进行分页查询
<!--pageHelper分页插件坐标-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
//Mapper层
/**
* 分页查询emp表记录并返回
* @return
*/
@Select("select * from emp")
List<Emp> list();
//service层
@Override
public PageBean list(Integer page, Integer pageSize) {
/*使用pageHelper插件实现*/
// 1.初始化PageHelper api, 传入页号和每页记录数
PageHelper.startPage(page, pageSize);
// 2.进行正常sql查询, 并将empList封装到Page对象,pagehelper插件会自动进行分页操作
List<Emp> empList = empMapper.list();
Page<Emp> ps = (Page<Emp>) empList;
// 3.获取分页查询结果并返回
return new PageBean(ps.getTotal(), ps.getResult());
}
- 文件上传
- 前端文件上传
<!-- 前端上传文件是通过form表单来提交的,要上传文件,有三个必选项:
1.请求方式method="post"
2.编码方式enctype="multipart/form-data"
3.<input type="file" name="image">设置类型为"file"
-->
<form action="/upload" method="post" enctype="multipart/form-data">
姓名: <input type="text" name="username"><br>
年龄: <input type="text" name="age"><br>
头像: <input type="file" name="image"><br>
<input type="submit" value="提交">
</form>
- 服务端接收文件(MultipartFile对象)
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image){ //接收file类型文件时,使用MultipartFile接口来接收
log.info("文件上传:{},{},{}",username,age,image);
return Result.success();
}
}
- 文件保存到本地(不推荐)
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws IOException {
log.info("文件上传:{},{},{}",username,age,image);
String originalFilename = image.getOriginalFilename(); //获取源文件名字(带扩展名)
String extname = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(".")); //拼接uuid+源文件扩展名
image.transferTo(new File("D:\\"+extname)); //将文件转存到本地指定路径
return Result.success();
}
- 文件保存到阿里云OSS
文件保存步骤:
- 参数配置化
将一些服务的配置参数,以
key=value
的形式配置在application.properties
配置文件中,然后在使用该配置参数的类中声明处,为每个参数变量添加一个注解@Value("${key}")
,在${}
中添加key名,这样在运行时会自动将配置文件中的值注入给当前变量
/*1. 配置文件*/
# 阿里云OSS默认参数配置信息
aliyun.oss.endpoint=https://oss-cn-shenzhen.aliyuncs.com
aliyun.oss.accessKeyId=LTAI5tDGy4TmiwW56WRuZzBa
aliyun.oss.accessKeySecret=xtIOICx7ryxBbed8g8Lmr9alsvGbLY
aliyun.oss.bucketName=sping-code
/*2. 类中变量声明处*/
@Value("${aliyun.oss.endpoint}")
private String endpoint ;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId ;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret ;
@Value("${aliyun.oss.bucketName}")
private String bucketName ;
- yml格式配置文件(推荐)
yml
格式配置文件相对于xml
和properties
更加简洁,层次结构更加分明,在springboot中添加yml文件需命名为application.yml
- yml基本数据类型配置
#配置对象/Map
user:
name: zs
age: 18
address: shenzhen
#配置数组/List/Set
hobby:
- java
- c
- python
- c++
ConfigurationProperties
注解
@ConfigurationProperties(prefix = "") 注解用于批量注入
配置文件
中的key-value
,
prefix
值为配置属性的统一前缀,使用方法为:
- 添加一个实体类,类中属性声明为与prefix.key同名属性
- 在类上添加@Data、@Component和@ConfigurationProperties(prefix = "")注解,目的是提供get/set方法,提供IOC容器管理,以及注入批量同级
key
的value
值- 在需要
value
值时,调用bean对象,通过getter
获取对应同名的属性值即可
/*声明同名属性类*/
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
/*在别处获取value*/
public class example{
...
@Autowired
private AliOSSProperties aliOSSProperties;
public String upload(MultipartFile file) throws IOException {
String endpoint = aliOSSProperties.getEndpoint();
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String bucketName = aliOSSProperties.getBucketName();
...
}
...
}
- 配置文件优先级
登录校验
1, Cookie技术——存储在客户端
优点:
- HTTP协议中支持的技术,由浏览器自动解析和携带
缺点:
- 移动端无法使用Cookie
- 用户可自定义禁用Cookie
- 不可跨域(跨域:协议/IP/端口不同)
2, Session技术——存储在服务端
优点:
- 存储在服务端,安全
缺点:
- 服务器集群环境无法直接使用Session
- Cookie的所有缺点,因为其底层是基于Cookie的
3, 令牌Token技术
JWT(JSON Web Token)令牌
定义了一种简洁的,自包含的格式,用于在通信双方以JSON数据格式安全地传递信息。由于有数字签名存在,这些信息是可靠的
base64:
基于64个打印字符(A-Z、a-z、0-9 + /)来表示二进制数据的编码方式
- JWT组成
- Header(请求头)
- Payload(有效载荷)
- Signature(签名)
- JWT令牌的生成和校验
- 引入JWT依赖:
<!--jwt令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
- 生成一个jwt令牌:
@Test public void testGenJwt(){ Map<String, Object> claims = new HashMap<>(); claims.put("id", 1); claims.put("name","tom"); /*生成一个jwt令牌*/ String jwt = Jwts.builder() .setClaims(claims) //自定义载荷 .signWith(SignatureAlgorithm.HS256, "poemcode") //签名算法和秘钥 .setExpiration(new Date(System.currentTimeMillis() + 10*1000)) //有效期 .compact(); System.out.println(jwt); }
- jwt令牌校验:
/* 校验失败通常有两种原因: 1. JWT令牌被篡改 2. JWT令牌过期 */ @Test public void testParseJwt(){ Claims claims = Jwts.parser() .setSigningKey("poemcode") //指定签名秘钥 .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcxNDI5NTkzMH0.8bvfhvXFAYxQAJ-1QQ7MPZnhiCSwXmd8LBPBZ2R4S7E") //解析令牌 .getBody(); //获取载荷数据 System.out.println(claims); }
4, 过滤器Filter
Filter是JavaWeb三大(Servlet、Filter、Listener)组件之一
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的处理,通常为登录校验、统一编码处理、敏感字符等
定义过滤器
定义一个过滤器只需要两步:
- 创建一个类,实现标准的
Filter
接口,同时重写init(), doFilter(), destroy()
三个方法,init和destroy
可以不重写,Filter
接口有默认实现,doFilter
方法就是我们拦截请求或响应时的处理方法- 在过滤器上添加
@WebFilter(urlPatterns = "")
注解,urlPatterns 指要拦截的请求路径;同时,由于Filter是javaweb的三大组件,并不属于springboot,因此还需在springboot启动类上添加一个@ServletComponentScan
注解来开启Servlet组件支持注:@WebFilter不能与@Component注解共用,不然会导致Spring加载两次,导致默认拦截所有路径
@WebFilter(urlPatterns = "/*") public class DemoFilter implements Filter { @Override //初始化方法, 只调用一次 public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init 初始化方法执行了"); } @Override //拦截到请求之后调用, 调用多次 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("拦截到了请求...放行前逻辑"); //放行,调用chain对象 chain.doFilter(request,response); System.out.println("拦截到了请求...放行后逻辑"); } @Override //销毁方法, 只调用一次 public void destroy() { System.out.println("destroy 销毁方法执行了"); } }
- 过滤器执行流程
请求——>放行前操作——>放行——>下一个过滤器/资源(如果是最后一个过滤器)——>放行后操作
Filter拦截路径
过滤器链
一个Web应用中,可以创建多个过滤器,多个过滤器就形成一条有顺序的过滤器链
顺序:注解配置的Filter,默认按照类名进行优先级排序
5、拦截器interceptor
6、全局异常处理器
定义全局异常处理器:
- 需要创建一个类并且在类上添加
@RestControllerAdvice
注解- 定义一个方法,形参为
要捕获的异常
,接着为方法添加一个注解@ExceptionHandler(异常类型.class)
,这样就可以自动捕获全局的异常了
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result catchEx(Exception e){
e.printStackTrace();
return Result.error("操作失败,信息有误!");
}
}
7、Spring事务管理
事务进阶——事务属性
- 1- rollbackFor
@Transactional
默认只有RunTimeException
异常才会执行回滚,使用rollbackFor
属性可以指定要执行回滚的异常类型@Transactional(rollbackFor = Exception.class) //todo
- 2- propagation
事务传播行为:当前事务在被另一个事务调用时,这个事务方法应该如何进行事务控制
AOP——面向切面编程/面向特定方法编程
AOP,全称Aspect Oriented Programming,是一种编程思想
动态代理技术就是面向切面编程最主流的实现,SpringAOP技术底层就是基于动态代理技术,就是对特定的
类/接口/方法
进行编程,从而在方法执行的前后分别执行特定的操作
SpringAOP快速入门
- 1、在
pom.xml
中导入AOP的依赖
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 2、创建一个AOP类
@Component //将当前类加入IOC容器
@Aspect //指明当前类是一个AOP类
public class TimeAspect {
}
- 3、新增一个面向方法编程的方法,用来在原始方法执行前后添加一些逻辑处理代码
@Component
@Slf4j
@Aspect
/*
统计service层方法执行的时间
*/
public class TimeAspect {
@Around("execution(* com.codepoem.service.*.*(..))") //Around注解用来标识要执行哪些方法
//ProceedingJoinPoint--类型的形参用来封装要执行的"原始方法"
Object runTime(ProceedingJoinPoint joinPoint) throws Throwable {
//原始方法调用前
long begin = System.currentTimeMillis();
//执行原始方法
Object result = joinPoint.proceed(); //proceed()返回原始方法的返回值,类型为Object
//原始方法调用后
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature() + "执行时间为:" + (end - begin) + "ms");
return result;
}
}
AOP的核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指要对接入点执行的共性的操作,最终体现为一个方法
- 切入点:PointCut,匹配连接点的条件,通知仅在切入点匹配连接点时执行
- 切面:Aspect,描述切入点与通知的对应关系(切面 = 切入点 + 通知)
- 目标对象:Target,通知所应用的对象
通知的类型
- 提取公共切入点
切入点表达式
问号处可以省略
- execution
- @annotation
@annotation()
中传入匹配注解的全类名
连接点
可以获取目标方法运行时的相关信息
bean的管理
单例bean的获取
默认情况下,Spring项目启动时,会把bean都创建好放在IOC容器中(只针对默认的单例非延迟加载的bean),如果想要主动获取这些bean,可以通过
IOC容器对象
对象的方法来获取
- 上下文对象
@Autowired
private ApplicationContext applicationContext;
- 通过
name
获取bean
Object getBean(String name)
- 通过
类型
获取bean
<T> T getBean(Class<T> requiredType)
- 通过
name
和类型
获取bean
<T> T getBean(String name, Class<T> requiredType)
bean的作用域和延迟加载
第三方bean
如果要管理的bean对象来自第三方,使用
@Component及其衍生注解
声明bean的还需要指定扫描,这时能使用@Bean
注解,bean注解作用于方法上,通过声明一个返回第三方类对象的方法 + @Bean注解的方式就可以将第三方类加入IOC容器管理中
@Configuration //声明一个配置类,专门管理Bean
public class CommonConfig{
/**
* 默认下,@Bean管理的第三方bean对象名字为'方法名'
* 如果该第三方bean要注入其他bean,直接在bean定义方法中设置形参,容器会根据类型'自动装配'
*/
@Bean
public TestClass testClass(DeptController dept){
return new TestClass(); //返回第三方bean对象
}
}
- Component及其衍生注解和Bean的使用场景
项目中定义的,使用@Component及其衍生注解
项目中引用第三方的,使用@Bean注解
Springboot原理
一、起步依赖
基于maven的
依赖传递
特性,Springboot的起步依赖就变得更加简洁了
二、自动配置(面试高频)
Springboot的
自动配置
就是当Spring容器启动后,自动将一些配置类
和bean
对象存入到IOC容器中,不用我们再去手动声明,极大简化了开发
方案一:使用@ComponentScan扫描组件 (不推荐)
当引入第三方依赖时,即使第三方依赖声明了@Component等注解,在当前项目仍不会加入IOC容器,因为Spring的扫描组件并没有去扫描第三方依赖的包,因此需要显示的声明@ComponentScan注解来扫描第三方包和当前项目包
//在springboot启动类上添加@ComponentScan注解
@ComponentScan({"com.path1","com.path2"})
@SpringBootApplication
public class SpringbootTliasQuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTliasQuickstartApplication.class, args);
}
}
方案二:使用@Import注解导入
方案一太过繁琐,并且占用资源高。使用@Import导入的类会被Spring加载到IOC容器中,一般声明在
启动类
上
导入方式主要有4种:
- 导入普通类
@Import({SimpleClass.class, ...})
- 导入配置类
原理是通过
配置类+@Bean
的方式声明多个第三方bean对象,然后用@Import导入,IOC容器会自动将该配置类下的所用Bean都引入
@Import({ConfigClass.class, ...})
- 导入
ImportSelector
接口实现类
ImportSelector接口定义了一个返回值为
String[]
的selectImports
方法,通过该方法来返回我们所要引入的第三方类全类名
,左右与前面两种类似
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{UserDaoImpl2.class.getName()};
}
}
- - - - - - - - - - - - - - - - - - - - - - - - - - 分 隔 线 - - - - - - - - - - - - - - - - - - - - - - - -
@Import({MyImportSelector.class, ...})
@EnableXxxx
注解,封装@Import注解——(Springboot默认实现方式)
由第三方定义@EnableXxxx注解,在其中封装
@Import
注解导入要成为bean的类。如果我们要导入这些bean,只需在类上声明该@EnableXxxx
注解
Bean的条件装配
自动配置原理
自定义starter
Web后端开发主要内容
Maven高级
一、分模块设计与开发
将项目按照功能分成若干个子模块,方便项目的维护、拓展,也方便模块间相互调用、资源共享
二、Maven的继承与聚合
继承
1.概念:
继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承,子工程会继承父工程的依赖
2.作用:
简化依赖配置,统一依赖管理
3.实现
<parent></parent>
标签4.打包方式
- 继承结构
- 继承关系实现
<!------------------------------------------父工程pom.xml------------------------------------------->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!--父工程的相对路径,默认从本地仓库或远程仓库查找-->
</parent>
<groupId>com.myproj</groupId>
<artifactId>proj-parent</artifactId>
<version>1.1.1</version>
<packaging>pom</packaging> <!--指定父工程打包方式为pom-->
<dependencies>
<!--将子工程公共的依赖提取到父工程,若子工程与父工程配置了不同版本的同一依赖,默认以子工程为准-->
</dependencies>
<!------------------------------------------子工程pom.xml------------------------------------------->
<parent>
<groupId>com.myproj</groupId>
<artifactId>proj-parent</artifactId>
<version>1.1.1</version>
<relativePath>"父工程的相对路径"</relativePath> <!--父工程的相对路径-->
</parent>
<groupId>com.myproj</groupId> <!--默认子工程会继承父工程声明依赖的groupId,可以不用再声明-->
<artifactId>proj-child1</artifactId>
<version>0.0.1</version>
三、Maven的版本锁定
在Maven特性中,可以在父工程的pom.xml文件中对所有子工程进行
依赖
的版本管理,使用<dependencyManagement>
标签
<dependencyManagement>
和<dependencies>
的区别
-
<dependencies>
是直接依赖,如果直接配置的是<dependencies>
,子工程会自动继承父工程的依赖 -
<dependencyManagement>
是统一管理依赖版本,使用时用<dependencyManagement>
来包裹<dependencies>
,这样该<dependencies>
中配置的依赖不会直接被子工程继承,还需子工程自己引入,但无需指定版本,统一使用父工程中的版本- version管理方式1
<dependencyManagement> <dependencies> <dependency> <groupId>xxxx</groupId> <artifactId>xxxx</artifactId> <version>1.1.1</version> </dependency> </dependencies> </dependencyManagement>
- version管理方式2
<!--自定义属性--> <properties> <xxxx.version>1.1.1</xxxx.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>xxxx</groupId> <artifactId>xxxx</artifactId> <version>${xxxx.version}</version> <!--读取属性值${xxxx.version}--> </dependency> </dependencies> </dependencyManagement>
四、Maven聚合
使用聚合可以实现一键构建项目,一键打包,通常在父工程的pom.xml中添加
<modules>
和<module>
标签来聚合其他模块
<modules>
<module>聚合模块的相对路径</module>
</modules>