Javaweb后端

发布时间 2023-10-16 11:33:37作者: chenyaaang

Javaweb后端

Maven

1.1 Apache软件基金会

www.apache.org/index.html#projects-list

**1.2 ** 依赖范围

image-20230713200529539

HTTP

2.1 概述

规定了数据传输的规则

2.2 特点

  • 基于TCP
  • 请求-响应模型
  • 无状态 - > 速度快,但多次请求间不能共享

2.3 请求协议

  • 请求行
  • 请求头

image-20230714002634678

  • 请求体:POST请求,存放请求参数

2.4 (补充)GET和POST区别

2.4.1 数据传输方式:

  • GET:GET请求将数据附加在URL的末尾,作为查询参数传输。这些参数会显示在浏览器的地址栏中,因此对于传输敏感信息不太安全。GET请求使用URL长度限制(通常为2048个字符),因此传输的数据量有限
  • POST:POST请求将数据包含在请求的消息体中,而不会显示在URL中。由于数据不会直接显示在URL上,POST请求对于传输敏感信息更加安全。POST请求没有URL长度限制,可以传输更大量的数据。

2.4.2 数据语义:

  • GET:GET请求用于获取资源,它是一种幂等操作,多次发送相同的GET请求不会对服务器资源产生副作用。GET请求应该只用于读取数据,而不应该用于对服务器进行修改。
  • POST:POST请求用于向服务器提交数据,可能会对服务器资源进行修改或产生副作用。POST请求是非幂等的,多次发送相同的POST请求可能会对服务器产生不同的结果。

2.4.3 缓存:

  • GET:GET请求可以被浏览器缓存,可以通过缓存机制减少对服务器的请求,提高性能。但是,缓存的数据可能会被其他人查看或访问。
  • POST:POST请求默认情况下不会被缓存,每次发送POST请求都会向服务器请求最新的数据。

总结来说,GET适合用于获取资源,数据量较小,且不涉及敏感信息的传输。而POST适合用于向服务器提交数据,数据量较大,或者涉及敏感信息的传输

2.5 响应协议

  • 响应行

image-20230714004248703

image-20230714004518241

  • 响应头

image-20230714005518729

  • 响应体,存放响应数据

请求响应

请求

  • 简单参数
    • 定义方法形参,请求参数与形参变量名一致
    • 不一致可通过@RequestParam手动映射
//post http://localhost:8080/method12?id=1&name=小小  
@PostMapping("/method12")
    public ApiResponse method12(@RequestParam(value = "id",required = true) int id, @RequestParam(value = "name",required = false)String name){
        String s = id + "" + name;
        System.out.println(s);
        return ApiResponse.ok().data(s);
    }
  • 数组集合参数

    • 数组:请求参数名与数组名一致,直接封装
    • 集合:请求参数名与集合名一致,@RequestParam绑定关系
  • 日期参数

    • @DataTimeFormat(pattern = "yyyy-MM-dd")
  • JSON参数:@RequestBody

    @PostMapping("/method4")
        public ApiResponse method4(@RequestBody Dog dog){
            System.out.println(dog.toString());
            return ApiResponse.ok().data(dog);
        }
    
  • 路径参数

    • @PathVariable
  • @RestController = @Controller(用于标记一个类为处理HTTP请求的控制器) + @ResponseBody(用于标记方法返回值将直接作为响应体返回给客户端;返回数据格式,如JSON、XML等)

(补充)xml文件

  • xml解析代码实现

    //创建解析器
    SAXReader saxReader = new SAXReader();
    //获取Document 对象
    Document document = saxReader.read(new FileInputStream("itheima-xml\\students.xml"));
    //获取根节点对象
    Element rootElement = document.getRootElement();
    //从根节点来查找其他的节点元素
    List<Element>elements = rootElement.elements("student");
    
    for (Element studentElement: elements) {
        //获取属性
        Attribute attribute = studentElement.attribute("id");
        // 获取id属性的值
        String id = attribute.getValue();
        // 获取name元素
        Element nameElements = studentElement.element("name");
        //获取name元素对应的内容
        String name = nameElements.getText();
        //获取age元素
        Element ageElements = studentElement.element("age");
        //获取age元素对应的内容
        String age = ageElements.getText();
    }
    
  • 动态地获取文件路径

    String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
    

分层解耦

三层架构

由上到下依次是controller(接受页面传过来的参数传给service处理)、service(对一个或多个dao封装成服务)、dao(mapper)层(对应数据库增删改查)。

详细参考:https://blog.csdn.net/qq_26866883/article/details/116886614

IOC/DI入门

  1. Service层及Dao的实现类,交给IOC容器管理,成为IOC容器中的bean - > 在类前加@Component
  2. 为Controller及Service注入运行时依赖的对象 - > 在变量前加@Autowaired(运行时,IOC容器会提供该类型的bean对象,并赋值给该对象)
  3. 作用:层与层之间解耦

IoC 控制反转

  • 注解:

    • @Component 不属于以下三类时用
    • @Controller 标注在控制器类型上
    • @Service 标注在业务类型上
    • @Repository 标注在数据访问类上(与mybatis整合,用得少)
  • @ComponentScan({"com.example","com.itheima"}) 扫描指定包

    • SpringBootApplication默认扫描当前包及其子包

DI 依赖注入

  • @Autowaired默认按照类型进行,如果存在多个相同类型的bean
    • @Primary 优先级,让当前bean生效
    • @Qualifier("xx"):xx为bean的名字(默认为类名,首字母小写)
    • @Resource:去掉@Autowaired,改为@Resource(name = "xx"),xx为bean的名字

MySQL

事务

开启事务:start transaction;/begin;
提交事务:commit;
(若部分报错,则)回滚事务:rollback;
  • 四大特性:原子性、一致性、隔离性、持久性

索引

  • 结构:B+ 树,所有的数据都存储在叶子结点,叶子结点间有双向链表

  • 语法

    create [unique] index 索引名 on 表名(字段名,...);
    show index from 表名; 
    drop index 索引名 on 表名;
    

分页查询

  • 分页查询语法

    -- 参数1:起始索引(从0开始)= (页码-1)*每页展示的记录数
    -- 参数2:查询返回记录数
    select * from emp limit 0,5
    

MyBatis

快速入门

  1. 引入MyBatis依赖

  2. 配置MyBatis数据库连接信息

    • 驱动类名称;url;用户名;密码
  3. 创建User实体类

  4. 创建mapper接口

    • @Mapper 在运行时会自动生成该接口的实现类对象,并且将该对象交给IOC容器管理
    • @Select("sql语句")
    @Mapper
    public interface UserMapper{
        @Select("select * from user")
        public List<User> list();
    }
    

数据库连接池

Lombok

  • @Data
  • @NoArgConstructor 无参构造
  • @AllArgsConstructor 全参构造

增删改查

删除

  • // #{id} 被?替代,采用预编译sql
    @Delete("delete from emp where id = #{id};")
    
  • // ${id} 直接拼接,有sql注入的风险
    @Delete("delete from emp where id = &{id};")
    

新增

  • 需要传入太多数据时 可以传入对象,注意#{}中的内容要和对象里的字段(驼峰命名)一致,而不是和表中的字段(下划线命名)一致

  • 主键返回:在insert注解上方再加一个注解

    @Options(keyProperty = "id", userGeneratedKeys = true)
    

查询

  • 如果 实体类属性名 和 数据库表查询返回的字段名 一致,mybatis会自动封装;不一致则不能。

    • 方法一:给字段起别名

      @Select("select dept_id deptId, create_time createTime from emp where id = #{id}")
      
    • 方法二:采用@Result,Results注解

      @Results({
          //前者为表中的字段名,后者为类中的属性名
          @Result(colum = " ",property = " ")
      })
      
    • 方法三:(配置文件)开启mybatis的驼峰命名自动映射开关

    #开启mybatis驼峰命名自动映射开关
    mybatis.configuration.map-underscore-to-camel-case=true
    

条件查询

  • '%张%' 应该用 '%${name}%'而不是'%#{name}%',因为 ? 不能在 ’ ‘ 内
  • 问题:不安全,sql注入等
  • 解决:concat字符串拼接函数 写成concat('%', #{name}, '%')

xml映射文件

  • xml映射文件的名称与mapper接口名称一致,并且将xml映射文件和mapper接口防治在相同包下(相同包名)
    • 多级目录不能用.分隔,要用/分隔
  • xml文件中namespace属性与mapper接口全限定名一致
  • xml映射文件中sql语句的id与mapper接口中的方法名一致,并保持返回类型一致(resultType是单条记录所封装的类型)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
    <select id="list" resultType="com.itheima.pojo.Emp">
        select * from emp where name like concat ('%',#{name},'%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc
    </select>
</mapper>

动态sql

  • <if>标签:条件判断,如果成立则拼接
  • <where>标签:动态地去掉多余的and,or等,保证sql语句的准确性
//动态查询
<select id="list" resultType="com.itheima.pojo.Emp">
        select *
        from emp
        <where>
          <if test="name!=null">
              name like concat('%', #{name}, '%')
          </if>
          <if test="gender!=null">
              and gender = #{gender}
          </if>
          <if test="begin != null and end != null">
              and entrydate between #{begin} and #{end}
          </if>
        </where>
        order by update_time desc
    </select>
  • 批量删除

     void deleteByIds(List<Integer> ids);
    
    <delete id="deleteByIds">
            delete from emp where id in
                <foreach collection="ids" item="id" separator="," open="(" close=")">
                    #{id}
                </foreach>
        </delete>
    
  • sql & include:提高代码的复用性

    id自己取
    <sql id = "aaa">
        里面写可复用的代码
    </sql>
    需要这段代码的地方直接用以下代替
    <include refid = "aaa"/>
    

案例 部门管理

  • Controller层优化:将类中的公共路径用@RequestMapping("depts") 注解提到类前
  • @RequestParam注解:属性defaultValue可以来设置参数的默认值

分页查询

  • 请求参数:页码,每页展示记录数

  • 响应结果:总记录数、结果列表

  • PageHelper插件

    @Select("select * from emp")
    List<Emp> list();
    
    @Override
    public PageBean page(Integer page, Integer pageSize){
    	PageHelper.startPage(page,pageSize);
        
        List<Emp> empList = empMapper.list();
        Page<Emp> p = (Page<Emp>) empList;
        
        PageBean pageBean = new PageBean(p.getTotal(),p.getResult());
        return pageBean;
    }
    
  • 如果需要使用动态sql,只需将@Select("select * from emp")删掉然后在配置文件里根据要求写即可

文件上传

  • MutipartFile :注意该表单项的名称需要与方法形参的名称保持一致

    • 若不一致,可用@RequestParam("image") 括号中表示的是表单项中的名称
  • 本地存储

     MultipartFile image;
    //获取原始文件名
            String originalFilename = image.getOriginalFilename();
    //取后缀
            int index = originalFilename.lastIndexOf(".");
            String extname = originalFilename.substring(index);
    //构造唯一的文件名 --uuid(通用唯一识别码)
            String newFileName = UUID.randomUUID().toString()+ extname;
    //存储在磁盘目录中
            image.transferTo(new File("D:\\images\\"+ newFilename));
    
  • 上传大文件:在properties中配置

    # 配置单个文件最大上传大小
    spring.servlet.multipart.max-file-size=10MB
    #配置单个请求最大上传大小(一次请求可以上传多个文件)
    spring.servlet.multipart.max-request-size=100MB
    

阿里云OSS

配置文件

参数配置化

  • 用配置文件代替硬编码
  • @Value("${}") 赋值

不同种类的配置文件

  • properties 结构不清晰

    • 键值对
  • yml / yaml 推荐

    • xx: xx ,不同层级缩进不同
    • 大小写敏感
    • 数值前面必须有空格,作为分隔符
    • 缩进时不能用TAB要用空格(idea会自动转换)
    • # 表注释
    #对象/Map集合
    user:
    	name: zhangsan
    	age: 18
    
    #数组/List/Set集合
    hobby:
     - java
     - game
     - sport
    
  • xml 臃肿

    • 标签
  • @ConfigurationProperties(prefix = "前缀") 可以批量将外部的属性配置注入到bean对象的属性中

    • 添加依赖(可选)
        <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
            </dependency>
    

接上案例

登录校验

会话技术

  • 会话:一次会话中可以包含多次请求和响应
  • 会话跟踪:维护浏览器状态,服务器识别是否来自同一浏览器,以便在同一次会话的多次请求间共享数据
    • 客户端会话跟踪Cookie:Http协议中支持的技术;移动端无法使用Cookie;不安全,用户可以禁用;Cookie不能跨域;
    • 服务器端会话跟踪Session:存储在服务器端,安全;服务器集群环境下无法使用;Cookie的缺点;
    • 令牌技术(目前主流):支持PC端、移动端;解决集群环境下的认证问题;减轻服务器端存储的压力

JWT令牌

  • 官网:https://jwt.io

  • 依赖

     <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
    
  • 组成:

    • Header 头,记录令牌类型,签名算法
    • Payload 有效载荷,携带一些自定义信息
    • Signature 签名,将header、payload、制定秘钥通过指定签名算法计算而来
  • 生成jwt令牌

    public void testGenJwt(){
            Map<String, Object> claims = new HashMap<>();
            claims.put("id",1);
            claims.put("name","tom");
            String jwt = Jwts.builder()
                    .signWith(SignatureAlgorithm.HS256, "xiaoyang")//签名算法
                    .setClaims(claims)//自定义内容(载荷)
                    .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为一个小时
                    .compact();
            System.out.println(jwt);
        }
    
  • 将生成的令牌放入官网(也有在线解码工具)可以自动解析令牌携带的信息

  • 解析(一般会创建一个新的工具类,存放生成令牌和解析令牌的方法)

     @Test
        public void testParseJwt(){
            Claims claims = Jwts.parser()
                    .setSigningKey("xiaoyang")           		.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY5MzE0NDQ0OH0.yvP02RKkPftX8y7N4Ks9izQoBafelA0-z9xwT6DWkLc")
                    .getBody();
            System.out.println(claims);
        }
    

拦截

过滤器Filter

  • 拦截对资源的请求,实现特殊功能
  • 在启动类加 @ServletComponentScan ,开启对Servlet组件的支持
  • 标准接口:Filter(import javax.servlet.*)

过滤器链

  • 多个过滤器拦截
  • 优先级按字母排序

登录校验过滤器

  • 字符串转换

      <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version></version>
            </dependency>
    
  • 登录

    @Slf4j
    @WebFilter(urlPatterns = "/*")
    public class LoginCheckFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            HttpServletResponse resp = (HttpServletResponse) servletResponse;
    
            String url = req.getRequestURL().toString();
            log.info("请求的url:{}", url);
    
            if (url.contains("login")) {
                log.info("登录操作 放行");
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }
            String jwt = req.getHeader("token");
    
            if (!StringUtils.hasLength(jwt)) {
                log.info("请求头token为空");
    
                Result error = Result.error("NOT_LOGIN");
                String notLogin = JSONObject.toJSONString(error);
                resp.getWriter().write(notLogin);
                return;
            }
            try {
                JwtUtils.parseJWT(jwt);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("解析失败");
                Result error = Result.error("NOT_LOGIN");
                String notLogin = JSONObject.toJSONString(error);
                resp.getWriter().write(notLogin);
                return;
            }
            log.info("令牌合法,放行");
            filterChain.doFilter(servletRequest,servletResponse);
    
        }
    }
    

拦截(二)

拦截器Interceptor

  • Spring框架中提供的

  • 标准接口

  • 定义拦截器

    @Component
    public class LoginCheckInterceptor implements HandlerInterceptor {
        //目标资源方法运行前运行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
        //目标资源方法运行后运行
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
        //视图方法渲染完毕后运行
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  • 注册配置拦截器

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private LoginCheckInterceptor loginCheckInterceptor;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    //.addPathPatterns("")表示要拦截的资源
    //.excludePathPatterns()表示不拦截某些资源       
            registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
        }
    }
    
  • 常用拦截路径

    • /* 一级路径
    • /** 任意级路径

异常处理

  • 全局异常处理器
//该注解中包括@ResponseBody,会将返回值转为json格式 
@RestControllerAdvice
public class GlobalExceptionHander{
    @ExceptionHandler(Exception.class)//捕获所有异常
    public Result ex(Exception ex){
        ex.printStackTrace();//输出异常的堆栈信息
        return Result.error("对不起,请联系管理员");
    }
}

事务管理

  • @Transactional

    • 位置:service层的方法上、类上、接口上
    • 默认情况下,只有RuntimeException才回滚异常
  • rollbackFor:控制出现何种异常类型,回滚事务

    • eg. @Transactional(rollbackFor = Exception.class)
  • propagation:控制事务的传播行为,默认为REQUIRED(需要事务,有则加入无则新建)

    • REQUIRES_NEW:每次都开启一个新的事物
  • 开启事务管理日志

    #spring事务管理日志
    logging:
      level:
        org.springframework.jdbc.support.JdbcTransactionManager: debug
    

AOP

  • 面相特定方法编程

  • 运行顺序:类名

    • @Order(数字) 数字越小越先执行
    @Slf4j
    @Component
    @Aspect
    public class timeAspect {
    
        @Around("execution(* com.itheima.service.*.*(..))")//切入点表达式
        public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
    
            long begin = System.currentTimeMillis();
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
    
            log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);
        }
    }
    

相关概念

  • 连接点:JoinPoint 可以被AOP控制的方法
  • 通知:Advice 被提取出的重复逻辑
  • 切入点:PointCut 匹配连接点的条件
  • 目标对象:Target 通知所应用的对象

通知类型

  • @Around 环绕
    • 只有它需要自己调用 Object result = joinPoint.proceed() 来让原始方法执行
    • 返回值必须指定为Object,来接收原始方法的返回值
  • @Before 前置通知
  • @After 后置通知
  • @AfterReturning 返回后通知
  • @AfterThrowing 异常后通知

切入点表达式

  • 抽取(复用)
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt(){};
  • execution

    • execution(访问修饰符? 返回值 包名.类名.? 方法名(方法参数) throws 异常?) 其中带?的可以省略
    • 通配符:* ..
    • 尽量基于接口描述,增强拓展性
  • @annotation(注解全类名)

    • 新建一个注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyLog {
    }
    

连接点

  • JoinPoint 获取连接点信息
    • @Around 通知只能用 ProcrrdingJoinPoint

实践:记录增删改日志

SpringBoot

配置优先级

  • 命令行参数 > Java系统属性 > properties > yml > yaml

Bean的管理

bean的获取

@Autowired
private ApplicationContext applicationContext;
  • 根据名称(默认为首字母小写)
  • 根据bean的类型
  • 根据bean的名称及类型

bean的作用域

  • singleton 默认
  • prototype 每次创建新的实例
  • @Scope()
  • @Lazy() 延迟(到第一次使用的时候)初始化

第三方Bean的管理

  • @Bean 将方法的返回值交给IOC容器管理

    @Bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
    
  • 一般通过@Configuration 声明一个配置类,几种分类配置

  • 在第三方bean中实现注入:直接写在方法形参中即可

SpringBoot原理

起步依赖

自动配置

  • @ComponentScan({"com.example","com.itheima"}) 扫描指定包

  • @Import({mySelector.class}) 导入

  • @Enablexxxx注解 封装Import注解

  • @ConditionalOnClass 判断环境中是否有对应的字节码文件

  • @ConditionalOnMissingBean 判断有没有对应的bean

  • @ConditionalOnProperty 判断是否有对应的属性和值

自定义starter

总结

image-20230831234117231

Maven高级

分模块设计

  • eg. 把实体类、工具类分别单独用模块存放,需要的时候引入依赖

继承与聚合

继承关系

  • 将公共依赖写在父工程中,子工程可以直接继承父工程的配置信息

  • 只能单继承

    image-20230901000723500

版本锁定

  • 在父工程中用<dependencyManagement>统一管理版本,子工程无须再指定版本

  • 自定义属性

    image-20230901001810112

聚合

  • 原:先install再package

  • 聚合工程:一个不具有业务功能的工程(一般为父工程)

    image-20230901002318526

私服

  • 架设在局域网内的仓库服务
  • 仓库
    • release 发行版本
    • snapshot 快照版本
    • central 从中央仓库下载的
  • 配置过程详见文档

maven私服配置(from黑马案例)_大鱼吃大鱼的博客-CSDN博客