首页
复制
搜索
前进
后退
重载网页
和我当邻居
给我留言吧
首页
壁纸
影院
统计
关于
友链
留言
Search
1
面向对象核心
28 阅读
2
Java枚举类
26 阅读
3
Lambda表达式与Stream
23 阅读
4
Java常用API与类
20 阅读
5
集合框架主要集合类及其用法
20 阅读
随笔
JAVA
Mysql
JavaWeb
Tools
登录
Search
标签搜索
集合框架
fnm
枚举
数组
基础语法
面向对象
Comparator
Comparable
Cloneable
内部类
普通成员内部类
普通局部内部类
静态成员内部类
静态成员代码块
Throwable
异常类
密封类
代码块
普通成员代码块
Mathlei
阿简
累计撰写
34
篇文章
累计收到
0
条评论
首页
栏目
随笔
JAVA
Mysql
JavaWeb
Tools
页面
壁纸
影院
统计
关于
友链
留言
搜索到
34
篇与
的结果
2024-09-28
Nginx环境部署
Ubuntu/Debian 上安装 Nginx# 更新包索引 sudo apt update # 安装 Nginx sudo apt install nginx # 启动 Nginx sudo systemctl start nginx # 设置开机自启 sudo systemctl enable nginx # 检查 Nginx 状态 sudo systemctl status nginx2. 在 CentOS 上安装 Nginx# 安装 EPEL 仓库 sudo yum install epel-release # 安装 Nginx sudo yum install nginx # 启动 Nginx sudo systemctl start nginx # 设置开机自启 sudo systemctl enable nginx # 检查 Nginx 状态 sudo systemctl status nginxRocky Linux 上安装 Nginx# 安装 EPEL 仓库 sudo dnf install epel-release # 安装 Nginx sudo dnf install nginx # 启动 Nginx sudo systemctl start nginx # 设置开机自启 sudo systemctl enable nginx # 检查 Nginx 状态 sudo systemctl status nginx验证安装访问 http://localhost 来验证 Nginx 是否成功运行。应该能看到 Nginx 的默认欢迎页面。要检查 Nginx 版本、重载和重启,可以使用以下命令:检查版本nginx -v重载 Nginxsudo systemctl reload nginx重启 Nginxsudo systemctl restart nginx重载会应用配置文件的更改,而重启会停止再启动 Nginx。
2024年09月28日
3 阅读
0 评论
0 点赞
2024-09-27
MyBatis-Plus
MyBatis-PlusMyBatis-Plus 是 MyBatis 的增强工具,可以简化开发过程,提供 CRUD 操作和其他常用功能的实现。MyBatis-Plus 极大地简化了 MyBatis 的使用,提供了丰富的功能和便利的操作。引入依赖在 pom.xml 中添加 MyBatis-Plus 的依赖:<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency>配置 MyBatis-Plus在 application.yml 或 application.properties 中进行配置:mybatis-plus: global-config: db-config: id-type: auto # 主键策略 configuration: map-underscore-to-camel-case: true # 下划线转驼峰实体类创建实体类时,使用 @TableName 注解指定表名,使用 @TableId 注解指定主键。import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @TableName("user") public class User { @TableId private Long id; private String name; private Integer age; // getters and setters }Mapper 接口创建 Mapper 接口,继承 BaseMapper<T>。import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper<User> { }Service 层创建 Service 层,使用 @Service 注解,并注入 Mapper。import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class UserService extends ServiceImpl<UserMapper, User> { // 自定义方法 }常用操作对于 CRUD 操作,查询使用 getById 和 list 方法,插入使用 save 方法,更新使用 updateById,删除使用 removeById。条件构造器使用 QueryWrapper 和 UpdateWrapper 进行条件查询和更新。import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.gt("age", 18); List<User> users = userService.list(queryWrapper);分页查询使用 Page<T> 和 PageHelper 进行分页。import com.baomidou.mybatisplus.extension.plugins.pagination.Page; Page<User> page = new Page<>(1, 10); // 第1页,每页10条 Page<User> userPage = userService.page(page);代码生成器MyBatis-Plus 提供代码生成器,可以快速生成实体类、Mapper、Service 等。import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.*; AutoGenerator generator = new AutoGenerator(); GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir("D://code"); globalConfig.setAuthor("author"); generator.setGlobalConfig(globalConfig); DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("password"); generator.setDataSource(dataSourceConfig); PackageConfig packageConfig = new PackageConfig(); packageConfig.setParent("com.example"); generator.setPackageInfo(packageConfig); generator.execute();性能分析MyBatis-Plus 支持性能分析,添加配置可以在控制台输出 SQL 语句和执行时间。mybatis-plus: configuration: performance-interceptor: max-time: 1000 # 超过 1000ms 记录逻辑删除使用 @TableLogic 注解实现逻辑删除。import com.baomidou.mybatisplus.annotation.TableLogic; public class User { @TableId private Long id; @TableLogic private Integer deleted; // 逻辑删除标志 }
2024年09月27日
1 阅读
0 评论
0 点赞
2024-09-11
MyBatis 框架
MyBatis一个优秀的持久层(Persistence Layer)框架,支持自定义 SQL、存储过程及高级映射。它主要用于 Java 应用程序中简化对数据库的操作。轻量级:MyBatis 比较轻量,核心功能集中在 SQL 映射上,不会强制要求全自动化的 ORM(Object Relational Mapping)。可控性:程序员可以完全掌控 SQL 的执行,适合需要定制复杂 SQL 语句的场景。灵活性:支持自定义 SQL、存储过程和高级映射,适合大多数数据库操作需求。相比于 Hibernate 等全自动 ORM 框架,MyBatis 将 SQL 写入 XML 文件中或直接使用注解来编写 SQL,允许更大的灵活性和控制力。Hello World 示例为了演示 MyBatis 的基本用法,以下是一个简单的 "Hello World" 示例,展示如何进行数据库操作。基本步骤Dao 接口与 XML 配置文件:每个 Dao 接口(Data Access Object)都有一个对应的 XML 实现文件。XML 文件用于定义 SQL 查询语句,并映射到接口的方法。自动生成的代理对象:MyBatis 会自动为 Dao 接口生成实现类,创建代理对象来执行 SQL 语句。XML 文件配置:namespace: 必须与 Dao 接口的全类名一致,以便 MyBatis 能够将接口方法与 SQL 语句关联。select、insert、update、delete 标签:用于定义相应的 SQL 操作。CRUD 标签的 id: 必须与 Dao 接口的方法名一致,便于映射。示例代码Dao 接口 (UserMapper.java):public interface UserMapper { // 根据用户ID查询用户 User getUserById(int id); // 插入新用户 void insertUser(User user); }XML 配置文件 (UserMapper.xml):<mapper namespace="com.example.mapper.UserMapper"> <!-- 查询用户 --> <select id="getUserById" resultType="com.example.model.User"> SELECT * FROM users WHERE id = #{id} </select> <!-- 插入新用户 --> <insert id="insertUser"> INSERT INTO users (name, age) VALUES (#{name}, #{age}) </insert> </mapper>CRUD 操作MyBatis 提供了丰富的 CRUD 操作(增删改查)功能,通过 Mapper 接口和 XML 文件中的 CRUD 标签来实现。1 定义 Mapper 接口在接口中定义方法,例如增删改查方法。public interface UserMapper { // 查询用户 User getUserById(int id); // 插入用户 void insertUser(User user); // 更新用户 void updateUser(User user); // 删除用户 void deleteUser(int id); }2 定义 CRUD 标签在 XML 文件中使用 select、insert、update 和 delete 标签分别表示对应的 SQL 操作。<mapper namespace="com.example.mapper.UserMapper"> <select id="getUserById" resultType="com.example.model.User"> SELECT * FROM users WHERE id = #{id} </select> <insert id="insertUser"> INSERT INTO users (name, age) VALUES (#{name}, #{age}) </insert> <update id="updateUser"> UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id} </update> <delete id="deleteUser"> DELETE FROM users WHERE id = #{id} </delete> </mapper>自增 ID如果数据库中的某些表具有自增 ID(auto-increment ID),MyBatis 也可以进行相关配置。XML 配置在插入操作中,配置 useGeneratedKeys 和 keyProperty 来自动获取生成的主键。<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (name, age) VALUES (#{name}, #{age}) </insert>MyBatis 参数传递1. #{} 和 ${} 的区别MyBatis 提供了两种方式来传递参数:#{} 和 ${},它们的区别在于底层使用的方式和安全性。#{}: 使用 PreparedStatement 方式,SQL 预编译后设置参数,避免了 SQL 注入风险。推荐优先使用。${}: 使用 Statement 方式,直接拼接参数,有可能造成 SQL 注入攻击。应尽量避免使用。示例安全的参数传递(推荐):<select id="getUserByName" resultType="com.example.model.User"> SELECT * FROM users WHERE name = #{name} </select>存在安全风险的参数传递:<select id="getUserByNameUnsafe" resultType="com.example.model.User"> SELECT * FROM users WHERE name = ${name} </select>2. 参数取值方式单个参数: 直接传递一个参数(无需特殊处理)。多个参数: 使用 @Param 注解指定参数名称。List<User> getUsersByAgeAndGender(@Param("age") int age, @Param("gender") String gender);集合或对象参数: 可以直接传递集合或对象作为参数。List<User> getUsersByIds(List<Integer> ids);MyBatis 结果封装在 MyBatis 中,不同类型的返回值有不同的处理方式常见的返回值类型包括普通类型、对象类型、List、Map,以及通过自定义 ResultMap 进行复杂的结果映射。返回值类型普通类型和对象类型:MyBatis 支持直接返回数据库中查询的单个字段结果(如 int、String 等)。对于对象类型,查询结果会直接映射为 Java 对象类型,通常是实体类。示例:返回普通类型返回一个用户的姓名(String 类型):<select id="getUserNameById" resultType="String"> SELECT name FROM users WHERE id = #{id} </select>示例:返回对象类型返回一个用户对象(User 类型):<select id="getUserById" resultType="com.example.model.User"> SELECT * FROM users WHERE id = #{id} </select>返回 List 类型:返回一个用户的列表:<select id="getAllUsers" resultType="com.example.model.User"> SELECT * FROM users </select>返回 Map 类型:MyBatis 可以将查询结果封装为 Map,常见的使用场景是需要将数据库中的数据映射为键值对。示例:返回 Map<select id="getUserMap" resultType="map"> SELECT id, name, age FROM users </select>可以使用 @MapKey 注解将 Map 的键指定为某个字段:@MapKey("id") Map<Integer, User> getUserMap();自定义结果集(ResultMap)当数据库字段和 Java 对象的属性名称不一致,或查询结果需要映射到多个对象时,可以使用 ResultMap 进行自定义结果映射。示例:定义 ResultMap<resultMap id="userResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> <result property="age" column="user_age"/> </resultMap> <select id="getUserById" resultMap="userResultMap"> SELECT user_id, user_name, user_age FROM users WHERE user_id = #{id} </select>MyBatis 关联查询MyBatis 通过 ResultMap 的 association 和 collection 标签来实现一对一和一对多的关联查询。可以在查询过程中动态加载关联对象的数据,实现更加灵活的结果映射。在开始关联查询之前,需要复习数据库中的关联关系(如一对一、一对多)并确保项目中正确配置 MyBatis 环境。示例:环境配置数据库中的用户表(User)和订单表(Order)之间存在一对多的关系。配置数据库连接信息和 Mapper 配置。一对一关系封装 (association 标签)一对一关系表示每个实体仅与另一个实体相关联。例如,每个用户(User)只有一个订单(Order)。在 MyBatis 中,可以使用 association 标签来处理一对一关系。association 标签使用方法原生查询方法通过 SQL 的 JOIN 语句将两个表的数据连接起来,实现一对一关系的查询:<select id="getUserWithOrder" resultType="com.example.model.User"> SELECT u.user_id, u.user_name, o.order_id, o.order_date FROM users u JOIN orders o ON u.user_id = o.user_id WHERE u.user_id = #{id} </select>在上述例子中,JOIN 语句将用户表 (users) 和订单表 (orders) 连接起来,并查询出用户及其对应的订单信息。分步查询方法在分步查询中,主查询和关联查询是分开的,通过 association 标签实现关联对象的按需加载。association 标签配置<resultMap id="userResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> <association property="order" javaType="com.example.model.Order" select="com.example.mapper.OrderMapper.getOrderById" column="user_id"/> </resultMap>配置解释:property="order":指定关联的 Java 对象属性名为 order。javaType="com.example.model.Order":指定关联对象的类型为 Order。select="com.example.mapper.OrderMapper.getOrderById":指定分步查询的方法 getOrderById。column="user_id":指定作为参数传递的字段。定义分步查询方法在 OrderMapper.xml 中:<select id="getOrderById" resultType="com.example.model.Order"> SELECT order_id, order_date FROM orders WHERE user_id = #{user_id} </select>主查询使用分步查询:<select id="getUserById" resultMap="userResultMap"> SELECT user_id, user_name FROM users WHERE user_id = #{id} </select>一对多关系封装 (collection 标签)一对多关系表示一个实体可以与多个其他实体相关联。例如,一个用户(User)可以有多个订单(Order)。在 MyBatis 中,可以使用 collection 标签来处理一对多关系。collection 标签使用方法原生查询方法在原生 SQL 中,通过 JOIN 语句查询一个用户及其所有订单:<select id="getUserWithOrders" resultType="com.example.model.User"> SELECT u.user_id, u.user_name, o.order_id, o.order_date FROM users u LEFT JOIN orders o ON u.user_id = o.user_id WHERE u.user_id = #{id} </select>这里使用 LEFT JOIN 可以将用户和其所有订单数据查询出来。分步查询方法在分步查询中,通过 collection 标签动态加载关联对象的集合。collection 标签配置<resultMap id="userResultMap" type="com.example.model.User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> <collection property="orders" ofType="com.example.model.Order" select="com.example.mapper.OrderMapper.getOrdersByUserId" column="user_id"/> </resultMap>配置解释:property="orders":指定关联的 Java 集合属性名为 orders。ofType="com.example.model.Order":指定集合中每个元素的类型为 Order。select="com.example.mapper.OrderMapper.getOrdersByUserId":指定分步查询的方法 getOrdersByUserId。column="user_id":指定用于分步查询的参数列。定义分步查询方法在 OrderMapper.xml 中:<select id="getOrdersByUserId" resultType="com.example.model.Order"> SELECT order_id, order_date FROM orders WHERE user_id = #{user_id} </select>主查询使用分步查询:<select id="getUserById" resultMap="userResultMap"> SELECT user_id, user_name FROM users WHERE user_id = #{id} </select>动态 SQLif 标签if 标签用于在 SQL 语句中加入条件逻辑。根据指定的条件表达式,MyBatis 会判断是否将其包含的 SQL 片段放入最终的 SQL 中。<select id="findUserByCondition" resultType="User"> SELECT * FROM users <where> <if test="username != null"> AND username = #{username} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>where 标签where 标签用于自动处理 SQL 语句中的条件部分。如果条件为空,它不会添加 WHERE 关键字,同时会去掉多余的 AND 或 OR。<select id="findUserByCondition" resultType="User"> SELECT * FROM users <where> <if test="username != null"> username = #{username} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>where 标签会自动去掉前置的 AND 或 OR,并且仅在有条件时添加 WHERE 关键字。set 标签set 标签用于生成 UPDATE 语句中的 SET 子句。它自动去掉尾部的逗号(,)。<update id="updateUser" parameterType="User"> UPDATE users <set> <if test="username != null">username = #{username},</if> <if test="age != null">age = #{age},</if> </set> WHERE id = #{id} </update>set 标签会自动去掉 SET 子句中最后的逗号。trim 标签trim 标签是一个更通用的标签,允许自定义前缀和后缀,通常用来替代 where 标签。<select id="findUserByCondition" resultType="User"> SELECT * FROM users <trim prefix="WHERE" prefixOverrides="AND | OR"> <if test="username != null"> AND username = #{username} </if> <if test="age != null"> AND age = #{age} </if> </trim> </select>trim 标签使用 prefix 属性指定前缀,用 prefixOverrides 去掉指定的多余前缀。trim 标签同样可以用于 UPDATE 语句中的 SET 子句,用来替代 set 标签。<update id="updateUser" parameterType="User"> UPDATE users <trim prefix="SET" suffixOverrides=","> <if test="username != null">username = #{username},</if> <if test="age != null">age = #{age},</if> </trim> WHERE id = #{id} </update>trim 标签通过 suffixOverrides 属性去掉末尾的逗号。分支选择choose、when、otherwise 标签用于实现类似 switch-case 的逻辑选择。<select id="findUserByStatus" resultType="User"> SELECT * FROM users <where> <choose> <when test="status == 'active'"> status = 'ACTIVE' </when> <when test="status == 'inactive'"> status = 'INACTIVE' </when> <otherwise> status = 'UNKNOWN' </otherwise> </choose> </where> </select>choose 标签确保只有一个 when 条件成立,如果没有匹配项,使用 otherwise 子句。foreach 标签foreach 标签用于处理集合类型的参数(如 List、Set),通常用于 IN 查询。<select id="findUsersByIds" resultType="User"> SELECT * FROM users WHERE id IN <foreach collection="list" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>foreach 标签生成一组逗号分隔的值,常用于批量操作。foreach 标签也可以用于批量插入操作。<insert id="insertUsers" parameterType="list"> INSERT INTO users (username, age) VALUES <foreach collection="list" item="user" separator=","> (#{user.username}, #{user.age}) </foreach> </insert>foreach 标签生成多条 VALUES 子句,实现批量插入。foreach 标签还可以用于批量更新操作。<update id="updateUsers" parameterType="list"> <foreach collection="list" item="user" separator=";"> UPDATE users <set> username = #{user.username}, age = #{user.age} </set> WHERE id = #{user.id} </foreach> </update>每次迭代生成一条 UPDATE 语句,用于批量更新数据。在 MyBatis 中,批量操作默认会在一个事务中执行,只有在所有操作都成功时才会提交,否则会回滚。需要配置 ExecutorType.BATCH 才能启用批处理模式。<sql> 标签<sql> 标签用于抽取可复用的 SQL 片段,<include> 标签用于引用这些片段。<sql id="userColumns">id, username, age</sql> <select id="findAllUsers" resultType="User"> SELECT <include refid="userColumns" /> FROM users </select>sql 标签定义一个片段,include 标签引用这个片段。
2024年09月11日
2 阅读
0 评论
0 点赞
2024-09-10
RESTful 规范的 CRUD实践
RESTful 规范的 CRUD 操作符合 REST 架构风格的 API,能够使服务具有良好的可扩展性和松耦合性。RESTful API 的核心概念包括资源、URI(统一资源标识符)、无状态性,以及使用标准的 HTTP 方法来对资源进行操作。HTTP 方法在 RESTful API 中用于定义资源操作:GET 用于获取资源数据,不会对服务器端的数据造成任何修改。示例:GET /api/users/1 用于获取 ID 为 1 的用户信息。POST 用于在服务器端创建新资源,通常会在成功创建后返回新资源的 URI。示例:POST /api/users 用于创建一个新用户。PUT 用于更新资源,通常用于更新整个资源或根据请求内容对资源进行修改。示例:PUT /api/users/1 用于更新 ID 为 1 的用户信息。PATCH 用于部分更新资源,区别于 PUT,PATCH 只更新提供的字段。示例:PATCH /api/users/1 用于更新 ID 为 1 的用户的部分信息。DELETE 用于删除资源。示例:DELETE /api/users/1 用于删除 ID 为 1 的用户。状态码在 RESTful API 中用于表示请求结果的状态:200 OK 表示请求成功,返回期望的响应数据。201 Created 表示成功创建资源,返回新资源的 URI。204 No Content 表示请求成功,但不返回任何内容,通常用于删除操作。400 Bad Request 表示客户端请求无效,通常由于请求参数有误。401 Unauthorized 表示请求未经授权,通常因为身份验证失败。403 Forbidden 表示请求被服务器拒绝,通常因为权限不足。404 Not Found 表示请求的资源不存在。500 Internal Server Error 表示服务器内部错误。RESTful API 的 URI 设计应清晰明了、符合层次结构,并使用名词表示资源。通常使用名词的复数形式表示资源集合,例如 /api/users 表示用户资源。路径设计中避免使用动词,如 /getUser,而应通过 HTTP 方法表达操作类型。版本控制可以通过在 URI 中添加版本号(如 /api/v1/users),或者通过请求头指定在 RESTful API 的版本控制中,使用 URI 版本控制或请求头版本控制是较为常见的两种策略:URI 版本控制:直接在 URI 中添加版本号,例如 /api/v1/users。这种方式简单直观,易于理解,但会导致 URI 更加冗长,并且如果版本众多,管理起来会比较复杂。请求头版本控制:通过自定义请求头来指定版本号,例如 Accept: application/vnd.example.v1+json。这种方式更加灵活,URI 不会随着版本的增加而变得混乱,但客户端需要明确设置请求头。在实际应用中,RESTful API 通常需要实现基本的 CRUD 操作,并遵循最佳实践以确保 API 的易用性、可维护性和安全性。实现 CRUD 操作的一个基本示例使用 Spring Boot 框架。以下是如何在 Spring Boot 中创建一个完整的 RESTful CRUD 操作示例。创建实体类 User 表示用户资源:@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增策略 private Long id; @NotBlank(message = "Name is mandatory") // 校验字段不为空 private String name; @Email(message = "Email should be valid") // 校验邮箱格式 private String email; // Getters 和 Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }在数据访问层,创建一个接口 UserRepository,继承自 JpaRepository,用于基本的 CRUD 操作:public interface UserRepository extends JpaRepository<User, Long> { // 自定义查询方法:按名称查找用户 List<User> findByName(String name); }服务层的实现用于处理业务逻辑。UserService 提供了用户的增删改查操作:@Service public class UserService { @Autowired private UserRepository userRepository; // 创建用户 public User createUser(User user) { return userRepository.save(user); } // 根据ID获取用户 public User getUserById(Long id) { return userRepository.findById(id).orElse(null); } // 获取所有用户 public List<User> getAllUsers() { return userRepository.findAll(); } // 更新用户 public User updateUser(Long id, User userDetails) { User user = getUserById(id); if (user != null) { user.setName(userDetails.getName()); user.setEmail(userDetails.getEmail()); return userRepository.save(user); } return null; } // 删除用户 public void deleteUser(Long id) { userRepository.deleteById(id); } }控制器层 UserController 负责处理 HTTP 请求并返回相应的响应数据:@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @PostMapping // 创建用户 public ResponseEntity<User> createUser(@RequestBody User user) { User createdUser = userService.createUser(user); return new ResponseEntity<>(createdUser, HttpStatus.CREATED); } @GetMapping("/{id}") // 根据ID获取用户 public ResponseEntity<User> getUserById(@PathVariable Long id) { User user = userService.getUserById(id); if (user != null) { return new ResponseEntity<>(user, HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @GetMapping // 获取所有用户 public ResponseEntity<List<User>> getAllUsers() { List<User> users = userService.getAllUsers(); return new ResponseEntity<>(users, HttpStatus.OK); } @PutMapping("/{id}") // 更新用户 public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) { User updatedUser = userService.updateUser(id, userDetails); if (updatedUser != null) { return new ResponseEntity<>(updatedUser, HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.NOT_FOUND); } @DeleteMapping("/{id}") // 删除用户 public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } }RESTful API 设计的最佳实践包括:使用 HTTPS 确保数据传输的安全性。实现身份验证和授权,例如使用 JWT(JSON Web Token)或 OAuth 2.0,确保只有合法的用户能够访问资源。使用标准化的 API 文档工具(如 Swagger)生成文档,使 API 更加易于理解和使用。提供分页、过滤和排序功能,以提升数据查询的灵活性和性能。遵循 HATEOAS(超媒体作为应用状态的引擎)原则,为客户端提供导航链接,帮助客户端更容易地探索和使用 API。拦截器(Interceptor)拦截器在 Web 应用中是一种特殊的过滤器,用于在请求到达控制器之前或返回响应之前执行某些逻辑。拦截器通常用于日志记录、权限验证、数据预处理、异常处理等场景。在 Spring MVC 中,可以通过实现 HandlerInterceptor 接口来定义自定义拦截器。拦截器通常有三个方法:preHandle:在请求到达控制器之前调用。可以用于身份验证、权限检查、日志记录等操作。如果该方法返回 false,请求将不会继续向下传递。postHandle:在控制器执行完逻辑之后,但在视图渲染之前调用。可以用于修改视图或向模型中添加通用数据。afterCompletion:在请求完全处理完毕后调用,通常用于清理资源、记录日志等操作。在 Spring Boot 中,拦截器的配置可以通过创建一个配置类并实现 WebMvcConfigurer 接口来完成:@Component public class LoggingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Request URI: " + request.getRequestURI()); return true; // 继续处理请求 } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Post Handle method is Calling"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception { System.out.println("Request and Response is completed"); } }通过创建一个配置类并将拦截器注册到 Spring 的拦截器链中:@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoggingInterceptor loggingInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loggingInterceptor).addPathPatterns("/**"); } }使用 @Validated 注解可以结合拦截器实现数据校验。例如,在拦截器中可以结合方法参数中的注解进行校验,确保请求数据的合法性。异常处理全局异常处理可以确保应用程序在出现异常时返回统一的错误响应,从而提高应用的健壮性和用户体验。在 Spring Boot 中,可以使用 @ControllerAdvice 和 @ExceptionHandler 注解实现全局异常处理。通过定义一个全局异常处理类,并使用 @ControllerAdvice 注解标识,该类会捕获并处理应用程序中的所有异常:@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<?> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) { ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); } @ExceptionHandler(Exception.class) public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) { ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); } }自定义异常类 ResourceNotFoundException 用于表示资源未找到的情况:public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } }通过这种方式,应用程序的异常处理逻辑集中在一个地方,便于管理和维护。异常处理程序根据捕获的异常类型,返回适当的 HTTP 状态码和错误信息。使用 ResponseEntity 可以灵活地构建标准化的错误响应对象。在实际应用中,为了提高 API 的可用性和可维护性,统一的错误响应格式是一个良好的实践。通常会包含以下信息:timestamp:错误发生的时间。message:错误的具体信息。details:错误的详细描述,例如请求的 URI。示例的错误响应对象 ErrorDetails 类如下:public class ErrorDetails { private Date timestamp; private String message; private String details; public ErrorDetails(Date timestamp, String message, String details) { super(); this.timestamp = timestamp; this.message = message; this.details = details; } // Getters 和 Setters public Date getTimestamp() { return timestamp; } public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } }通过全局异常处理器,所有未捕获的异常都会被自动处理,并返回标准化的错误响应。这样可以确保 API 对外暴露的接口稳定,同时为客户端提供一致的错误信息格式,方便调试和错误处理。自定义校验器在实际应用中,有时我们需要对输入数据进行复杂的校验,而这些校验可能无法通过简单的注解(如 @NotNull、@Email 等)来实现。此时可以通过实现自定义校验器来完成。创建一个自定义校验注解,如 @ValidPassword,用于验证用户密码的复杂性(例如必须包含大写字母、小写字母、数字和特殊字符):定义注解 ValidPassword:@Constraint(validatedBy = PasswordValidator.class) // 绑定自定义校验器 @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ValidPassword { String message() default "Invalid password"; // 默认错误信息 Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }实现自定义校验逻辑:public class PasswordValidator implements ConstraintValidator<ValidPassword, String> { private Pattern pattern; private Matcher matcher; private static final String PASSWORD_PATTERN = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$"; @Override public void initialize(ValidPassword constraintAnnotation) { pattern = Pattern.compile(PASSWORD_PATTERN); // 初始化密码匹配正则 } @Override public boolean isValid(String password, ConstraintValidatorContext context) { if (password == null) { return false; // 密码不能为空 } matcher = pattern.matcher(password); return matcher.matches(); // 返回匹配结果 } }在实体类中使用自定义校验注解:public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Name is mandatory") private String name; @Email(message = "Email should be valid") private String email; @ValidPassword // 使用自定义的密码校验注解 private String password; // Getters 和 Setters }当客户端请求中包含无效密码时,自定义校验器会触发相应的错误,并在响应中返回错误信息。通过自定义校验器,可以灵活地满足各种复杂的校验需求。数据校验与错误消息提示使用 @Validated 注解结合 BindingResult 对象,可以在控制器中实现更加细粒度的错误处理逻辑。示例中,我们在创建用户时应用这些技术:@PostMapping("/users") public ResponseEntity<?> createUser(@Validated @RequestBody User user, BindingResult result) { if (result.hasErrors()) { // 检查校验结果是否有错误 List<String> errors = result.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) // 提取所有错误消息 .collect(Collectors.toList()); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); // 返回错误响应 } User createdUser = userService.createUser(user); return new ResponseEntity<>(createdUser, HttpStatus.CREATED); }通过 BindingResult 对象,可以获取所有的校验错误,并返回给客户端。这样,客户端可以根据具体的错误消息提示用户输入的错误信息。CORS(跨域资源共享)跨域资源共享(CORS)是一种浏览器安全机制,用于防止不受信任的域对资源的未经授权的访问。RESTful API 通常需要支持 CORS,以允许来自不同域的客户端(如 Web 应用程序)与服务器通信。在 Spring Boot 中,可以使用 @CrossOrigin 注解启用 CORS。例如,在控制器方法上添加此注解:@CrossOrigin(origins = "http://example.com") // 允许来自指定来源的跨域请求 @GetMapping("/users") public ResponseEntity<List<User>> getAllUsers() { List<User> users = userService.getAllUsers(); return new ResponseEntity<>(users, HttpStatus.OK); }或者在全局配置中启用 CORS 支持:@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 允许跨域的路径 .allowedOrigins("http://example.com") // 允许的来源 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的 HTTP 方法 .allowCredentials(true); // 是否允许发送凭证(如 cookies) } }通过正确配置 CORS,客户端可以安全地访问 RESTful API,同时确保数据和用户隐私的安全性。
2024年09月10日
2 阅读
0 评论
0 点赞
2024-09-08
SpingWebMVC
开发模式前后端不分离视图层由服务端渲染前后端分离视图层由用户端渲染HelloWorldpackage xin.links.framework.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * ClassName: SpringFramework * Package: xin.links.framework.springmvc.contriller * Description: * * @author Jane * @version 1.0 * @since 2024/9/3 10:12 */ //@RequestMapping的路径通配符 // ?:匹配一个字符 // *:匹配任意字符 // **:匹配任意字符,包括路径中的“/” // 精确匹配 // // 各级优先级 // 精确匹配 > ? > * > ** @Controller public class HelloController { @ResponseBody @RequestMapping("/hello")// @RequestMapping的value属性指定请求路径 public String hello() { return "hello页面"; } } 注解@RestController@Controller和@ResponseBody注解的组合,意味着这个类的所有方法返回值都会直接用作响应体。请求响应处理请求首行请求方式:GET、POST、PUT、DELETE等请求路径:/hello、/index.html等请求协议:HTTP/1.1等请求头键值对:Host: www.example.com等每对之间用换行符分隔请求体发送给服务器的内容在POST请求中携带大量数据GET请求通常在URL中携带数据响应首行响应状态码:200、404、500等响应描述:OK、Not Found、Internal Server Error等响应协议:HTTP/1.1等响应头键值对:Content-Type: text/html; charset=utf-8等每对之间用换行符分隔响应体服务器返回的内容可以是一个HTML页面、JSON数据等@RequestMapping取出参数值路径映射(@RequestMapping)@RequestMapping 注解支持丰富的映射规则,可以对路径、请求方法、参数、请求头、数据类型等进行细粒度控制。基础映射和通配符使用@Controller public class BasicMappingController { // 简单路径映射 @RequestMapping("/hello") public String helloWorld() { return "hello"; } // 使用单个通配符 @RequestMapping("/users/*/profile") public String userProfile() { return "userProfile"; } // 使用多级通配符 @RequestMapping("/documents/**") public String handleDocuments() { return "documents"; } }* 匹配单层路径,** 匹配多层路径。请求方法限定@RequestMapping(value = "/submit", method = RequestMethod.POST) public String handleSubmit() { return "submitSuccess"; }支持多种请求方法:GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH 等。请求参数和请求头限定请求参数限定:@RequestMapping(value = "/search", params = "type=advanced") public String handleAdvancedSearch() { return "advancedSearch"; }请求头限定:@RequestMapping(value = "/upload", headers = "Content-Type=multipart/form-data") public String handleFileUpload() { return "uploadSuccess"; }请求数据类型(consumes)和响应数据类型(produces)请求数据类型:@RequestMapping(value = "/json", consumes = "application/json") public String handleJsonRequest(@RequestBody MyObject obj) { return "jsonProcessed"; }响应数据类型:@RequestMapping(value = "/getJson", produces = "application/json") @ResponseBody public MyObject getJsonResponse() { return new MyObject(); }请求处理普通变量、@RequestParam 和 POJO普通变量收集请求参数:@RequestMapping("/greet") public String greetUser(@RequestParam("name") String name) { return "Hello, " + name; }使用 @RequestParam 明确指定参数:@RequestMapping("/show") public String showUserInfo(@RequestParam(name = "id", required = true) int userId) { return "User ID: " + userId; }使用 POJO 封装请求参数:public class User { private String name; private int age; // getters and setters } @RequestMapping("/register") public String registerUser(User user) { return "User registered: " + user.getName(); }获取请求头和 Cookie 值获取请求头:@RequestMapping("/info") public String getInfo(@RequestHeader("User-Agent") String userAgent) { return "User-Agent: " + userAgent; }获取 Cookie 值:@RequestMapping("/cookie") public String getCookieValue(@CookieValue("JSESSIONID") String sessionId) { return "Session ID: " + sessionId; }JSON 字符串和文件上传处理接受 JSON 字符串并转换为对象:@RequestMapping(value = "/addUser", consumes = "application/json") public String addUser(@RequestBody User user) { return "User added: " + user.getName(); }文件上传:@RequestMapping(value = "/upload", method = RequestMethod.POST) public String handleFileUpload(@RequestParam("file") MultipartFile file) { return "File uploaded: " + file.getOriginalFilename(); }获取完整请求对象和使用原生 API获取完整请求对象(HttpEntity):@RequestMapping("/fullRequest") public String getFullRequest(HttpEntity<String> httpEntity) { return "Request Body: " + httpEntity.getBody(); }使用原生 Servlet API 对象:@RequestMapping("/nativeApi") public String handleNative(HttpServletRequest request) { return "Request URL: " + request.getRequestURL(); }响应处理返回 JSON 数据@RequestMapping(value = "/getJsonData", produces = "application/json") @ResponseBody public User getJsonData() { return new User("Alice", 30); }文件下载@RequestMapping("/download") public ResponseEntity<Resource> downloadFile() { // 实现文件下载功能 return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=file.txt") .body(resource); }使用 Thymeleaf 渲染视图通过 Thymeleaf 模板引擎将数据渲染到 HTML 页面中:<p th:text="'Hello, ' + ${name}">Hello, user!</p>
2024年09月08日
1 阅读
0 评论
0 点赞
2024-09-08
Spring框架声明式事务
声明式事务是Spring框架提供的一种管理事务的方式允许在不修改业务代码的情况下,通过注解或XML配置来管理事务。【声明式】VS【编程式】声明式:通过注解等方式,告诉框架,我要做什么,框架会帮我做什么。优点:代码量小。缺点:封装太多。排错不容易编程式:通过代码的方式,告诉框架,我要做什么,需要自己写代码实现。优点:排错容易缺点:代码量多操作数据库1、导入包:spring-boot-starter-data-jdbc、mysql-connector-java2、配置数据库连接信息:在appLication.properties中spring.datasource.*3、可以直接使用DataSource/JdbcTemplate@Transactional 是 Spring 框架中用于声明式事务管理的一个重要注解。它可以应用于类或者方法上,帮助开发者在不编写大量手动事务控制代码的情况下实现对数据库事务的管理。以下是关于 @Transactional 注解的详细笔记:事务管理@Transactional可以应用于接口、接口方法、类以及类方法上。当应用于类上时,表示该类的所有公共方法都需要被事务管理。当应用于方法上时,表示该方法需要被事务管理。类级别:该类中的所有方法都会被事务管理。方法级别:仅该方法会被事务管理。@Service @Transactional public class BookService { // 类中的所有方法都具有事务性 } @Service public class BookService { @Transactional public void updateBook(Book book) { // 只有这个方法具有事务性 } }事务的基本概念在使用 @Transactional 之前,了解事务的基本特性非常重要。事务具有以下四个基本属性(ACID):原子性(Atomicity):事务要么完全执行,要么不执行。一致性(Consistency):事务完成后,数据库必须处于一致的状态。隔离性(Isolation):事务的执行彼此独立,避免相互干扰。持久性(Durability):一旦事务提交,对数据库的更改是永久的。`属性配置@Transactional 注解支持多种属性配置,用于更精细地控制事务行为:propagation(传播行为):定义事务方法如何与当前事务关联。常见的传播行为有:REQUIRED(默认):如果有现成的事务,就加入这个事务;如果没有,则创建一个新事务。REQUIRES_NEW:无论是否有现成的事务,都创建一个新的事务。SUPPORTS:如果有现成的事务,就加入;如果没有,就以非事务方式执行。NOT_SUPPORTED:不支持事务,如果有现成的事务,则暂停它。MANDATORY:必须在现成事务中执行,否则抛出异常。NEVER:必须在没有事务的环境中执行,否则抛出异常。NESTED:如果有现成的事务,则在嵌套的事务中执行。isolation(隔离级别):定义事务与其他事务之间的隔离程度。常见的隔离级别有:DEFAULT:使用数据库默认的隔离级别。READ_UNCOMMITTED:允许脏读、不可重复读和幻读。READ_COMMITTED:不允许脏读,但允许不可重复读和幻读。REPEATABLE_READ:不允许脏读和不可重复读,但允许幻读。SERIALIZABLE:完全串行化的读,防止所有事务问题。timeout(超时):事务的超时时间(以秒为单位)。如果超过这个时间,事务将自动回滚。readOnly(只读):是否只读事务。如果设置为 true,则该事务不允许对数据库进行修改操作,优化查询性能。rollbackFor 和 rollbackForClassName:指定哪些异常会导致事务回滚。noRollbackFor 和 noRollbackForClassName:指定哪些异常不会导致事务回滚。@Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, readOnly = false, rollbackFor = {SQLException.class, DataAccessException.class} ) public void updateBook(Book book) { // 业务逻辑 }使用场景与注意事项适用场景:@Transactional 通常用于数据库操作较多的服务层(Service Layer),以确保数据操作的原子性和一致性。事务传播:选择合适的传播行为非常重要。例如,某些情况需要新建事务(REQUIRES_NEW),而有些情况需要加入现有事务(REQUIRED)。异常处理:默认情况下,Spring 仅对未检查的异常(运行时异常)进行事务回滚。如果需要对检查异常(如 SQLException)进行回滚,必须在 @Transactional 中指定 rollbackFor。懒加载问题:在事务外访问懒加载的实体属性可能导致 LazyInitializationException。因此,确保事务管理的范围足够大,以覆盖所有需要的数据访问操作。实现原理@Transactional 的实现依赖于 Spring AOP(面向切面编程)。当一个带有 @Transactional 注解的方法被调用时,Spring 会创建一个代理对象,并在代理对象中添加事务管理逻辑:方法调用前:打开一个新的数据库连接,启动事务。方法执行中:执行实际的业务逻辑。方法调用后:如果没有异常:提交事务。如果有异常:根据配置回滚事务。常见问题与解决No qualifying bean of type 错误:确保相关类(如 DataSource、PlatformTransactionManager)已正确配置,并在 Spring 容器中注册。LazyInitializationException 错误:增加事务范围,确保事务在访问懒加载数据时依然活跃。事务失效问题:@Transactional 注解的生效范围仅限于外部方法调用,因此不能在同一个类中直接调用带有 @Transactional 的方法。解决办法是通过代理类或者将事务方法提取到单独的类中。示例代码@Service public class BookService { @Autowired private BookDao bookDao; @Transactional public void updateBookStock(int bookId, int quantity) { Book book = bookDao.getBookById(bookId); if (book.getStock() < quantity) { throw new RuntimeException("库存不足"); } book.setStock(book.getStock() - quantity); bookDao.updateBook(book); } @Transactional(readOnly = true) public Book findBook(int id) { return bookDao.getBookById(id); } }包含所有 @Transactional 属性的调用演示import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class BookService { // 自动注入DAO对象,用于数据库操作 @Autowired private BookDao bookDao; /** * 演示使用所有属性的事务方法 */ @Transactional( propagation = Propagation.REQUIRED, // 事务传播行为:如果有现存的事务,则加入;否则新建一个事务 isolation = Isolation.REPEATABLE_READ, // 事务隔离级别:防止脏读和不可重复读 timeout = 30, // 事务超时时间:30秒 readOnly = false, // 是否只读事务:否,允许数据修改 rollbackFor = {RuntimeException.class, SQLException.class}, // 指定哪些异常会导致事务回滚 noRollbackFor = {IllegalArgumentException.class} // 指定哪些异常不会导致事务回滚 ) public void updateBookStock(int bookId, int quantity) { // 获取指定ID的图书对象 Book book = bookDao.getBookById(bookId); // 如果库存不足,抛出异常,触发事务回滚 if (book.getStock() < quantity) { throw new RuntimeException("库存不足"); } // 更新图书库存 book.setStock(book.getStock() - quantity); bookDao.updateBook(book); } /** * 演示只读事务 */ @Transactional( readOnly = true, // 设置为只读事务,不允许对数据库进行修改操作 propagation = Propagation.SUPPORTS, // 支持现有事务,如果没有事务则以非事务方式执行 isolation = Isolation.READ_COMMITTED // 允许读取已提交的数据,防止脏读 ) public Book findBook(int id) { // 查找图书信息 return bookDao.getBookById(id); } /** * 演示事务超时和特定异常回滚 */ @Transactional( timeout = 10, // 设置事务超时时间为10秒 rollbackFor = {CustomException.class}, // 仅在遇到自定义异常时回滚 propagation = Propagation.REQUIRES_NEW, // 每次调用都创建一个新的事务 isolation = Isolation.SERIALIZABLE // 串行化隔离级别,最严格的隔离方式 ) public void processTransaction() throws CustomException { // 执行一些数据库操作 try { // 假设进行某些复杂操作 bookDao.someComplexOperation(); } catch (SQLException e) { // 抛出自定义异常以触发回滚 throw new CustomException("操作失败", e); } } }
2024年09月08日
4 阅读
0 评论
0 点赞
2024-09-06
SpringFramework-AOP
Spring AOPSpring AOP使用动态代理来创建代理对象,并在代理对象上织入横切关注点横切关注点通常指与业务逻辑无关,需要在多个地方重复出现,如日志记录、事务管理、安全检查等核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)和切入点(Pointcut)。切面(Aspect):切面是横切关注点的模块化,它包含了通知和切入点。连接点(Join Point):连接点是程序执行的一个点,如方法调用或异常抛出。通知(Advice):通知是切面在连接点执行的操作,如方法调用前后执行某些操作。切入点(Pointcut):切入点定义了通知应该被应用在哪些连接点上。原生动态代理动态代理是Java反射机制的一部分,它允许程序在运行时创建接口的代理对象。动态代理主要用于实现一些通用的功能,如日志记录、事务管理、权限检查等,而不需要修改源代码。Hello.javapublic interface Hello { void sayHello(); }HelloImpl.javapublic class HelloImpl implements Hello { @Override public void sayHello() { System.out.println("Hello, world!"); } }HelloProxy.java:import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class HelloProxy implements InvocationHandler { private Object target; public HelloProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After method: " + method.getName()); return result; } }Main.java:public class Main { public static void main(String[] args) { Hello hello = new HelloImpl(); Hello proxy = (Hello) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), new HelloProxy(hello) ); proxy.sayHello(); } }AOP流程引入依赖:在pom.xml中添加Spring AOP的依赖。定义切面:创建一个类,使用@Aspect注解标记,定义切点和通知。配置AOP:在Spring配置文件中启用AOP,或者使用Java配置类启用AOP。编写业务逻辑:定义一个普通的业务逻辑类,并使用切面进行增强。运行程序:测试AOP功能是否生效。引入依赖在pom.xml中添加Spring AOP的依赖:<dependencies> <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <!-- Spring AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency> <!-- AspectJ Weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> </dependencies>定义切面创建一个切面类LoggingAspect,并使用@Aspect注解标记: import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.JoinPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Aspect // 定义切面 @Component // 将该类声明为Spring组件 public class LoggingAspect { private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); // 日志记录器 // 使用切点表达式匹配com.example.service包下的所有方法 @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { logger.info("Before method: " + joinPoint.getSignature().getName()); // 在方法执行前记录日志 } @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { logger.info("After method: " + joinPoint.getSignature().getName()); // 在方法执行后记录日志 } // 匹配com.example.service包下的所有方法,并在方法成功返回后记录日志 @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { logger.info("Method returned: " + result); // 记录方法返回值 } // 匹配com.example.service包下的所有方法,并在方法抛出异常后记录日志 @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error") public void logAfterThrowing(JoinPoint joinPoint, Throwable error) { logger.error("Exception in method: " + joinPoint.getSignature().getName() + ", Exception: " + error); // 记录异常信息 } }配置AOP创建一个配置类AopConfig,并使用@EnableAspectJAutoProxy注解启用AOP:import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy public class AopConfig { }编写业务逻辑创建一个业务逻辑类MyService:import org.springframework.stereotype.Service; @Service public class MyService { public void performTask() { System.out.println("Performing task..."); } public void performTaskWithError() { System.out.println("Performing task with error..."); throw new RuntimeException("Task failed!"); } }运行程序创建一个主程序类App来测试AOP功能:import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.example.service.MyService; public class App { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); MyService myService = context.getBean(MyService.class); myService.performTask(); myService.performTaskWithError(); } }运行结果运行上述程序,你将看到类似以下的日志输出:INFO c.e.LoggingAspect - Before method: performTask INFO c.e.LoggingAspect - After method: performTask INFO c.e.LoggingAspect - Method returned: null INFO c.e.LoggingAspect - Before method: performTaskWithError INFO c.e.LoggingAspect - After method: performTaskWithError ERROR c.e.LoggingAspect - Exception in method: performTaskWithError, Exception: java.lang.RuntimeException: Task failed!AOP术语切面(Aspect):一个模块化的关注点,它横切多个类,通常包含横切关注点的代码。连接点(Join Point):程序执行的一个点,例如方法调用或异常抛出。通知(Advice):在连接点执行的操作,例如前置通知、后置通知、返回通知和异常通知。切点(Pointcut):匹配连接点的表达式。引入(Introduction):允许向现有的类添加新的方法或属性。织入(Weaving):将切面应用到目标对象,创建一个被通知的对象。常用注解@Aspect:标记一个类为切面。@Pointcut:定义一个切点表达式。@Before:在连接点之前执行的通知。@After:在连接点之后执行的通知。@AfterReturning:在连接点正常返回后执行的通知。@AfterThrowing:在连接点抛出异常后执行的通知。@Around:环绕通知,可以在方法执行前后执行逻辑。可以控制目标方法是否执行,修改目标方法参数执行结果等。@EnableAspectJAutoProxy:启用Spring AOP自动代理。切点表达式execution:匹配方法执行连接点,例如execution(* com.example.service.*.*(..))。within:匹配特定类型的连接点,例如within(com.example.service.*)。this:匹配当前AOP代理对象类型的连接点,例如this(com.example.service.MyService)。target:匹配目标对象类型的连接点,例如target(com.example.service.MyService)。args:匹配参数列表的连接点,例如args(java.lang.String)。@annotation:匹配带有特定注解的连接点,例如@annotation(com.example.annotation.MyAnnotation)。常用工具类常用工具类可以帮助我们处理类型、反射、注解和类等操作TypeUtils位于org.springframework.core包中,提供了一些与类型相关的实用方法,例如获取泛型类型、判断类型是否为某个类的子类等。ReflectionUtils位于org.springframework.util包中,提供了一些与反射相关的实用方法,例如获取类的所有方法、调用方法等。AnnotationUtils位于org.springframework.core.annotation包中,提供了一些与注解相关的实用方法,例如获取类或方法上的注解、判断类或方法是否包含某个注解等。ClassUtils位于org.springframework.util包中,提供了一些与类相关的实用方法,例如获取类的全限定名、判断类是否为某个类的子类等。在使用这些工具类时,需要导入相应的包,并使用静态导入来简化代码。例如:import static org.springframework.core.annotation.AnnotationUtils.findAnnotation; import static org.springframework.util.ClassUtils.getAllInterfacesForClass; import static org.springframework.util.ReflectionUtils.findMethod;然后,就可以直接使用这些工具类的方法,而不需要每次都写出完整的类名。例如:Annotation annotation = findAnnotation(MyClass.class, MyAnnotation.class); Class[] interfaces = getAllInterfacesForClass(MyClass.class); Method method = findMethod(MyClass.class, "myMethod");这些工具类是Spring框架中的一部分,可以在任何Spring应用程序中使用。代码package xin.links.framework.springaop.operation; /** * ClassName: SpringFramework * Package: xin.links.framework.springaop.operation * Description: * * @author Jane * @version 1.0 * @since 2024/9/3 18:37 */ public interface MathOperation { // 定义四则运算 int add(int a, int b); int sub(int a, int b); int mul(int a, int b); int div(int a, int b); } package xin.links.framework.springaop.operation; import org.springframework.stereotype.Component; /** * ClassName: SpringFramework * Package: xin.links.framework.springaop.operation * Description: * * @author Jane * @version 1.0 * @since 2024/9/3 18:39 */ @Component public class MathOperationImpl implements MathOperation { @Override public int add(int a, int b) { System.out.println(a+"+"+b+"="+(a+b)); return a+b;} @Override public int sub(int a, int b) { System.out.println(a+"-"+b+"="+(a-b)); return a-b; } @Override public int mul(int a, int b) { System.out.println(a+"*"+b+"="+(a*b)); return a*b; } @Override public int div(int a, int b) { System.out.println(a+"/"+b+"="+(a/b)); return a/b; } } package xin.links.framework.springaop.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * ClassName: SpringFramework * Package: xin.links.framework.springaop.aspect * Description: * * @author Jane * @version 1.0 * @since 2024/9/4 15:52 */ @Aspect @Component public class LogAspect { /**切入点表达式 * execution(访问修饰符 返回值类型 方法名(参数类型)) 包名.类名.方法名(参数类型) * */ /**简单通知 * @AfterReturning * @Before * @AfterThrowing * @After */ @Before("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))") public void logStart(){ System.out.println("【开始】log日志"); } @After("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))") public void logEnd(){ System.out.println("【结束】log日志"); } @AfterThrowing("execution(int *xin.links.framework.springaop.operation.MathOperation.*(..))") public void logException(){ System.out.println("【异常】log日志"); } @AfterReturning("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))") public void logReturn(){ System.out.println("【返回】log日志"); } /** * 定义切入点 */ @Pointcut("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))") public void logPointcut(){} /** * 环绕通知 * @param joinPoint * @return */ @Around("logPointcut()") public Object logAround(ProceedingJoinPoint joinPoint){ System.out.println("【环绕开始】log日志"); Object result = null; try { result = joinPoint.proceed(); System.out.println("【环绕返回】log日志"); } catch (Throwable e) { e.printStackTrace(); System.out.println("【环绕异常】log日志"); } System.out.println("【环绕结束】log日志"); return result; } } package xin.links.framework.springaop; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cglib.proxy.Proxy; import xin.links.framework.springaop.operation.MathOperation; import xin.links.framework.springaop.operation.MathOperationImpl; import java.lang.reflect.Array; import java.util.Arrays; @SpringBootTest class SpringAopApplicationTests { @Autowired MathOperation mathOperation; @Test public void Test01 () { System.out.println("mathOperation.getClass() = " + mathOperation.getClass()); System.out.println("mathOperation.add(a, b) = " + mathOperation.add(1, 2)); } @Test void contextLoads() { // 原生计算对象 // MathOperationImpl calculator = new MathOperationImpl(); // System.out.println("calculator.add(1, 2) = " + calculator.add(1, 2)); /**动态代理对象 * 1. 代理对象和被代理对象实现相同的接口 * 2. 代理对象内部持有一个被代理对象的引用 * 3. 代理对象和被代理对象有相同的父类或父接口 */ // Spring动态代理对象(Java原生支持) //创建一个MathOperation类型的代理对象 // MathOperation mathOperation = (MathOperation)Proxy.newProxyInstance( // calculator.getClass().getClassLoader(), // calculator.getClass().getInterfaces(), // (proxy, method, args) -> { // //执行前打印日志 // System.out.println("执行前"); // //打印参数 // System.out.println("Arrays.asList(args) = " + Arrays.asList(args)); // //将参数2的值改为1 // args[1]=1; // //执行方法 // Object result = method.invoke(calculator, args); // //执行后打印日志 // System.out.println("执行后"); // //打印参数 // System.out.println("Arrays.asList(args) = " + Arrays.asList(args)); // //返回结果 // return result; // }); // //调用add方法并打印结果 // System.out.println("mathOperation.add(1, 2) = " + mathOperation.add(1, 2)); // } }
2024年09月06日
6 阅读
0 评论
0 点赞
2024-09-06
SpringFramework-IOC
IOC(Inversion of Control,控制反转)控制反转 是一种设计原则,旨在将对象创建和依赖管理的控制权从应用程序代码中转移到外部容器或框架中。换句话说,应用程序不再负责创建和管理依赖,而是将这些职责交给外部的容器或框架。DI(Dependency Injection,依赖注入)依赖注入 是实现控制反转的一种方式,表示对象所依赖的其他对象(依赖)是由外部注入的,而不是自己创建的。DI 的核心目的是将对象的创建和管理职责从类内部转移到外部。组件注册容器初探package xin.links.spring.ioc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import java.util.Arrays; // Spring主程序入口 @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { // ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器 ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args); // 调试输出IOC容器 System.out.println("ioc = " + ioc); System.out.println("增强for调试输出"); // 增强for调试输出IOC容器中所有组件 for (String beanDefinitionName : ioc.getBeanDefinitionNames()) { System.out.println("beanDefinitionName = " + beanDefinitionName); } System.out.println("forEach调试输出"); // forEach调试输出IOC容器中所有组件 // ioc.getBeanDefinitionNames()获取的默认为数组,不支持forEach,转为流后Stream有这个API Arrays.stream(ioc.getBeanDefinitionNames()).forEach(System.out::println); } }自建组件组件创建前先创建实体类使用@Data注解实现get/set @Data注解不可用说明没有导入lombok依赖可以借助IDEA中的Maven Search插件快速导入package xin.links.spring.ioc.bean; import lombok.Data; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.bean * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 15:44 */ @Data public class Person { private String name; private int age; private String gender; }创建方式一直接在主程序中创建(以用户组件为例)package xin.links.spring.ioc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import xin.links.spring.ioc.bean.Person; @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { // ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器 ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args); } // 使用@Bean注解来声明组件 @Bean public Person person(){ Person person = new Person(); person.setName("张三"); person.setAge(22); person.setGender("男"); return person; } }创建方式二通过配置类创建(配置类本身也是一个组件)在主程序目录下新建Config目录,创建配置类(以用户组件为例)PersonConfig.classpackage xin.links.spring.ioc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import xin.links.spring.ioc.bean.Person; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.config * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 16:14 */ //使用@Configuration注解来声明一个配置类 @Configuration public class PersonConfig { // 使用@Bean注解来声明组件,组件名默认是方法名 @Bean public Person person(){ Person person = new Person(); person.setName("张三"); person.setAge(22); person.setGender("男"); return person; } // 通过修改方法名来修改组件名 @Bean public Person lisi() { Person person = new Person(); person.setName("李四"); person.setAge(30); person.setGender("女"); return person; } // 通过@Bean注解的name属性来修改组件名 @Bean(name = "wangWu") public Person wangWu() { Person person = new Person(); person.setName("王五"); person.setAge(28); person.setGender("男"); return person; } }获取getbean实例基于上述容器内容通过 Bean 名称获取:System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean("person");通过 Bean 类型获取:System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean(Person.class);通过 Bean 名称和类型获取:System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean("lisi", Person.class));通过类型获取所有 Bean 实例:ioc.getBeansOfType(Person.class).forEach((name, person) -> System.out.println(name + " = " + person));注意:如果不慎在创建组件时发生了重复,只会将首个创建的组件放入容器异常信息汇总NoSuchBeanDefinitionException当 bean 名称不存在时(getBean(String name))。当指定的 bean 类型不存在时(getBean(Class<T> requiredType))。当指定的 bean 名称和类型都不存在或不匹配时(getBean(String name, Class<T> requiredType))。错误消息示例:No bean named 'person1' availableNo qualifying bean of type 'Person' availableNo bean named 'person1' available of required type 'Person'NoUniqueBeanDefinitionException当容器中存在多个相同类型的 bean 时,使用 getBean(Class<T> requiredType) 会抛出此异常。错误消息示例:No qualifying bean of type 'Person' available: expected single matching bean but found 2: person1, person2Empty Result for getBeansOfTypegetBeansOfType(Class<T> type) 方法不会抛出异常,如果没有找到符合条件的 bean,会返回一个空的 Map。MVC分层注解MVC是常用的Web应用架构这些注解所表示类所在文件夹应该在主程序所在根包下,用这些注解来声明并组件放入容器内部有组件使用@bean组件声明@Controller控制器,用于标识一个类作为控制器。package xin.links.spring.ioc.controller; import org.springframework.stereotype.Controller; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.controller * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 18:43 */ // 示例 @Controller public class UserController { }@Repository数据访问对象注解,用于标识一个类作为数据访问对象(DAO)。package xin.links.spring.ioc.dao; import org.springframework.stereotype.Repository; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.dao * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 18:43 */ // 示例 @Repository public class UserDao { }@Service服务层注解,用于标识一个类作为服务类。package xin.links.spring.ioc.service; import org.springframework.stereotype.Service; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.service * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 18:44 */ // 示例 @Service public class UserService { }@Component通用组件注解,用于标识一个类作为Spring容器管理的组件。javapackage xin.links.spring.ioc.component; import org.springframework.stereotype.Component; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.component * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 20:29 */ @Component public class ComponentTest { }导入第三方配置类导入第三方配置类到容器中作为组件,导入前需要确认配置类的类路径可用假定的三方配置类package outclass; /** * ClassName: SpringFramework * Package: xin.links.spring.outclass * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 20:03 */ public class OutClass { public void sayHello(){ System.out.println("Hello World"); } }导入方式一通过new对象的方式导入package xin.links.spring.ioc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import outclass.OutClass; @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { // ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器 ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args); // 新建对象引入外部配置类 new OutClass().sayHello(); } }导入方式二通过@Import注解导入package xin.links.spring.ioc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Import; import outclass.OutClass; //@Import引入外部配置类 @Import(OutClass.class) @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { // ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器 ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args); ioc.getBean(OutClass.class).sayHello(); } }自定义组件扫描在主程序入口中,默认扫描当前根路径下的组件类如果要获取当前根路径上层文件,需要更改扫描范围假定在xin.links.spring.scan路径下存在ScanTest类,而主程序根路径为 xin.links.spring.ioc那么ScanTest类默认不会被扫描,想要扫描需要借助@ComponentScan注解扩大扫描范围,可以一次指定多个包此外还可以用来只扫描指定注解,排除指定注解,关闭默认注解只扫描的=定义的指定注解,以及懒加载package xin.links.spring.scan; import org.springframework.stereotype.Component; /** * ClassName: SpringFramework * Package: xin.links.spring.scan * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 21:19 */ @Component public class ScanTest { public void test(){ System.out.println("ScanTest"); } }package xin.links.spring.ioc.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Service; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.config * Description: * * @author Jane * @version 1.0 * @since 2024/9/1 20:04 */ @Import(ScanTest.class) @ComponentScan( // 指定要扫描的包 指定路径的所有子包也会被扫描 basePackages = {"xin.links.spring.ioc", "xin.links.spring.scan"}, // 仅包含 @Controller 注解的类 includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class), // 排除所有 @Service 注解的类 excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class), // 关闭默认扫描行为,只使用 includeFilters 指定的过滤规则 useDefaultFilters = false, // 延迟初始化所有的组件(即懒加载) lazyInit = true ) @Configuration public class MainConfig { } @scope(作用域)用于控制 Spring 管理的 bean 的生命周期和可见性Singleton这是默认的作用域,Spring 容器中只会创建一个 bean 实例,该实例在容器的整个生命周期内唯一。适用于线程安全的、状态无关的对象。 跟随容器创建,可以使用@Lazy注解实现懒加载@Component @Scope("singleton") public class MySingletonBean { // 单例 bean 的代码 }Prototype每次请求都会创建一个新的 bean 实例,即使在同一个容器中也会有多个实例。适用于需要每次都获取新实例的情况。@Component @Scope("prototype") public class MyPrototypeBean { // 原型 bean 的代码 }Request(了解)在每个 HTTP 请求中,Spring 会创建一个新的 bean 实例。该作用域主要用于 Web 应用程序。适用于每次 HTTP 请求都需要新实例的情况。@Component @Scope("request") public class MyRequestBean { // 请求作用域 bean 的代码 }Session(了解)在每个 HTTP 会话中,Spring 会创建一个新的 bean 实例。每个用户的会话都有自己独立的 bean 实例。适用于在用户会话中共享数据的情况。@Component @Scope("session") public class MySessionBean { // 会话作用域 bean 的代码 }Application (了解)与 singleton 类似,但它的作用域与 Spring 容器的生命周期相同,整个应用程序范围内共享 bean 实例。通常用于 Java EE 应用程序。@Component @Scope("application") public class MyApplicationBean { // 应用作用域 bean 的代码 }WebSocket(了解)在 WebSocket 环境中,Spring 会为每个 WebSocket 会话创建一个新的 bean 实例。适用于 WebSocket 连接中的 bean。@Component @Scope("websocket") public class MyWebSocketBean { // WebSocket 作用域 bean 的代码 }FactoryBean涉及到复杂的对象需要动态创建,可以使用 FactoryBean 来实现在容器中的类型取决于泛型指定的类型,组件名为工厂类的名称import org.springframework.beans.factory.FactoryBean; public class MyComplexObjectFactoryBean implements FactoryBean<MyComplexObject> { // 返回创建的 bean 实例 @Override public MyComplexObject getObject() throws Exception { // 创建并配置 MyComplexObject 实例 MyComplexObject obj = new MyComplexObject(); // 进行一些复杂的配置 return obj; } // 返回创建的 bean 实例的类型 @Override public Class<?> getObjectType() { return MyComplexObject.class; } @Override public boolean isSingleton() { //true或 false,取决于你的需求,true为单实例 false为多实例(即原型作用域) return true; } }在 配置中类使用 FactoryBean@Configuration public class AppConfig { @Bean public FactoryBean<MyComplexObject> myComplexObjectFactoryBean() { return new MyComplexObjectFactoryBean(); } }@Conditional条件注册@Conditional基本注解,用于根据一个自定义的条件类来决定是否创建一个 Bean。你可以实现自己的条件逻辑,例如检查某些属性或环境变量是否满足特定条件。 使用场景:需要自定义复杂的条件逻辑时。@ConditionalOnProperty根据某个配置属性(如 application.properties 中的属性)是否存在或具有特定值来决定是否创建一个 Bean。 使用场景:需要根据外部配置(如系统属性或配置文件中的属性)来控制 Bean 的创建。@ConditionalOnClass只有在类路径中存在指定类时才创建 Bean。它检查的是编译期类路径中是否存在特定类的定义。 使用场景:需要在某个第三方库存在时自动配置相关的 Bean。@ConditionalOnMissingBean只有在上下文中缺少指定名称或类型的 Bean 时才创建新的 Bean。 使用场景:提供默认的 Bean 配置,当没有其他符合条件的 Bean 时使用。@ConditionalOnBean只有在上下文中存在指定名称或类型的 Bean 时才创建新的 Bean。 使用场景:需要依赖其他 Bean 存在的条件下才创建当前 Bean,例如在依赖链中使用。@ConditionalOnResource只有在类路径中存在指定的资源(如配置文件)时才创建 Bean。 使用场景:需要根据外部资源(如文件或配置)的存在与否来控制 Bean 的创建。@Profile只有在指定的 Spring 环境 Profile 激活时才创建 Bean,例如开发环境(development)或生产环境(production)。 使用场景:根据不同环境(如开发、测试、生产)来控制 Bean 的创建。@ConditionalOnMissingClass只有在类路径中缺少指定类时才创建 Bean。 使用场景:在某个类不可用时提供备用实现。组件关键属性名称(Name)Bean 在 Spring 容器中的唯一标识符,通常用于通过名称查找 Bean。可以在配置时显式指定(例如通过 @Component("myBean") 或 <bean id="myBean">),如果未指定则默认使用类名的首字母小写形式。类型(Type)Bean 的类型,即 Bean 所对应的 Java 类。这决定了 Bean 的行为、方法和属性。实例(Instance)Bean 在 Spring 容器中的实际对象引用。实例是 Spring Bean 的具体对象,每次调用 getBean 方法获取的就是这个实例,具体实例的数量由作用域(Scope)属性决定。作用域(Scope)Bean 的作用域,定义了 Bean 实例的创建方式和生命周期。常见的作用域有:单实例(Singleton)在整个 Spring 容器中共享一个实例(默认),多实例(Prototype)每次请求时创建一个新的实例,Web 作用域如 Request(每个 HTTP 请求一个实例)和 Session(每个会话一个实例)。生命周期回调(Lifecycle Callbacks)Bean 在创建和销毁时调用的方法,定义了 Bean 的生命周期管理逻辑。例如,通过 @PostConstruct 和 @PreDestroy 注解的方法。依赖关系(Dependencies)定义 Bean 之间的依赖关系,通过构造器注入、Setter 注入或字段注入的方式进行管理。懒加载(Lazy Initialization)指的是在 Spring 容器启动时,不立即创建 Bean 实例,而是在第一次被请求或需要时才进行实例化。懒加载可以优化启动性能,减少内存占用,避免在启动时立即加载不必要的 Bean。组件注入@Autowired(重点)Spring 的核心注解之一,用于自动注入依赖。可以用于构造器、字段(属性)、setter 方法。默认情况下按类型自动注入,如果有多个同类型 Bean 可以配合 @Qualifier 或 @Primary 使用。//通过设置required属性用于指定在自动注入时,是否跳过注入,false状态下,依赖的 Bean 就会保持 null @Autowired(required = false) @Qualifier配合 @Autowired 使用,用于指定注入 Bean 的名称。当有多个相同类型的 Bean 时,可以用 @Qualifier 来明确指定要注入的具体 Bean。@Primary用于标记一个默认的首选 Bean,当有多个相同类型的 Bean 可供注入时,优先选择带有 @Primary 注解的那个。@Resource Java 的标准注解(JSR-250),功能类似 @Autowired,但默认按名称注入。可以通过 name 或 type 属性进行指定。它是 Java EE 提供的注解,Spring 也支持它来进行依赖注入。Setter 方法注入通过 setter 方法注入依赖对象。Spring 会调用这些方法来设置依赖。public class CalculatorService { private Calculator calculator; @Autowired public void setCalculator(Calculator calculator) { this.calculator = calculator; } }构造器注入(重点)通过类的构造函数来注入依赖。适用于依赖项为不可变对象(final)或依赖关系必须在对象创建时就提供的情况。public class CalculatorService { private final Calculator calculator; @Autowired public CalculatorService(Calculator calculator) { this.calculator = calculator; } }xxxAwareSpring 提供的注解接口(例如 ApplicationContextAware, BeanFactoryAware 等),当实现这些接口时,Spring 会注入相关的容器对象(如 ApplicationContext 或 BeanFactory)到你的 Bean 中,通常用于获取 Spring 容器自身的资源。感知接口(Aware Interface)在 Spring 中是一种特殊的机制,允许 Bean “感知”到 Spring 容器的内部资源,如 ApplicationContext、BeanFactory、ServletContext 等。通过实现这些接口,Bean 可以直接访问这些容器对象或资源,从而与 Spring 的底层框架进行更紧密的集成。ApplicationContextAware 使 Bean 能够感知到 ApplicationContext(即 Spring 容器上下文),从而可以访问其他 Bean、发布事件等。BeanFactoryAware 使 Bean 能够感知到 BeanFactory(Spring 容器的基础实现之一),可以直接访问和操作 Bean 的工厂方法。EnvironmentAware 使 Bean 能够感知到 Environment 对象,可以访问当前环境的属性和配置(如系统属性、环境变量等)。ResourceLoaderAware 使 Bean 能够感知到 ResourceLoader,从而可以用来加载资源文件(如类路径下的文件、URL 资源等)。ServletContextAware 使 Bean 能够感知到 ServletContext(Web 应用程序上下文),仅适用于 Web 应用。实现感知接口非常简单,只需要让你的 Bean 类去实现相应的接口,并覆盖接口中定义的方法即可。例如,下面的类可以感知到 ApplicationContext 并在启动时打印所有 Bean 的名称。import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class MyApplicationContextAwareBean implements ApplicationContextAware { private ApplicationContext applicationContext; // 实现接口中的方法,当 Bean 被创建时,Spring 会调用这个方法 @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; System.out.println("所有 Bean 名称如下:"); for (String beanName : applicationContext.getBeanDefinitionNames()) { System.out.println(beanName); } } }注意事项虽然感知接口提供了对 Spring 内部机制的强大访问能力,但过度使用它们可能会导致你的代码和 Spring 框架之间的耦合变得过于紧密。理想情况下,应该尽量使用依赖注入(DI)来获得 Bean 的依赖,而不是直接访问容器本身,这样可以保持更好的解耦和模块化设计。感知接口的使用是一把双刃剑,应谨慎使用,确保你的设计不因此变得复杂和难以维护。@Value(重点)用于将外部化的值(如配置文件中的值)注入到 Bean 的属性中。可以直接在字段、构造函数或 setter 方法上使用,支持 SpEL 表达式语言。@Value("${app.name}") private String appName;SpEL 一种强大的表达式语言(Spring Expression Language),可以在 Spring 中用于注入复杂的值,如计算表达式、方法调用、正则表达式匹配等。通常与 @Value 注解一起使用。@Value("#{1 + 2}") // 注入计算结果 3 private int sum; @Value("#{systemProperties['user.name']}") // 注入系统属性 "user.name" private String userName; @Value("#{T(java.util.UUID).randomUUID().toString()}") // 注入UUID private String uuid;@PropertySource用于加载外部属性文件到 Spring 的 Environment 中,以便通过 @Value 注解访问这些属性。@Configuration @PropertySource("classpath:application.properties") public class AppConfig { }@Profile(重点) 用于定义 Bean 在特定环境(Profile)下才会被创建。可以用在类或方法上,通常用于区分开发、测试、生产环境的配置。@Configuration @Profile("development") public class DevConfig { }组件生命周期示例说明(创建User组件,调试输出各个生命周期)可以使用@Bean底层提供的初始化销毁方法,也可以使用InitializingBean, DisposableBean或者是@PostConstruct可以在构造器之后执行(后置处理钩子)@PreDestroy可以在接口实现的销毁方法前执行,实现销毁预处理@BeanPostProcessor可以在初始化之前和初始化之后分别操作生命周期执行流程package xin.links.spring.ioc.bean; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.Data; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.bean * Description: * * @author Jane * @version 1.0 * @since 2024/9/2 20:22 */ @Data public class User implements InitializingBean, DisposableBean { private String username; private String password; private Person person; @Autowired public void setPerson(Person person) { this.person = person; System.out.println("自动注入Person"); } public User() { System.out.println("User无参构造函数"); } public void initUser(){ System.out.println("User初始化方法"); } public void destroyUser(){ System.out.println("User销毁方法"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("User接口实现初始化方法"); } @Override public void destroy() throws Exception { System.out.println("User接口实现销毁方法"); } @PostConstruct public void initUserFun(){ System.out.println("User注解后置处理钩子"); } @PreDestroy public void destroyUserFun(){ System.out.println("User注解销毁预处理"); } } package xin.links.spring.ioc.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import xin.links.spring.ioc.bean.User; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.config * Description: * * @author Jane * @version 1.0 * @since 2024/9/2 20:22 */ //创建配置类,将组件放入容器 @Configuration public class UserConfig { // bean注解在底层提供了声明周期方法 // initMethod = "init" 指定初始化方法 // destroyMethod = "destroy" 指定销毁方法 // @Bean(initMethod = "init",destroyMethod = "destroy") // 1.参数中的名字为组件中的方法名 // 2.可以指定bean的id,如果不指定,默认使用方法名作为id // 3.可以指定bean的初始化方法和销毁方法 // 还可以使用实现InitializingBean, DisposableBean接口的方案初始化和销毁 @Bean(initMethod = "initUser",destroyMethod = "destroyUser") public User user(){ return new User(); } } package xin.links.spring.ioc.PostProcessor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; import xin.links.spring.ioc.bean.User; /** * ClassName: SpringFramework * Package: xin.links.spring.ioc.PostProcessor * Description: * * @author Jane * @version 1.0 * @since 2024/9/2 20:32 */ @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanName = " + beanName + ",bean = " + bean); System.out.println("Bean初始化之前执行");; return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanName = " + beanName + ",bean = " + bean); System.out.println("Bean初始化之后执行"); if (bean instanceof User user) { user.setUsername("张三"); } return bean; } } package xin.links.spring.ioc; import lombok.ToString; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import xin.links.spring.ioc.bean.User; @SpringBootApplication public class SpringIocApplication { public static void main(String[] args) { // ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器 ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args); System.out.println("IOC容器创建完成"); System.out.println("ioc.getBean(User.class) = " + ioc.getBean(User.class)); // User组件生命周期的执行顺序 // 1.构造方法 // 2.属性赋值(自动注入) // 3.初始化前 后置处理器 // 4.注解初始化方法 // 5.接口初始化方法 // 6.初始化方法 // 7.初始化后 后置处理器 // 8.使用 // 9.销毁预处理 // 10.接口销毁方法 // 11.销毁方法 } }@Autowired的实现@Autowired注解是Spring框架中用于自动装配bean的注解。它可以通过在构造函数、属性、setter方法上使用,来自动注入依赖项。@Autowired注解的工作原理是自动寻找匹配的bean,并将其注入到使用@Autowired注解的变量、方法或构造函数中。@Autowired注解的实现主要依赖于AutowiredAnnotationBeanPostProcessor这个后处理器。当Spring容器创建Bean的时候,会调用BeanPostProcessor的postProcessBeforeInitialization方法。在这个过程中,AutowiredAnnotationBeanPostProcessor会检查当前Bean的属性上是否标注了@Autowired注解。如果发现@Autowired注解,就会通过反射的方式,获取属性对应的类型,然后在Spring容器中查找匹配的Bean,并将其注入到当前Bean的属性中。具体来说,AutowiredAnnotationBeanPostProcessor会遍历当前Bean的所有属性,对于每个属性,它会检查是否标注了@Autowired注解。如果标注了,就会通过TypeFilter匹配器,根据属性类型在Spring容器中查找匹配的Bean。找到之后,就会通过BeanFactory将这个Bean注入到当前属性中。步骤简要描述:扫描含有@Autowired注解的属性根据属性类型查找Spring容器中的匹配Bean将找到的Bean注入到含有@Autowired注解的属性中自定义UUID注解Person类有一个uuid属性,它被UUID注解标记UUIDProcess类是一个自定义的`BeanPostProcessorPersonConfig类是一个配置类,用于声明Person beanPerson类有一个uuid属性,它被UUID注解标记。在Spring容器初始化Person bean之前,UUIDProcess为Person bean中带有UUID注解的String类型字段生成一个随机的UUID并赋值。Person 类package xin.links.spring.ioc.bean; import lombok.Data; import xin.links.spring.ioc.annotation.UUID; @Data public class Person { private String name; private int age; private String gender; @UUID private String uuid; }PersonConfig 类package xin.links.spring.ioc.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import xin.links.spring.ioc.bean.Person; @Configuration public class PersonConfig { @Bean @ConditionalOnMissingBean(name = "UserService") public Person person() { Person person = new Person(); person.setName("张三"); person.setAge(22); person.setGender("男"); return person; } }UUID 注解package xin.links.spring.ioc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface UUID { }UUIDProcess 类package xin.links.spring.ioc.annotation; import lombok.SneakyThrows; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; import java.lang.reflect.Field; /** * 在Bean初始化之前对Bean进行后置处理。 * * @param bean 要处理的Bean实例 * @param beanName Bean的名称 * @return 处理后的Bean实例 */ @Override @SneakyThrows public Object postProcessBeforeInitialization(Object bean, String beanName) { // 获取Bean的所有字段 Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { // 如果字段上有UUID注解且类型为String if (field.isAnnotationPresent(UUID.class) && field.getType().equals(String.class)) { // 设置字段为可访问,以便进行赋值操作 field.setAccessible(true); // 生成一个随机的UUID字符串 String s = java.util.UUID.randomUUID().toString(); // 将生成的UUID字符串赋值给字段 field.set(bean, s); } } return bean; }
2024年09月06日
4 阅读
0 评论
0 点赞
2024-08-31
MVC模型
MVC(Model-View-Controller)是一种常见的设计模式,用于构建结构化、模块化的应用程序。MVC 模型通过将程序分为三层:模型(Model)、视图(View)和控制器(Controller),来实现职责的分离。以一个“餐饮”的例子来解释 MVC。1. Model:(类比厨房)模型(Model)是核心逻辑层,负责处理数据和业务逻辑。在我们的餐饮店例子中,模型就像厨房里的厨师。厨师们负责制作餐食(处理数据)、配料选择(业务逻辑)等。厨师根据菜单和顾客要求(数据)做出决定。厨房是不直接和顾客打交道的(类似于 Model 不直接与用户界面互动),他们只负责制作餐食,把制作好的餐食交给服务员即可。厨房不会关心餐食要送给哪个顾客,服务员(Controller)会处理这部分。2. View:(类比餐桌)视图(View)是用户看到的界面部分。在餐饮店的例子中,视图就像顾客的餐桌。餐桌上摆放着菜单(用户界面),顾客通过菜单选择想要的餐食(查看和操作数据)。服务员将制作好的餐食端上餐桌(将数据传递给视图)。餐桌不会关心餐食是怎么做出来的,也不会关心它从哪里来,它的工作只是展示餐食(显示数据)。3. Controller:(类比服务员)控制器(Controller)是连接视图和模型的桥梁,它就像餐厅里的服务员。在餐饮店中,顾客(用户)会把他们的订单(请求)告诉服务员(Controller)。服务员将订单送到厨房(Model),然后等厨师(Model)做出餐食(处理数据)。当餐食做好后,服务员会把餐食送到顾客的桌子上(View)。如果顾客有任何问题,服务员会根据要求来调整厨房的操作(更改数据逻辑)。服务员不需要知道具体的做法(具体业务逻辑),他只需要将顾客的需求转达给厨房,并把制作好的餐食送给顾客就行。MVC 流程总结顾客(用户):代表用户发出请求,比如要求点餐。服务员(Controller):接收顾客的请求,把订单(请求)送到厨房,并将结果(餐食)送回顾客。厨房(Model):根据订单(请求)制作餐食(处理数据和业务逻辑),完成后通知服务员。餐桌(View):展示餐食(显示数据)给顾客。可以想象一下当顾客抱怨餐食太咸了时,服务员(Controller)可能会立即通知厨师(Model)改进配方,同时告诉其他顾客(View)稍等一下。这就相当于当用户在前端操作页面时,触发某些动作(如提交表单),控制器会去执行一些逻辑(如修改数据库),并立即更新用户界面。这样一来,通过 MVC 的分层模型,每个角色各司其职,餐食店运行井井有条,不管是上菜、做菜还是点菜,都有了明确的分工,互不干扰。无论菜单(数据)如何变化,服务员(控制器)如何调整上菜速度,厨房(数据逻辑)如何改变,顾客(用户)都能享受到美味的餐食!
2024年08月31日
4 阅读
0 评论
0 点赞
2024-08-26
NPM 的路径配置
NPM 是 Node.js 的包管理器,用于管理 JavaScript 包和依赖。在使用 NPM 时,通常需要配置以下两个主要路径:全局安装路径(prefix):NPM 全局安装的包会存放在这个路径下。缓存路径(cache):NPM 下载的包缓存会存放在这个路径下,以加快后续安装速度。路径配置说明Windows 系统设置全局安装路径:npm config set prefix "NODEJS路径\node_global"设置缓存路径:npm config set cache "NODEJS路径\node_cache"设置 PATH 环境变量:需要将全局安装路径和 Node.js 的可执行文件路径添加到 PATH 环境变量中。 步骤:打开“系统属性”对话框(Win + Pause > 高级系统设置 > 环境变量)。在“用户变量”或“系统变量”中,找到 PATH,点击“编辑”。添加以下路径:NODEJS路径 NODEJS路径\node_global点击“确定”保存更改。验证配置:打开命令提示符(CMD),执行以下命令验证配置是否成功:node -v npm -vmacOS/Linux 系统设置全局安装路径:npm config set prefix "$HOME/.npm-global"设置缓存路径:npm config set cache "$HOME/.npm-cache"设置 PATH 环境变量:需要将全局安装路径添加到 PATH 环境变量中。在终端中编辑 ~/.bashrc 或 ~/.zshrc 文件(根据使用的 shell 不同):export PATH="$HOME/.npm-global/bin:$PATH"然后使配置生效:source ~/.bashrc # 或者 source ~/.zshrc验证配置:打开终端,执行以下命令验证配置是否成功:node -v npm -v如何取消配置加载如果你需要取消或重置之前的配置,可以按照以下方式进行:Windows 系统:打开命令提示符(CMD),执行以下命令:npm config delete prefix npm config delete cache打开“系统属性”对话框,删除 PATH 环境变量中与 NPM 相关的路径。MacOS/Linux 系统:编辑 ~/.npmrc 文件,删除或注释掉 prefix 和 cache 配置:# prefix=$HOME/.npm-global # cache=$HOME/.npm-cache编辑 ~/.bashrc 或 ~/.zshrc 文件,删除 export PATH 的相关配置。项目级 NPM 配置NPM 允许在项目级别配置特定的 NPM 设置,这样不同项目可以使用不同的 NPM 配置。在项目根目录下创建 .npmrc 文件,并配置你希望为该项目设置的选项。例如:prefix=./.npm-global cache=./.npm-cache当你在该项目中执行 NPM 命令时,它将优先使用 .npmrc 中的配置。常见问题和解决方案NPM 全局命令无法找到:检查 PATH 环境变量是否正确配置,并确保它包含 npm global 安装路径。npm install 速度慢:可以配置淘宝 NPM 镜像源以加速安装:npm config set registry https://registry.npm.taobao.org如何查看当前 NPM 配置:使用以下命令查看当前配置:npm config list
2024年08月26日
4 阅读
0 评论
0 点赞
2024-08-25
使用 `fnm` 管理 Node.js 版本
通用步骤项目级 Node.js 版本控制:在项目根目录创建 .nvmrc 文件或 .node-version 文件,指定项目所需的 Node.js 版本。Windows 系统配置 PowerShell 加载 fnm(可选):PowerShell 配置文件路径由 $PROFILE 变量指定。可以在 PowerShell 中运行以下命令查看具体路径:echo $PROFILE常见路径如下:Windows PowerShell:C:\Users\你的用户名\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1PowerShell Core (pwsh):C:\Users\你的用户名\Documents\PowerShell\Microsoft.PowerShell_profile.ps1打开配置文件,你可以在 PowerShell 中使用以下命令:notepad $PROFILE在文件中添加以下行来自动加载 fnm:fnm env --use-on-cd | Out-String | Invoke-Expression如果你不希望自动加载 fnm,找到这行代码并将其删除或注释掉(用 # 注释)。保存文件后,重新启动 PowerShell,fnm 将不再自动加载。VSCode 终端验证:在 VSCode 设置中,确保终端使用的是 PowerShell,并且 PowerShell 已配置好自动加载 fnm。在 VSCode 终端中运行以下命令验证:node -v npm -v输出的版本号应与项目中 .nvmrc 文件指定的版本相符。项目级 Node.js 版本控制:在项目根目录创建 .nvmrc 文件,指定 Node.js 版本号,例如:20进入项目目录时,fnm 会自动切换到指定版本,或者手动运行 fnm use 切换版本。系统的 PowerShell 执行策略不允许运行脚本解决方法查看当前的执行策略首先,查看当前的执行策略,以了解是否需要更改。Get-ExecutionPolicy -List这会显示在系统范围内和用户范围内的执行策略。修改执行策略如果执行策略限制了脚本运行,可以将其修改为允许运行脚本。推荐的策略是 RemoteSigned,它允许本地脚本运行,但对从互联网下载的脚本进行签名检查。全局修改执行策略(需要管理员权限):Set-ExecutionPolicy RemoteSigned -Scope LocalMachine仅为当前用户修改执行策略:Set-ExecutionPolicy RemoteSigned -Scope CurrentUser确认执行策略已更改运行以下命令,确认执行策略已成功更改:Get-ExecutionPolicy -List确保 LocalMachine 或 CurrentUser 的策略设置为 RemoteSigned。重新加载 PowerShell 配置文件执行以下命令来重新加载 PowerShell 配置文件:. $PROFILEmacOS 系统配置终端加载 fnm(可选):根据你使用的终端类型,配置文件路径如下:Bash:配置文件路径通常是:/Users/你的用户名/.bashrc可以通过以下命令打开:nano ~/.bashrcZsh:配置文件路径通常是:/Users/你的用户名/.zshrc可以通过以下命令打开:nano ~/.zshrcFish:配置文件路径通常是:/Users/你的用户名/.config/fish/config.fish可以通过以下命令打开:nano ~/.config/fish/config.fish在相应的配置文件中添加以下行来自动加载 fnm:Bash/Zsh:eval "$(fnm env --use-on-cd)"Fish:fnm env --use-on-cd | source如果你不希望自动加载 fnm,找到这行代码并将其删除或注释掉(用 # 注释或删除行)。保存文件后,重新启动终端,fnm 将不再自动加载。VSCode 终端验证:在 VSCode 设置中,确保终端使用的是已配置的 Shell(Bash、Zsh 或 Fish)。在 VSCode 终端中运行以下命令验证:node -v npm -v输出的版本号应与项目中 .nvmrc 文件指定的版本相符。项目级 Node.js 版本控制:在项目根目录创建 .nvmrc 文件,写入所需的 Node.js 版本号,例如:20fnm 会自动切换到该版本,或手动运行 fnm use 切换版本。Linux 系统配置终端加载 fnm(可选):根据你使用的终端类型,配置文件路径如下:Bash:配置文件路径通常是:/home/你的用户名/.bashrc可以通过以下命令打开:nano ~/.bashrcZsh:配置文件路径通常是:/home/你的用户名/.zshrc可以通过以下命令打开:nano ~/.zshrcFish:配置文件路径通常是:/home/你的用户名/.config/fish/config.fish可以通过以下命令打开:nano ~/.config/fish/config.fish在相应的配置文件中添加以下行来自动加载 fnm:Bash/Zsh:eval "$(fnm env --use-on-cd)"Fish:fnm env --use-on-cd | source如果你不希望自动加载 fnm,找到这行代码并将其删除或注释掉(用 # 注释或删除行)。保存文件后,重新启动终端,fnm 将不再自动加载。VSCode 终端验证:在 VSCode 设置中,确保终端使用的是已配置的 Shell(Bash、Zsh 或 Fish)。在 VSCode 终端中运行以下命令验证:node -v npm -v输出的版本号应与项目中 .nvmrc 文件指定的版本相符。项目级 Node.js 版本控制:在项目根目录创建 .nvmrc 文件,写入所需的 Node.js 版本号,例如:20手动运行 fnm use 切换到指定版本,或让 fnm 自动切换。配置加载的必要性配置加载是可选的。它能够自动化管理 Node.js 版本,减少手动操作,特别是在多个项目之间频繁切换时。如果你希望完全手动管理和切换版本,可以不配置加载,直接在需要时使用 fnm use 命令。
2024年08月25日
3 阅读
0 评论
0 点赞
2024-08-25
Node.js 版本管理工具-fnm
fnm 是一个快速、轻量级的 Node.js 版本管理工具,用于安装和管理不同版本的 Node.js。它的设计目标是提供快速的版本切换,并支持多平台,包括 Windows、macOS 和 Linux。安装 fnm首先,你需要在系统上安装 fnm。可以通过以下方式进行安装:使用 winget(Windows 用户):winget install Schniz.fnm使用 curl(适用于 macOS 和 Linux 用户):curl -fsSL https://fnm.vercel.app/install | bash或者参考 fnm 官方安装文档 进行安装。基本命令查看已安装的 Node.js 版本:fnm list列出所有可用的 Node.js 版本:fnm ls-remote安装指定版本的 Node.js:fnm install 20这将安装 Node.js 的 20.x 版本。切换到指定版本的 Node.js:fnm use 20这将切换到已安装的 Node.js 20.x 版本。卸载某个 Node.js 版本:fnm uninstall 20这将卸载 Node.js 20.x 版本。设置默认的 Node.js 版本:fnm default 20这将设置 Node.js 20.x 版本为默认版本。使用 .node-version 文件fnm 支持 .node-version 文件或 .nvmrc 文件。你可以在项目目录中创建这个文件,并写入所需的 Node.js 版本号。每次进入该目录时,fnm 会自动切换到文件中指定的 Node.js 版本。例如:20环境变量和配置在你的终端配置文件(例如 .bashrc, .zshrc, .profile)中添加 fnm 环境变量的配置:eval "$(fnm env)"这将确保你的终端会话中始终使用 fnm 管理的 Node.js 版本。自动加载 Node.js 版本使用 fnm env --use-on-cd 命令,你可以在每次切换目录时自动切换到对应的 Node.js 版本。在终端配置文件中添加以下内容:fnm env --use-on-cd | Out-String | Invoke-Expression
2024年08月25日
6 阅读
0 评论
0 点赞
2024-08-25
Cookie,Session,三大域对象,过滤器,监听器
Cookie 与 SessionCookieCookie 是服务器发送到客户端并存储在客户端的一小段数据,用于识别用户或保存用户偏好设置等信息。设置 Cookieimport jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/servletCookie") public class servletCookie extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 创建一个 Cookie Cookie cookie = new Cookie("ZHANGSAN", "001"); // 设置 Cookie 的路径 cookie.setPath("/"); // 添加 Cookie 到响应中 resp.addCookie(cookie); } }SessionSession 用于在服务器端存储每个用户的会话数据,通常使用 HttpSession 来管理。使用 Sessionimport jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/servletSession") public class servletSession extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取或创建 Session HttpSession session = req.getSession(); // 设置属性 session.setAttribute("user", "Alice"); // 获取属性 String user = (String) session.getAttribute("user"); // 设置响应内容类型为 HTML resp.setContentType("text/html;charset=utf-8"); // 向客户端返回响应内容 resp.getWriter().write("<html><body>"); resp.getWriter().write("<h1>User: " + user + "</h1>"); resp.getWriter().write("</body></html>"); } }三大域对象在 Servlet 中,三大域对象用于在不同组件之间共享数据:ServletContext:表示整个 Web 应用程序的上下文,适用于在不同的 Servlet 和 JSP 页面之间共享数据。HttpSession:表示用户的会话,适用于在用户会话期间共享数据。HttpServletRequest:表示单个 HTTP 请求,适用于在同一个请求范围内共享数据。设置三大域对象import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/setScope") public class setScope extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置请求域对象 req.setAttribute("user", "zhangSan"); // 设置应用域对象 this.getServletContext().setAttribute("user", "wangWu"); // 设置 session 域对象 req.getSession().setAttribute("user", "sushi"); // 设置响应内容类型 resp.setContentType("text/html;charset=utf-8"); // 向客户端返回响应内容 resp.getWriter().write("设置三大域对象成功<br>"); resp.getWriter().write("请求域对象:" + req.getAttribute("user") + "<br>session域对象:" + req.getSession().getAttribute("user") + "<br>应用域对象:" + req.getServletContext().getAttribute("user")); } }获取三大域对象import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/getScope") public class getScope extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应内容类型 resp.setContentType("text/html;charset=utf-8"); // 获取并展示三大域对象的值 resp.getWriter().write("获取三大域对象成功<br>"); resp.getWriter().write("请求域对象:" + req.getAttribute("user") + "<br>session域对象:" + req.getSession().getAttribute("user") + "<br>应用域对象:" + req.getServletContext().getAttribute("user")); } }过滤器 (Filter)过滤器用于在请求到达 Servlet 或 JSP 页面之前,对请求进行预处理,或者在响应发送到客户端之前对响应进行后处理。创建 Filterimport jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化 Filter } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 前处理逻辑 System.out.println("Request is being processed"); // 继续执行请求 chain.doFilter(request, response); // 后处理逻辑 System.out.println("Response is being processed"); } @Override public void destroy() { // 清理资源 } }使用注解配置 Filter可以使用 @WebFilter 注解来配置过滤器,无需在 web.xml 文件中进行配置。import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; @WebFilter("/*") // 过滤所有请求 public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化 Filter } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 前处理逻辑 System.out.println("Request is being processed"); // 继续执行请求 chain.doFilter(request, response); // 后处理逻辑 System.out.println("Response is being processed"); } @Override public void destroy() { // 清理资源 } }在 web.xml 中配置 Filter如果不使用注解,可以在 web.xml 文件中配置过滤器:<filter> <filter-name>MyFilter</filter-name> <filter-class>com.example.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>监听器 (Listener)监听器用于监听 Web 应用程序的生命周期事件,如创建和销毁 Session、ServletContext 等,还可以监听属性的变化。ServletContextListenerServletContextListener 用于监听 ServletContext 的创建和销毁。import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; import jakarta.servlet.annotation.WebListener; @WebListener public class MyServletContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // ServletContext 初始化时触发 System.out.println("ServletContext 初始化"); } @Override public void contextDestroyed(ServletContextEvent sce) { // ServletContext 销毁时触发 System.out.println("ServletContext 销毁"); } }ServletContextAttributeListenerServletContextAttributeListener 用于监听 ServletContext 属性的添加、移除和替换。import jakarta.servlet.ServletContextAttributeEvent; import jakarta.servlet.ServletContextAttributeListener; import jakarta.servlet.annotation.WebListener; @WebListener public class MyServletContextAttributeListener implements ServletContextAttributeListener { @Override public void attributeAdded(ServletContextAttributeEvent event) { // 属性添加时触发 System.out.println("属性添加: " + event.getName() + " = " + event.getValue()); } @Override public void attributeRemoved(ServletContextAttributeEvent event) { // 属性移除时触发 System.out.println("属性移除: " + event.getName()); } @Override public void attributeReplaced(ServletContextAttributeEvent event) { // 属性替换时触发 System.out.println("属性替换: " + event.getName() + " = " + event.getValue()); } }HttpSessionListenerHttpSessionListener 用于监听 HttpSession 的创建和销毁。import jakarta.servlet.http.HttpSessionEvent; import jakarta.servlet.http.HttpSessionListener; import jakarta.servlet.annotation.WebListener; @WebListener public class MyHttpSessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { // Session 创建时触发 System.out.println("Session 创建"); } @Override public void sessionDestroyed(HttpSessionEvent se) { // Session 销毁时触发 System.out.println("Session 销毁"); } }HttpSessionAttributeListenerHttpSessionAttributeListener 用于监听 HttpSession 属性的添加、移除和替换。import jakarta.servlet.http.HttpSessionAttributeListener; import jakarta.servlet.http.HttpSessionBindingEvent; import jakarta.servlet.annotation.WebListener; @WebListener public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { @Override public void attributeAdded(HttpSessionBindingEvent event) { // Session 属性添加时触发 System.out.println("Session 属性添加: " + event.getName() + " = " + event.getValue()); } @Override public void attributeRemoved(HttpSessionBindingEvent event) { // Session 属性移除时触发 System.out.println("Session 属性移除: " + event.getName()); } @Override public void attributeReplaced(HttpSessionBindingEvent event) { // Session 属性替换时触发 System.out.println("Session 属性替换: " + event.getName() + " = " + event.getValue()); } }ServletRequestListenerServletRequestListener 用于监听请求的创建和销毁。import jakarta.servlet.ServletRequestEvent; import jakarta.servlet.ServletRequestListener; import jakarta.servlet.annotation.WebListener; @WebListener public class MyServletRequestListener implements ServletRequestListener { @Override public void requestInitialized(ServletRequestEvent sre) { // 请求创建时触发 System.out.println("请求创建"); } @Override public void requestDestroyed(ServletRequestEvent sre) { // 请求销毁时触发 System.out.println("请求销毁"); } }ServletRequestAttributeListenerServletRequestAttributeListener 用于监听请求属性的添加、移除和替换。import jakarta.servlet.ServletRequestAttributeListener; import jakarta.servlet.ServletRequestAttributeEvent; import jakarta.servlet.annotation.WebListener; @WebListener public class MyServletRequestAttributeListener implements ServletRequestAttributeListener { @Override public void attributeAdded(ServletRequestAttributeEvent event) { // 请求属性添加时触发 System.out.println("请求属性添加: " + event.getName() + " = " + event.getValue()); } @Override public void attributeRemoved(ServletRequestAttributeEvent event) { // 请求属性移除时触发 System.out.println("请求属性移除: " + event.getName()); } @Override public void attributeReplaced(ServletRequestAttributeEvent event) { // 请求属性替换时触发 System.out.println("请求属性替换: " + event.getName() + " = " + event.getValue()); } }
2024年08月25日
6 阅读
0 评论
0 点赞
2024-08-23
服务器端组件Servlet
Servlet 是 Java 中用于创建动态 Web 内容的服务器端组件。它运行在 Web 服务器上,可以响应客户端请求(通常是 HTTP 请求)。Servlet 是 Java EE 标准的一部分,通常用于处理表单提交、动态生成网页等。Servlet 的生命周期Servlet 生命周期由以下几个阶段组成:加载与实例化:Servlet 容器加载 Servlet 类并创建其实例。初始化:调用 init() 方法来初始化 Servlet,仅在 Servlet 实例创建时调用一次。处理请求:调用 service() 方法来处理客户端请求,方法内根据请求类型调用 doGet() 或 doPost() 等方法。销毁:调用 destroy() 方法,在 Servlet 被卸载或服务器关闭时调用。public class MyServlet extends HttpServlet { public void init() throws ServletException { // 初始化代码 } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 处理 GET 请求 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 处理 POST 请求 } public void destroy() { // 销毁时的清理代码 } }处理 HTTP 请求通过 HttpServletRequest 和 HttpServletResponse 对象,可以轻松获取请求信息并生成响应内容。使用 service() 方法处理请求service() 方法根据请求类型自动分派到 doGet()、doPost() 等方法。@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (req.getMethod().equals("GET")) { // 处理 GET 请求 resp.getWriter().write("GET success"); } else if (req.getMethod().equals("POST")) { // 处理 POST 请求 resp.getWriter().write("POST success"); } }使用 doGet() 和 doPost() 方法处理请求通常通过覆盖 doGet() 或 doPost() 方法来分别处理 GET 或 POST 请求。处理 GET 请求示例@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应的内容类型 resp.setContentType("text/html"); // 响应输出内容 resp.getWriter().write("GET success"); }处理 POST 请求示例java复制代码@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求参数 String param = req.getParameter("param"); // 设置响应的内容类型 resp.setContentType("text/html"); // 响应输出内容 resp.getWriter().write("POST success. Received param: " + param); }请求转发(Request Forwarding)请求转发将请求从一个 Servlet 转发到另一个 Servlet 或 JSP 页面进行处理,转发在服务器内部完成,客户端不会察觉到。示例:请求转发java复制代码@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置请求属性 req.setAttribute("message", "This is a forwarded request."); // 转发请求到另一个 Servlet 或 JSP 页面 req.getRequestDispatcher("/targetServlet").forward(req, resp); }重定向(Redirect)重定向是将客户端的请求重定向到另一个 URL,服务器会发送一个响应指示客户端浏览器访问新的 URL。示例:重定向@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 重定向到另一个 URL resp.sendRedirect("/newPage.jsp"); }HttpServlet 是处理 HTTP 请求的基础工具,覆盖 service()、doGet() 和 doPost() 方法可以满足大部分需求。请求转发:服务器内部完成,适用于需要在多个组件间共享数据的场景。重定向:服务器告诉客户端浏览器访问新的 URL,适用于需要跳转到另一个页面的场景。
2024年08月23日
5 阅读
0 评论
0 点赞
2024-08-20
Maven 仓库安装配置
Maven 是一个开源的项目管理和构建工具,主要用于 Java 项目。Maven 使用一种名为 POM (Project Object Model) 的文件来管理项目的构建、报告和文档。Maven 还可以自动处理项目的依赖管理。Windows 系统安装 Maven下载 Maven 访问 Apache Maven 官方网站 下载最新版本的 Maven 压缩包(.zip 文件)。解压 Maven 将下载的 .zip 文件解压到一个目录,例如 C:\Software\Apache Maven\apache-maven-3.9.9。配置环境变量打开 系统属性:右键点击 此电脑 或 计算机 图标,选择 属性。点击 高级系统设置。点击 环境变量。设置 MAVEN_HOME 变量:点击 新建,输入以下内容:变量名:MAVEN_HOME变量值:C:\Software\Apache Maven\apache-maven-3.9.9更新 Path 变量:在“系统变量”中找到 Path 变量,选中并点击 编辑。添加新的条目:%MAVEN_HOME%\bin。点击 确定 保存更改。验证 Maven 安装打开命令提示符 (cmd)。运行以下命令查看 Maven 版本:mvn -v配置 Maven创建或编辑 settings.xml路径通常为:C:\Users\<你的用户名>\.m2\settings.xml示例配置内容:<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>C:/maven/repository</localRepository> <proxies> <proxy> <id>example-proxy</id> <active>true</active> <protocol>http</protocol> <host>proxy.example.com</host> <port>8080</port> <username>proxyuser</username> <password>somepassword</password> <nonProxyHosts>www.google.com|*.example.com</nonProxyHosts> </proxy> </proxies> <mirrors> <mirror> <id>central-mirror</id> <mirrorOf>central</mirrorOf> <url>http://repo.maven.apache.org/maven2</url> </mirror> </mirrors> <servers> <server> <id>example-repo</id> <username>myuser</username> <password>mypassword</password> </server> </servers> </settings>MacOS 和 Linux 系统安装 Maven下载 Maven 访问 Apache Maven 官方网站 下载最新版本的 Maven 压缩包(.tar.gz 文件)。解压 Maven 打开终端,使用以下命令解压下载的文件:tar -xzvf apache-maven-3.9.9-bin.tar.gz解压到一个目录,例如 /usr/local/apache-maven-3.9.9。配置环境变量编辑 ~/.bash_profile、~/.zshrc 或 ~/.profile 文件(根据你使用的 Shell)。添加以下行:export MAVEN_HOME=/usr/local/apache-maven-3.9.9 export PATH=$MAVEN_HOME/bin:$PATH保存文件并重新加载配置:source ~/.bash_profile或者:source ~/.zshrc验证 Maven 安装打开终端。运行以下命令查看 Maven 版本:mvn -v配置 Maven创建或编辑 settings.xml路径通常为:~/.m2/settings.xml示例配置内容:<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <localRepository>/usr/local/maven/repository</localRepository> <proxies> <proxy> <id>example-proxy</id> <active>true</active> <protocol>http</protocol> <host>proxy.example.com</host> <port>8080</port> <username>proxyuser</username> <password>somepassword</password> <nonProxyHosts>www.google.com|*.example.com</nonProxyHosts> </proxy> </proxies> <mirrors> <mirror> <id>central-mirror</id> <mirrorOf>central</mirrorOf> <url>http://repo.maven.apache.org/maven2</url> </mirror> </mirrors> <servers> <server> <id>example-repo</id> <username>myuser</username> <password>mypassword</password> </server> </servers> </settings>
2024年08月20日
8 阅读
0 评论
0 点赞
2024-08-17
Java 集合框架接口及方法详解
1. Collection 接口Collection 是 Java 集合框架的根接口,定义了集合操作的一般方法。方法boolean add(E e) 将指定元素添加到集合中。boolean addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到当前集合中。void clear() 移除集合中的所有元素。boolean contains(Object o) 判断集合是否包含指定元素。boolean containsAll(Collection<?> c) 判断集合是否包含指定集合中的所有元素。boolean isEmpty() 判断集合是否为空。Iterator<E> iterator() 返回集合中元素的迭代器。boolean remove(Object o) 移除集合中的指定元素。boolean removeAll(Collection<?> c) 移除当前集合中与指定集合中相同的所有元素。boolean retainAll(Collection<?> c) 保留集合中与指定集合中相同的元素。int size() 返回集合中元素的数量。Object[] toArray() 返回包含集合中所有元素的数组。<T> T[] toArray(T[] a) 返回包含集合中所有元素的数组,类型为指定数组的类型。2. List 接口(继承自 Collection)List 是一个有序集合,允许重复元素,并提供基于索引的访问。方法void add(int index, E element) 在指定位置插入元素。boolean addAll(int index, Collection<? extends E> c) 从指定位置开始将指定集合中的所有元素插入到列表中。E get(int index) 返回指定位置的元素。E set(int index, E element) 用指定元素替换指定位置的元素。E remove(int index) 移除指定位置的元素。int indexOf(Object o) 返回首次出现的指定元素的位置。int lastIndexOf(Object o) 返回最后一次出现的指定元素的位置。List<E> subList(int fromIndex, int toIndex) 返回列表中指定位置的子列表。3. Set 接口(继承自 Collection)Set 是一个不允许重复元素的集合。方法boolean add(E e) 将指定元素添加到集合中。boolean addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到当前集合中。boolean remove(Object o) 移除集合中的指定元素。boolean contains(Object o) 判断集合是否包含指定元素。boolean isEmpty() 判断集合是否为空。int size() 返回集合中元素的数量。4. SortedSet 接口(继承自 Set)SortedSet 是一个排序的 Set,允许根据自然顺序或构造时提供的比较器进行排序。方法E first() 返回集合中的第一个元素。E last() 返回集合中的最后一个元素。SortedSet<E> headSet(E toElement) 返回集合中小于指定元素的视图。SortedSet<E> tailSet(E fromElement) 返回集合中大于等于指定元素的视图。SortedSet<E> subSet(E fromElement, E toElement) 返回集合中介于两个指定元素之间的视图。5. NavigableSet 接口(继承自 SortedSet)NavigableSet 是一个可导航的 SortedSet,提供额外的导航方法。方法E lower(E e) 返回集合中小于指定元素的最大元素。E floor(E e) 返回集合中小于或等于指定元素的最大元素。E ceiling(E e) 返回集合中大于或等于指定元素的最小元素。E higher(E e) 返回集合中大于指定元素的最小元素。E pollFirst() 返回并移除集合中的第一个元素。E pollLast() 返回并移除集合中的最后一个元素。6. Queue 接口(继承自 Collection)Queue 是一个用于存储元素的集合,通常按照先进先出的原则进行处理。方法boolean offer(E e) 将指定元素插入队列(如果允许)。E poll() 返回并移除队列中的头部元素。E peek() 返回队列中的头部元素(如果队列为空则返回 null)。E remove() 移除并返回队列中的头部元素(如果队列为空则抛出异常)。E element() 返回队列中的头部元素(如果队列为空则抛出异常)。7. Deque 接口(继承自 Queue)Deque 是一个双端队列,允许在两端进行插入和删除操作。方法void addFirst(E e) 在双端队列的前端插入元素。void addLast(E e) 在双端队列的尾部插入元素。boolean offerFirst(E e) 将元素插入双端队列的前端(如果允许)。boolean offerLast(E e) 将元素插入双端队列的尾部(如果允许)。E removeFirst() 移除并返回双端队列中的第一个元素。E removeLast() 移除并返回双端队列中的最后一个元素。E getFirst() 返回双端队列中的第一个元素(不移除)。E getLast() 返回双端队列中的最后一个元素(不移除)。E peekFirst() 返回双端队列中的第一个元素(如果双端队列为空则返回 null)。E peekLast() 返回双端队列中的最后一个元素(如果双端队列为空则返回 null)。8. Map 接口Map 是一个映射接口,将键映射到值,一个键只能映射到一个值,但一个值可以被多个键映射。方法V put(K key, V value) 将指定值与指定键关联(如果键已存在,则覆盖旧值)。V get(Object key) 返回指定键所关联的值。V remove(Object key) 移除指定键及其关联的值。boolean containsKey(Object key) 如果映射包含指定键,则返回 true。boolean containsValue(Object value) 如果映射包含指定值,则返回 true。Set<K> keySet() 返回映射中所有键的集合视图。Collection<V> values() 返回映射中所有值的集合视图。Set<Map.Entry<K, V>> entrySet() 返回映射中所有键值对的集合视图。boolean isEmpty() 判断映射是否为空。int size() 返回映射中的键值对数量。void putAll(Map<? extends K, ? extends V> m) 将指定映射中的所有键值对添加到当前映射中。void clear() 移除映射中的所有键值对。9. SortedMap 接口(继承自 Map)SortedMap 是一个排序的映射,按自然顺序或构造时提供的比较器对键进行排序。方法K firstKey() 返回映射中的第一个键。K lastKey() 返回映射中的最后一个键。SortedMap<K, V> headMap(K toKey) 返回映射中小于指定键的子映射。SortedMap<K, V> tailMap(K fromKey) 返回映射中大于等于指定键的子映射。SortedMap<K, V> subMap(K fromKey, K toKey) 返回映射中介于两个指定键之间的子映射。10. NavigableMap 接口(继承自 SortedMap)NavigableMap 是一个可导航的 SortedMap,提供额外的导航方法。方法Map.Entry<K, V> lowerEntry(K key) 返回小于指定键的最大键值对。K lowerKey(K key) 返回小于指定键的最大键。Map.Entry<K, V> floorEntry(K key)返回小于或等于指定键的最大键值对。K floorKey(K key) 返回小于或等于指定键的最大键。Map.Entry<K, V> ceilingEntry(K key) 返回大于或等于指定键的最小键值对。K ceilingKey(K key) 返回大于或等于指定键的最小键。Map.Entry<K, V> higherEntry(K key) 返回大于指定键的最小键值对。K higherKey(K key) 返回大于指定键的最小键。Map.Entry<K, V> pollFirstEntry() 返回并移除映射中的第一个键值对。Map.Entry<K, V> pollLastEntry() 返回并移除映射中的最后一个键值对。
2024年08月17日
13 阅读
0 评论
0 点赞
2024-08-16
JDBC的使用
Java 数据库连接(JDBC, Java Database Connectivity) 是 Java 提供的一种用于连接和操作数据库的 API。它允许 Java 程序执行 SQL 语句、获取数据、更新数据、执行事务等数据库操作。JDBC 基本流程加载驱动程序:使用 Class.forName 方法加载数据库驱动程序。建立数据库连接:使用 DriverManager 的 getConnection 方法建立与数据库的连接。创建 SQL 语句:使用 Connection 对象的 createStatement 方法创建一个 Statement 对象。执行 SQL 语句:使用 Statement 对象的 executeQuery 或 executeUpdate 方法执行 SQL 语句。处理结果集:使用 ResultSet 对象处理从数据库返回的结果。关闭资源:关闭 ResultSet、Statement 和 Connection 对象,释放数据库资源。JDBC 示例代码import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.sql.SQLException; public class JDBCExample { public static void main(String[] args) { try { // 1. 加载驱动程序 Class.forName("com.mysql.cj.jdbc.Driver"); // 2. 建立数据库连接 String url = "jdbc:mysql://localhost:3306/testdb"; String username = "root"; String password = "password"; Connection conn = DriverManager.getConnection(url, username, password); // 3. 创建 SQL 语句 Statement stmt = conn.createStatement(); String sql = "SELECT id, name FROM users"; // 4. 执行 SQL 语句 ResultSet rs = stmt.executeQuery(sql); // 5. 处理结果集 while (rs.next()) { System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name")); } // 6. 关闭资源 rs.close(); stmt.close(); conn.close(); } catch (Exception e) { e.printStackTrace(); } } }封装 JDBC 工具类(JDBCUtil)并使用 Druid 连接池1. 添加 Druid 依赖在 pom.xml 文件中添加 Druid 依赖:<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> <!-- 根据最新版本选择 --> </dependency>2. 编写 JDBCUtil 工具类package com.atguigu.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.sql.SQLException; import java.util.Properties; public class JDBCUtil { // Druid 数据源对象 private static DataSource dataSource; static { try { // 加载配置文件 Properties properties = new Properties(); InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties"); properties.load(inputStream); // 创建 Druid 数据源 dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 获取数据库连接 public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 关闭资源 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }3. 配置 druid.properties 文件在 resources 目录下创建 druid.properties 文件,用于配置 Druid 连接池:# 基本连接属性 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC username=root password=your_password # 连接池配置 initialSize=5 maxActive=20 minIdle=5 # 超时配置 maxWait=60000 # 检测空闲连接间隔 timeBetweenEvictionRunsMillis=60000 # 连接最小生存时间 minEvictableIdleTimeMillis=300000 # 测试 SQL 连接的有效性 validationQuery=SELECT 1 testWhileIdle=true testOnBorrow=false testOnReturn=false4. 使用 JDBCUtil 类import com.atguigu.utils.JDBCUtil; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class TestJDBCUtil { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 获取数据库连接 conn = JDBCUtil.getConnection(); // 创建Statement对象 stmt = conn.createStatement(); String sql = "SELECT * FROM users"; // 执行查询 rs = stmt.executeQuery(sql); // 处理结果集 while (rs.next()) { System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭资源 JDBCUtil.close(conn, stmt, rs); } } }
2024年08月16日
3 阅读
0 评论
0 点赞
2024-08-15
MySQL 常用SQL语句
MySQL 主要概念与常用SQL语句入门介绍MySQL 为关系型数据库(Relational Database Management System),一个关系型数据库由一个或数个表格组成,如下所示的一个表格 name ▼ 键 ▼ 列(col) ┌┈┈┈┈┬┈┈┈┈┈┈┈┈┬┈┈┈┈┈┈┬┈┈┈┈┈┈┈┐ ┆ id ┆ name ┆ uid ┆ level ┆ ◀ 表头header ├┈┈┈┈┼┈┈┈┈┈┈┈┈┤┈┈┈┈┈┈┤┈┈┈┈┈┈┈┤ ┆ 1 ┆ mysql ┆ 0 ┆ 3 ┆ ├┈┈┈┈┼┈┈┈┈┈┈┈┈┤┈┈┈┈┈┈┤┈┈┈┈┈┈┈┤ ┆ 2 ┆ redis ┆ 12 ┆ 1 ┆ ◀ 行 row └┈┈┈┈┴┈┈┈┈┈┈┈┈┴┈┈┈┈┈┈┴┈┈┈┈┈┈┈┘ redis ▲ 值表头(header) 每一列的名称列(col) 具有相同数据类型的数据的集合行(row) 每一行用来描述某个人/物的具体信息值(value) 行的具体信息,每个值与该列数据类型相同键(key) 用来识别某个特定的人/物的方法,有唯一性登录MySQL# 默认用户名<root>,-p 是密码, # ⚠️参数后面不需要空格 mysql -h 127.0.0.1 -u <用户名> -p<密码> mysql -D 数据库名 -h 主机名 -u 用户名 -p mysql -h <host> -P <端口号> -u <user> -p [db_name] mysql -h <host> -u <user> -p [db_name]常用的数据库 DatabaseCREATE DATABASE db ;创建数据库SHOW DATABASES;列出数据库USE db;切换到数据库CONNECT db ;切换到数据库DROP DATABASE db;删除数据库表 TableSHOW TABLES;列出当前数据库的表SHOW FIELDS FROM t;表的列表字段DESC t;显示表格结构SHOW CREATE TABLEt;显示创建表sqlTRUNCATE TABLEt;删除表中的所有数据DROP TABLEt;删除表格Proccessshow processlist;列出进程kill pid;杀死进程查看 MySQL 信息# 显示当前mysql的version的各种信息 mysql> status; # 显示当前mysql的version信息 mysql> select version(); # 查看 MySQL 端口号 mysql> show global variables like 'port';退出MySQL会话mysql> exit 退出 quit; 或 \q; 一样的效果备份创建备份mysqldump -u user -p db_name > db.sql导出不带架构的数据库mysqldump -u user -p db_name --no-data=true --add-drop-table=false > db.sql恢复备份mysql -u user -p db_name < db.sqlMySQL 示例管理表格创建一个包含三列的新表CREATE TABLE t ( id INT, name VARCHAR DEFAULT NOT NULL, price INT DEFAULT 0 PRIMARY KEY(id) );从数据库中删除表DROP TABLE t ;向表中添加新列ALTER TABLE t ADD column;从表中删除列cALTER TABLE t DROP COLUMN c ;添加约束ALTER TABLE t ADD constraint;删除约束ALTER TABLE t DROP constraint;将表从t1重命名为t2ALTER TABLE t1 RENAME TO t2;将列c1重命名为c2ALTER TABLE t1 RENAME c1 TO c2 ;将列c1的数据类型改为datatypeALTER TABLE t1 MODIFY c1 datatype;删除表中的所有数据TRUNCATE TABLE t;从表中查询数据从表中查询列c1、c2中的数据SELECT c1, c2 FROM t查询表中的所有行和列SELECT * FROM t查询数据并使用条件筛选行SELECT c1, c2 FROM t WHERE condition查询表中的不同行SELECT DISTINCT c1 FROM t WHERE condition按升序或降序对结果集排序SELECT c1, c2 FROM t ORDER BY c1 ASC [DESC]跳过行的偏移并返回下n行SELECT c1, c2 FROM t ORDER BY c1 LIMIT n OFFSET offset使用聚合函数对行进行分组SELECT c1, aggregate(c2) FROM t GROUP BY c1使用HAVING子句筛选组SELECT c1, aggregate(c2) FROM t GROUP BY c1 HAVING condition从多个表查询内部连接 t1 和 t2SELECT c1, c2 FROM t1 INNER JOIN t2 ON condition左连接t1和t1SELECT c1, c2 FROM t1 LEFT JOIN t2 ON condition右连接t1和t2SELECT c1, c2 FROM t1 RIGHT JOIN t2 ON condition执行完全外部连接SELECT c1, c2 FROM t1 FULL OUTER JOIN t2 ON condition生成表中行的笛卡尔积SELECT c1, c2 FROM t1 CROSS JOIN t2执行交叉连接的另一种方法SELECT c1, c2 FROM t1, t2使用INNER Join子句将t1连接到自身SELECT c1, c2 FROM t1 A INNER JOIN t1 B ON condition使用SQL运算符,合并两个查询中的行SELECT c1, c2 FROM t1 UNION [ALL] SELECT c1, c2 FROM t2返回两个查询的交集SELECT c1, c2 FROM t1 INTERSECT SELECT c1, c2 FROM t2从另一个结果集中减去一个结果集SELECT c1, c2 FROM t1 MINUS SELECT c1, c2 FROM t2使用模式匹配%查询行_SELECT c1, c2 FROM t1 WHERE c1 [NOT] LIKE pattern查询列表中的行SELECT c1, c2 FROM t WHERE c1 [NOT] IN value_list查询两个值之间的行SELECT c1, c2 FROM t WHERE c1 BETWEEN low AND high检查表中的值是否为NULLSELECT c1, c2 FROM t WHERE c1 IS [NOT] NULL使用 SQL 约束将c1和c2设置为主键CREATE TABLE t( c1 INT, c2 INT, c3 VARCHAR, PRIMARY KEY (c1,c2) );将c2列设置为外键CREATE TABLE t1( c1 INT PRIMARY KEY, c2 INT, FOREIGN KEY (c2) REFERENCES t2(c2) );使c1和c2中的值唯一CREATE TABLE t( c1 INT, c1 INT, UNIQUE(c2,c3) );确保c1>0和c1>=c2中的值CREATE TABLE t( c1 INT, c2 INT, CHECK(c1> 0 AND c1 >= c2) );c2列中的设置值不为NULLCREATE TABLE t( c1 INT PRIMARY KEY, c2 VARCHAR NOT NULL );修改数据在表格中插入一行INSERT INTO t(column_list) VALUES(value_list);在表格中插入多行INSERT INTO t(column_list) VALUES (value_list), (value_list), …;将行从t2插入t1INSERT INTO t1(column_list) SELECT column_list FROM t2;更新列c1中所有行的新值UPDATE t SET c1 = new_value;更新列c1、c2中与条件匹配的值UPDATE t SET c1 = new_value, c2 = new_value WHERE condition;删除表中的所有数据DELETE FROM t;删除表中的行子集DELETE FROM t WHERE condition;管理视图创建由c1和c2组成的新视图CREATE VIEW v(c1,c2) AS SELECT c1, c2 FROM t;使用选中选项创建新视图CREATE VIEW v(c1,c2) AS SELECT c1, c2 FROM t; WITH [CASCADED | LOCAL] CHECK OPTION;创建递归视图CREATE RECURSIVE VIEW v AS select-statement -- anchor part UNION [ALL] select-statement; -- recursive part创建临时视图CREATE TEMPORARY VIEW v AS SELECT c1, c2 FROM t;删除视图DROP VIEW view_name;管理触发器创建或修改触发器CREATE OR MODIFY TRIGGER trigger_name WHEN EVENT ON table_name TRIGGER_TYPE EXECUTE stored_procedure;WHENBEFORE在事件发生前调用AFTER事件发生后调用EVENTINSERT为INSERT调用UPDATE调用UPDATEDELETE调用DELETETRIGGER_TYPEFOR EACH ROW-FOR EACH STATEMENT-管理索引在t表的c1和c2上创建索引CREATE INDEX idx_name ON t(c1,c2);在t表的c3、c4上创建唯一索引CREATE UNIQUE INDEX idx_name ON t(c3,c4)删除索引DROP INDEX idx_name ON t;MySQL 数据类型StringsCHARString (0 - 255)VARCHARString (0 - 255)TINYTEXTString (0 - 255)TEXTString (0 - 65535)BLOBString (0 - 65535)MEDIUMTEXTString (0 - 16777215)MEDIUMBLOBString (0 - 16777215)LONGTEXTString (0 - 4294967295)LONGBLOBString (0 - 4294967295)ENUMOne of preset optionsSETSelection of preset optionsDate & timeData TypeFormatDATEyyyy-MM-ddTIMEhh:mm:ssDATETIMEyyyy-MM-dd hh:mm:ssTIMESTAMPyyyy-MM-dd hh:mm:ssYEARyyyyNumericTINYINT xInteger (-128 to 127)SMALLINT xInteger (-32768 to 32767)MEDIUMINT xInteger (-8388608 to 8388607)INT xInteger (-2147483648 to 2147483647)BIGINT xInteger (-9223372036854775808 to 9223372036854775807)FLOATDecimal (precise to 23 digits)DOUBLEDecimal (24 to 53 digits)DECIMAL"DOUBLE" stored as string函数聚合函数函数解释SUM()计算一列值的总和AVG()计算一列值的平均值COUNT()计算行数,可选择性地忽略NULL值MAX()找出一列的最大值MIN()找出一列的最小值数学函数函数解释示例语法结果ABS(x)返回数值的绝对值ABS(-5)5ROUND(x,y)四舍五入到指定的小数位数,y为小数位数,默认为0ROUND(3.1415,2)3.14FLOOR(x)向下取整至最接近的整数FLOOR(3.7)3CEIL(x)向上取整至最接近的整数CEIL(3.3)4SQRT(x)返回一个数的平方根SQRT(16)4MOD(x,y)返回x除以y的余数MOD(10,3)1RAND([seed])返回0到1之间的随机数,可选种子值RAND() 或 RAND(123)0.345...日期和时间函数函数解释NOW()返回当前日期和时间CURDATE()返回当前日期CURTIME()返回当前时间DATE_FORMAT()格式化日期时间输出DATEDIFF()计算两个日期之间相差的天数STR_TO_DATE()将字符串转换为日期格式字符串函数函数解释示例语法结果CONCAT(s1,s2,...)连接两个或更多字符串CONCAT('Hello, ','World!')'Hello, World!'LOWER(str)转换为小写LOWER('HELLO')'hello'UPPER(str)转换为大写UPPER('world')'WORLD'TRIM(str)去除字符串两端空格TRIM(' Hello ')'Hello'LEFT(str,len)提取字符串左侧的若干字符LEFT('Hello', 3)'Hel'RIGHT(str,len)提取字符串右侧的若干字符RIGHT('Hello', 2)'lo'SUBSTR(str,pos,len)提取字符串中的一部分SUBSTR('Hello', 2, 3)'ell'REPLACE(str,from_str,to_str)替换字符串中的部分文本REPLACE('Hello', 'l', 'L')'HeLLo'高级函数函数解释示例语法结果BIN(x)返回 x 的二进制编码,x 为十进制数。BIN(2)10BINARY(s)将字符串 s 转换为二进制字符串。BINARY 'RUNOOB''RUNOOB'(显示效果,实际存储为二进制)CASE复合条件函数,根据条件返回不同结果。CASE WHEN 1 > 0 THEN '1 > 0' WHEN 2 > 0 THEN '2 > 0' ELSE '3 > 0' END'1 > 0'CAST(x AS type)转换数据类型。CAST('2017-08-29' AS DATE)2017-08-29COALESCE(expr1, expr2, ..., expr_n)返回第一个非空表达式的值。COALESCE(NULL, NULL, 'runoob.com', NULL, 'google.com')'runoob.com'CONNECTION_ID()返回当前连接的唯一ID。CONNECTION_ID()4292835(示例值)CONV(x, f1, f2)将 f1 进制数转换为 f2 进制数。CONV(15, 10, 2)1111CONVERT(s USING cs)转换字符串 s 的字符集为 cs。CHARSET(CONVERT('ABC' USING gbk))gbkCURRENT_USER()返回当前用户。CURRENT_USER()guest@%DATABASE()返回当前数据库名。DATABASE()runoobIF(expr, v1, v2)条件表达式,expr 为真则 v1,否则 v2。IF(1 > 0, '正确', '错误')'正确'IFNULL(v1, v2)如果 v1 不为 NULL,则返回 v1,否则返回 v2。IFNULL(NULL, 'Hello Word')'Hello Word'ISNULL(expression)判断表达式是否为 NULL。ISNULL(NULL)1LAST_INSERT_ID()返回最近生成的 AUTO_INCREMENT 值。LAST_INSERT_ID()6(示例值)NULLIF(expr1, expr2)若 expr1 等于 expr2,则返回 NULL,否则返回 expr1。NULLIF(25, 25)NULL
2024年08月15日
6 阅读
0 评论
0 点赞
2024-08-11
反射和注解
反射和注解javalang.Class类Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class对象是反射的根源。哪些类型可以获取Class对象所有Java类型用代码示例//(1)基本数据类型和void 例如:int.class void.class //(2)类和接口 例如:String.class Comparable.class //(3)枚举 例如:ElementType.class //(4)注解 例如:Override.class //(5)数组 例如:int[].class获取Class对象的四种方式(1)类型名.class要求编译期间已知类型(2)对象.getClass()获取对象的运行时类型(3)Class.forName(类型全名称)可以获取编译期间未知的类型(4)ClassLoader的类加载器对象.loadClass(类型全名称)可以用系统类加载对象或自定义加载器对象加载指定路径下的类型public class TestClass { @Test public void test05() throws ClassNotFoundException{ Class c = TestClass.class; ClassLoader loader = c.getClassLoader(); Class c2 = loader.loadClass("com.atguigu.test05.Employee"); Class c3 = Employee.class; System.out.println(c2 == c3); } @Test public void test03() throws ClassNotFoundException{ Class c2 = String.class; Class c1 = "".getClass(); Class c3 = Class.forName("java.lang.String"); System.out.println(c1 == c2); System.out.println(c1 == c3); } }反射的应用获取类型的详细信息可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)示例代码获取常规信息:public class TestClassInfo { public static void main(String[] args) throws NoSuchFieldException, SecurityException { //1、先得到某个类型的Class对象 Class clazz = String.class; //比喻clazz好比是镜子中的影子 //2、获取类信息 //(1)获取包对象,即所有java的包,都是Package的对象 Package pkg = clazz.getPackage(); System.out.println("包名:" + pkg.getName()); //(2)获取修饰符 //其实修饰符是Modifier,里面有很多常量值 /* * 0x是十六进制 * PUBLIC = 0x00000001; 1 1 * PRIVATE = 0x00000002; 2 10 * PROTECTED = 0x00000004; 4 100 * STATIC = 0x00000008; 8 1000 * FINAL = 0x00000010; 16 10000 * ... * * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0 * * mod = 17 0x00000011 * if ((mod & PUBLIC) != 0) 说明修饰符中有public * if ((mod & FINAL) != 0) 说明修饰符中有final */ int mod = clazz.getModifiers(); System.out.println(Modifier.toString(mod)); //(3)类型名 String name = clazz.getName(); System.out.println(name); //(4)父类,父类也有父类对应的Class对象 Class superclass = clazz.getSuperclass(); System.out.println(superclass); //(5)父接口们 Class[] interfaces = clazz.getInterfaces(); for (Class class1 : interfaces) { System.out.println(class1); } //(6)类的属性, 你声明的一个属性,它是Field的对象 /* Field clazz.getField(name) 根据属性名获取一个属性对象,但是只能得到公共的 Field[] clazz.getFields(); 获取所有公共的属性 Field clazz.getDeclaredField(name) 根据属性名获取一个属性对象,可以获取已声明的 Field[] clazz.getDeclaredFields() 获取所有已声明的属性 */ Field valueField = clazz.getDeclaredField("value"); // System.out.println("valueField = " +valueField); Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { //修饰符、数据类型、属性名 int modifiers = field.getModifiers(); System.out.println("属性的修饰符:" + Modifier.toString(modifiers)); String name2 = field.getName(); System.out.println("属性名:" + name2); Class<?> type = field.getType(); System.out.println("属性的数据类型:" + type); } System.out.println("-------------------------"); //(7)构造器们 Constructor[] constructors = clazz.getDeclaredConstructors(); for (Constructor constructor : constructors) { //修饰符、构造器名称、构造器形参列表 、抛出异常列表 int modifiers = constructor.getModifiers(); System.out.println("构造器的修饰符:" + Modifier.toString(modifiers)); String name2 = constructor.getName(); System.out.println("构造器名:" + name2); //形参列表 System.out.println("形参列表:"); Class[] parameterTypes = constructor.getParameterTypes(); for (Class parameterType : parameterTypes) { System.out.println(parameterType); } //异常列表 System.out.println("异常列表:"); Class<?>[] exceptionTypes = constructor.getExceptionTypes(); for (Class<?> exceptionType : exceptionTypes) { System.out.println(exceptionType); } } System.out.println("=--------------------------------"); //(8)方法们 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { //修饰符、返回值类型、方法名、形参列表 、异常列表 int modifiers = method.getModifiers(); System.out.println("方法的修饰符:" + Modifier.toString(modifiers)); Class<?> returnType = method.getReturnType(); System.out.println("返回值类型:" + returnType); String name2 = method.getName(); System.out.println("方法名:" + name2); //形参列表 System.out.println("形参列表:"); Class[] parameterTypes = method.getParameterTypes(); for (Class parameterType : parameterTypes) { System.out.println(parameterType); } //异常列表 System.out.println("异常列表:"); Class<?>[] exceptionTypes = method.getExceptionTypes(); for (Class<?> exceptionType : exceptionTypes) { System.out.println(exceptionType); } } } }创建任意引用类型的对象两种方式:1、直接通过Class对象来实例化(要求必须有无参构造)2、通过获取构造器对象来进行实例化方式一的步骤:(1)获取该类型的Class对象(2)创建对象 @Test public void test2()throws Exception{ Class<?> clazz = Class.forName("com.atguigu.test.Student"); //Caused by: java.lang.NoSuchMethodException: com.atguigu.test.Student.<init>() //即说明Student没有无参构造,就没有无参实例初始化方法<init> Object stu = clazz.newInstance(); System.out.println(stu); } @Test public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException{ // AtGuigu obj = new AtGuigu();//编译期间无法创建 Class<?> clazz = Class.forName("com.atguigu.test.AtGuigu"); //clazz代表com.atguigu.test.AtGuigu类型 //clazz.newInstance()创建的就是AtGuigu的对象 Object obj = clazz.newInstance(); System.out.println(obj); }方式二的步骤:(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)示例代码:public class TestNewInstance { @Test public void test3()throws Exception{ //(1)获取Class对象 Class<?> clazz = Class.forName("com.atguigu.test.Student"); /* * 获取Student类型中的有参构造 * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的 * 例如:public Student(int id, String name) */ //(2)获取构造器对象 Constructor<?> constructor = clazz.getDeclaredConstructor(int.class,String.class); //(3)创建实例对象 // T newInstance(Object... initargs) 这个Object...是在创建对象时,给有参构造的实参列表 Object obj = constructor.newInstance(2,"张三"); System.out.println(obj); } }操作任意类型的属性(1)获取该类型的Class对象Class clazz = Class.forName("com.atguigu.bean.User");(2)获取属性对象Field field = clazz.getDeclaredField("username");(3)设置属性可访问field.setAccessible(true);(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象Object obj = clazz.newInstance();(4)设置属性值field.set(obj,"chai");(5)获取属性值Object value = field.get(obj);如果操作静态变量,那么实例对象可以省略,用null表示示例代码:public class TestField { public static void main(String[] args)throws Exception { //1、获取Student的Class对象 Class clazz = Class.forName("com.atguigu.test.Student"); //2、获取属性对象,例如:id属性 Field idField = clazz.getDeclaredField("id"); //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作 idField.setAccessible(true); //4、创建实例对象,即,创建Student对象 Object stu = clazz.newInstance(); //5、获取属性值 /* * 以前:int 变量= 学生对象.getId() * 现在:Object id属性对象.get(学生对象) */ Object value = idField.get(stu); System.out.println("id = "+ value); //6、设置属性值 /* * 以前:学生对象.setId(值) * 现在:id属性对象.set(学生对象,值) */ idField.set(stu, 2); value = idField.get(stu); System.out.println("id = "+ value); } }调用任意类型的方法(1)获取该类型的Class对象Class clazz = Class.forName("com.atguigu.service.UserService");(2)获取方法对象Method method = clazz.getDeclaredMethod("login",String.class,String.class);(3)创建实例对象Object obj = clazz.newInstance();(4)调用方法Object result = method.invoke(obj,"chai","123);如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)如果方法是静态方法,实例对象也可以省略,用null代替示例代码:public class TestMethod { @Test public void test()throws Exception { // 1、获取Student的Class对象 Class<?> clazz = Class.forName("com.atguigu.test.Student"); //2、获取方法对象 /* * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载 * * 例如:void setName(String name) */ Method method = clazz.getDeclaredMethod("setName", String.class); //3、创建实例对象 Object stu = clazz.newInstance(); //4、调用方法 /* * 以前:学生对象.setName(值) * 现在:方法对象.invoke(学生对象,值) */ method.invoke(stu, "张三"); System.out.println(stu); } }注解注解概述定义:Java的注解(Annotation)是代码中的元数据,用于为代码元素(如类、方法、变量等)提供额外的信息,这些信息可以被编译器或运行时环境读取和处理。元数据(Metadata)是关于数据的数据,具体来说,它是用来描述类、方法、字段等的信息的数据。元数据可以包含类的名称、父类、接口实现、方法的参数和返回类型、字段的类型和访问修饰符等。这些信息的存在使得程序可以在运行时动态地获取和使用这些信息,从而实现更加灵活和智能的编程。作用:编写文档:通过代码里标识的注解生成文档【例如,生成文档doc文档 param】编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】取代配置文件:减少开发的代码量常见注解@author:用来标识作者名@version:用于标识对象的版本号,适用范围:文件、类、方法。@Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。自定义注解定义格式public @interface 注解名称{ 属性列表; }注解本质上就是一个接口,该接口默认继承Annotation接口。public @interface MyAnno extends java.lang.annotation.Annotation {}任何一个注解,都默认的继承Annotation接口。注解的属性属性的作用可以让用户在使用注解时传递参数,让注解的功能更加强大。属性的格式格式1:数据类型 属性名();格式2:数据类型 属性名() default 默认值;属性定义示例public @interface Student { String name(); // 姓名 int age() default 18; // 年龄 String gender() default "男"; // 性别 } // 该注解就有了三个属性:name,age,gender属性适用的数据类型八种基本数据类型(int,float,boolean,byte,double,char,long,short)。String类型,Class类型,枚举类型,注解类型。以上所有类型的一维数组。使用自定义注解在程序中使用(解析)注解的步骤(获取注解中定义的属性值):获取注解定义的位置的对象 (Class,Method,Field)获取指定的注解 getAnnotation(Class)调用注解中的抽象方法获取配置的属性值使用格式:@注解名(属性名=属性值,属性名=属性值,属性名=属性值...) 定义注解定义一个注解:Book包含属性:String value() 书名包含属性:double price() 价格,默认值为 100包含属性:String[] authors() 多位作者代码实现public @interface Book { // 书名 String value(); // 价格 double price() default 100; // 多位作者 String[] authors(); }使用注解public class BookShelf { @Book(value = "西游记",price = 998,authors = {"吴承恩","白求恩"}) public void showBook(){ } }使用注意事项如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。如果属性没有默认值,那么在使用注解时一定要给属性赋值。特殊属性value当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。// 定义注解Book public @interface Book { // 书名 String value(); } // 使用注解Book public class BookShelf { @Book("西游记") public void showBook(){ } } 或 public class BookShelf { @Book(value="西游记") public void showBook(){ } }如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。// 定义注解Book public @interface Book { // 书名 String value(); // 价格 double price() default 100; // 多位作者 String[] authors(); } // 使用Book注解:正确方式 @Book(value="红楼梦",authors = "曹雪芹") public class BookShelf { // 使用Book注解:正确方式 @Book(value="西游记",authors = {"吴承恩","白求恩"}) public void showBook(){ } } // 使用Book注解:错误方式 public class BookShelf { @Book("西游记",authors = {"吴承恩","白求恩"}) public void showBook(){ } } // 此时value属性名不能省略了。注解之元注解默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?那就要学习一个新的知识点:元注解。@Target@Retention元注解之@Target作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。可选的参数值在枚举类ElemenetType中包括: TYPE: 用在类,接口上 FIELD:用在成员变量上 METHOD: 用在方法上 PARAMETER:用在参数上 CONSTRUCTOR:用在构造方法上 LOCAL_VARIABLE:用在局部变量上元注解之@Retention作用:定义该注解的生命周期(有效范围)。可选的参数值在枚举类型RetentionPolicy中包括SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。 CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。 RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。注解解析通过Java技术获取注解数据的过程则称为注解解析。与注解解析相关的接口Anontation:所有注解类型的公共接口,类似所有类的父类是Object。AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则返回false。 T getAnnotation(Class<T> annotationClass); 获得当前对象上指定的注解对象。 Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。 Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。获取注解数据的原理注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。如注解作用在方法上,就通过方法(Method)对象得到它的注解。 // 得到方法对象 Method method = clazz.getDeclaredMethod("方法名"); // 根据注解名得到方法上的注解对象 Book book = method.getAnnotation(Book.class); 如注解作用在类上,就通过Class对象得到它的注解。// 获得Class对象 Class c = 类名.class; // 根据注解的Class获得使用在类上的注解对象 Book book = c.getAnnotation(Book.class);使用反射获取注解的数据需求说明定义注解Book,要求如下:包含属性:String value() 书名包含属性:double price() 价格,默认值为 100包含属性:String[] authors() 多位作者限制注解使用的位置:类和成员方法上指定注解的有效范围:RUNTIME定义BookStore类,在类和成员方法上使用Book注解定义TestAnnotation测试类获取Book注解上的数据代码实现注解book@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { // 书名 String value(); // 价格 double price() default 100; // 作者 String[] authors(); }BookStore类@Book(value = "红楼梦",authors = "曹雪芹",price = 998) public class BookStore { }TestAnnotation类public class TestAnnotation { public static void main(String[] args) throws Exception{ System.out.println("---------获取类上注解的数据----------"); test(); } /** * 获取BookStore类上使用的Book注解数据 */ public static void test(){ // 获得BookStore类对应的Class对象 Class c = BookStore.class; // 判断BookStore类是否使用了Book注解 if(c.isAnnotationPresent(Book.class)) { // 根据注解Class对象获取注解对象 Book book = (Book) c.getAnnotation(Book.class); // 输出book注解属性值 System.out.println("书名:" + book.value()); System.out.println("价格:" + book.price()); System.out.println("作者:" + Arrays.toString(book.authors())); } }
2024年08月11日
12 阅读
0 评论
0 点赞
2024-08-10
网络编程的介绍
网络编程软件结构C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、红蜘蛛、飞秋等软件。B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有IE、谷歌、火狐等。两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机之间正确通信的程序。InetAddress类InetAddress类主要表示IP地址Internet上的主机有两种方式表示地址:域名(hostName):www.atguigu.comIP 地址(hostAddress):202.108.35.210lInetAddress 类没有提供公共的构造器,而是提供 了 如下几个 静态方法来获取InetAddress 实例public static InetAddress getLocalHost()public static InetAddress getByName(String host)InetAddress 提供了如下几个常用的方法public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)。public String getHostName() :获取此 IP 地址的主机名package com.atguigu.ip; import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.Test; public class TestInetAddress { @Test public void test01() throws UnknownHostException{ InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost); } @Test public void test02()throws UnknownHostException{ InetAddress atguigu = InetAddress.getByName("www.atguigu.com"); System.out.println(atguigu); } }网络通信协议1、OSI参考模型和TCP/IP参考模型网络通信协议:通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。TCP/IP协议簇: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。而IP协议是一种非常重要的协议。IP(internet protocol)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。TCP(Transmission Control Protocol)协议,即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务。应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和POP3(Post Office Protocol 3的简称,即邮局协议的第3个版)等。而通常我们说的TCP/IP协议,其实是指TCP/IP协议族,因为该协议家族的两个最核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准,所以简称为TCP/IP协议。2、UDP协议UDP:用户数据报协议(User Datagram Protocol),它是非面向连的,不可靠的无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。大小限制的:数据被限制在64kb以内,超出这个范围就不能发送了。数据报(Datagram):网络传输的基本单位UDP:用户数据报协议(User Datagram Protocol),是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送,至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说,UDP协议是一种不可靠的协议。无连接的好处就是快,省内存空间和流量,因为维护连接需要创建大量的数据结构。UDP会尽最大努力交付数据,但不保证可靠交付,没有TCP的确认机制、重传机制,如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息。UDP协议是面向数据报文的信息传送服务。UDP在发送端没有缓冲区,对于应用层交付下来的报文在添加了首部之后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文。比如我们要发送100个字节的报文,我们调用一次send()方法就会发送100字节,接收方也需要用receive()方法一次性接收100字节,不能使用循环每次获取10个字节,获取十次这样的做法。UDP协议没有拥塞控制,所以当网络出现的拥塞不会导致主机发送数据的速率降低。虽然UDP的接收端有缓冲区,但是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。因为网络传输的时候,由于网络拥塞的存在是很大的可能导致先发的报文比后发的报文晚到达。如果此时缓冲区满了,后面到达的报文将直接被丢弃。这个对实时应用来说很重要,比如:视频通话、直播等应用。因此UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在64K以下。3、TCP协议TCP:传输控制协议 (Transmission Control Protocol)。它是面向连接的,可靠的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。是一种面向连接的、可靠的、基于字节流的传输层的通信协议,可以连续传输大量的数据。类似于打电话的效果。TCP协议负责收集这些数据信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确的还原。TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。这是因为它为当一台计算机需要与另一台远程计算机连接时,TCP协议会采用“三次握手”方式让它们建立一个连接,用于发送和接收数据的虚拟链路。数据传输完毕TCP协议会采用“四次挥手”方式断开连接。TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。第一次握手,客户端向服务器端发出连接请求,等待服务器确认。第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。第三次握手,客户端再次向服务器端发送确认信息,确认连接。通俗理解(1)男孩喜欢女孩,于是写了一封信告诉女孩:我爱你,请和我交往吧!;写完信之后,男孩焦急地等待,因为不知道信能否顺利传达给女孩。 (2)女孩收到男孩的情书后,心花怒放,原来我们是两情相悦呀!于是给男孩写了一封回信:我收到你的情书了,也明白了你的心意,其实,我也喜欢你!我愿意和你交往!; 写完信之后,女孩也焦急地等待,因为不知道回信能否能顺利传达给男孩。 (3)男孩收到回信之后很开心,因为发出的情书女孩收到了,并且从回信中知道了女孩喜欢自己,并且愿意和自己交往。然后男孩又写了一封信告诉女孩:你的心意和信我都收到了,谢谢你,还有我爱你! 女孩收到男孩的回信之后,也很开心,因为发出的情书男孩收到了。由此男孩女孩双方都知道了彼此的心意,之后就快乐地交流起来了~~TCP协议中,在发送数据结束后,释放连接时需要经过四次挥手。第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不再接收数据。第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客户端接收后就知道可以正式释放连接了。第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后,没有收到,那么彻底断开。第一次挥手":日久见人心,男孩发现女孩变成了自己讨厌的样子,忍无可忍,于是决定分手,随即写了一封信告诉女孩。 “第二次挥手”:女孩收到信之后,知道了男孩要和自己分手,怒火中烧,心中暗骂:你算什么东西,当初你可不是这个样子的!于是立马给男孩写了一封回信:分手就分手,给我点时间,我要把你的东西整理好,全部还给你! 男孩收到女孩的第一封信之后,明白了女孩知道自己要和她分手。随后等待女孩把自己的东西收拾好。 “第三次挥手”:过了几天,女孩把男孩送的东西都整理好了,于是再次写信给男孩:你的东西我整理好了,快把它们拿走,从此你我恩断义绝! “第四次挥手”:男孩收到女孩第二封信之后,知道了女孩收拾好东西了,可以正式分手了,于是再次写信告诉女孩:我知道了,这就去拿回来! 这里双方都有各自的坚持。 女孩自发出第二封信开始,限定一天内收不到男孩回信,就会再发一封信催促男孩来取东西! 男孩自发出第二封信开始,限定两天内没有再次收到女孩的信就认为,女孩收到了自己的第二封信;若两天内再次收到女孩的来信,就认为自己的第二封信女孩没收到,需要再写一封信,再等两天…..Socket编程通信的协议还是比较复杂的,java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。java.net 包中提供了两种常见的网络协议的支持:UDP:用户数据报协议(User Datagram Protocol)。TCP:传输控制协议 (Transmission Control Protocol)。通信的两端都要有Socket(也可以叫“套接字”),是两台机器间通信的端点。网络通信其实就是Socket间的通信。传输层协议分类类型描述TCP流套接字:使用TCP提供可依赖的字节流服务ServerSocket此类实现TCP服务器套接字。服务器套接字等待请求通过网络传入。 Socket此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。UDP数据报套接字:使用UDP提供“尽力而为”的数据报服务DatagramSocket此类表示用来发送和接收UDP数据报包的套接字。UDP网络编程DatagramSocket和DatagramPacket1、DatagramSocket此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。序号构造器或方法描述1public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。 2public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。3public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。 4public void close()关闭此数据报套接字。DatagramPacket类DatagramPacket此类表示数据报包。 数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。序号构造器和方法描述1public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。2public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。 3public int getLength()返回将要发送或接收到的数据的长度。4public byte[] getData()返回数据缓冲区。开发步骤发送端程序包含以下四个基本的 步骤:创建DatagramSocket :默认使用系统随机分配端口号。创建DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长度,接收方的IP地址和端口号。调用 该DatagramSocket 类对象的 send方法 :发送数据报DatagramPacket对象。关闭DatagramSocket 对象:发送端程序结束,关闭通信套接字。接收端程序包含以下四个基本的步骤 :创建DatagramSocket :指定监听的端口号。创建DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果,并指定最大可以接收的数据长度。调用 该DatagramSocket 类对象的receive方法 :接收数据报DatagramPacket对象。。关闭DatagramSocket :接收端程序结束,关闭通信套接字。演示发送和接收消息1、发送端示例代码package com.atguigu.udp; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.ArrayList; public class Send { public static void main(String[] args)throws Exception { // 1、建立发送端的DatagramSocket DatagramSocket ds = new DatagramSocket(); //要发送的数据 ArrayList<String> all = new ArrayList<String>(); all.add("世上只有一种英雄主义,就是在认清生活真相之后依然热爱生活"); //接收方的IP地址 InetAddress ip = InetAddress.getByName("127.0.0.1"); //接收方的监听端口号 int port = 9999; //发送多个数据报 for (int i = 0; i < all.size(); i++) { // 2、建立数据包DatagramPacket byte[] data = all.get(i).getBytes(); DatagramPacket dp = new DatagramPacket(data, 0, data.length, ip, port); // 3、调用Socket的发送方法 ds.send(dp); } // 4、关闭Socket ds.close(); } }2、接收端示例代码package com.atguigu.udp; import java.net.DatagramPacket; import java.net.DatagramSocket; public class Receive { public static void main(String[] args) throws Exception { // 1、建立接收端的DatagramSocket,需要指定本端的监听端口号 DatagramSocket ds = new DatagramSocket(9999); //一直监听数据 while(true){ //2、建立数据包DatagramPacket byte[] buffer = new byte[1024*64]; DatagramPacket dp = new DatagramPacket(buffer , buffer.length); //3、调用Socket的接收方法 ds.receive(dp); //4、拆封数据 String str = new String(dp.getData(),0,dp.getLength()); System.out.println(str); } // ds.close(); } }TCP网络编程ServerSocket和Socket1、ServerSocket类序号构造器或方法描述1ServerSocket(int port)创建绑定到特定端口的服务器套接字。2Socket accept()侦听并接受到此套接字的连接。3public void close()关闭此套接字。2、Sokcet类序号构造器或方法描述1public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。2public Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的指定端口号。 3public InputStream getInputStream()返回此套接字的输入流,可以用于接收消息4public OutputStream getOutputStream()返回此套接字的输出流,可以用于发送消息 5public InetAddress getInetAddress()此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。6public int getPort()此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。7public InetAddress getLocalAddress()获取套接字绑定的本地地址。8public int getLocalPort()返回此套接字绑定到的本地端口。如果尚未绑定套接字,则返回 -1。 9public void close()关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream。 10public void shutdownInput()如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。11public void shutdownOutput()禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。注意:先后调用Socket的shutdownInput()和shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用Socket的close()方法。在通信结束后,仍然要调用Scoket的close()方法,因为只有该方法才会释放Socket占用的资源,比如占用的本地端口号等。开发步骤服务器端程序包含以下四个基本的 步骤:调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。调用 该Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。关闭Socket 对象:客户端访问结束,关闭通信套接字。客户端程序包含以下四个基本的步骤 :创建 Socket :根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。打开连接到 Socket 的输入/ 出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线路。关闭 Socket :断开客户端到服务器的连接,释放线路演示单个客户端与服务器单次通信需求:客户端连接服务器,连接成功后给服务发送“lalala”,服务器收到消息后,给客户端返回“欢迎登录”,客户端接收消息后,断开连接1、服务器端示例代码package com.atguigu.tcp.one; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args)throws Exception { //1、准备一个ServerSocket对象,并绑定8888端口 ServerSocket server = new ServerSocket(8888); System.out.println("等待连接...."); //2、在8888端口监听客户端的连接,该方法是个阻塞的方法,如果没有客户端连接,将一直等待 Socket socket = server.accept(); InetAddress inetAddress = socket.getInetAddress(); System.out.println(inetAddress.getHostAddress() + "客户端连接成功!!"); //3、获取输入流,用来接收该客户端发送给服务器的数据 InputStream input = socket.getInputStream(); //接收数据 byte[] data = new byte[1024]; StringBuilder s = new StringBuilder(); int len; while ((len = input.read(data)) != -1) { s.append(new String(data, 0, len)); } System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是:" + s); //4、获取输出流,用来发送数据给该客户端 OutputStream out = socket.getOutputStream(); //发送数据 out.write("欢迎登录".getBytes()); out.flush(); //5、关闭socket,不再与该客户端通信 //socket关闭,意味着InputStream和OutputStream也关闭了 socket.close(); //6、如果不再接收任何客户端通信,可以关闭ServerSocket server.close(); } } 2、客户端示例代码package com.atguigu.tcp.one; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class Client { public static void main(String[] args) throws Exception { // 1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号 Socket socket = new Socket("127.0.0.1", 8888); // 2、获取输出流,用来发送数据给服务器 OutputStream out = socket.getOutputStream(); // 发送数据 out.write("lalala".getBytes()); //会在流末尾写入一个“流的末尾”标记,对方才能读到-1,否则对方的读取方法会一致阻塞 socket.shutdownOutput(); //3、获取输入流,用来接收服务器发送给该客户端的数据 InputStream input = socket.getInputStream(); // 接收数据 byte[] data = new byte[1024]; StringBuilder s = new StringBuilder(); int len; while ((len = input.read(data)) != -1) { s.append(new String(data, 0, len)); } System.out.println("服务器返回的消息是:" + s); //4、关闭socket,不再与服务器通信,即断开与服务器的连接 //socket关闭,意味着InputStream和OutputStream也关闭了 socket.close(); } } 演示多个客户端与服务器之间的多次通信通常情况下,服务器不应该只接受一个客户端请求,而应该不断地接受来自客户端的所有请求,所以Java程序通常会通过循环,不断地调用ServerSocket的accept()方法。如果服务器端要“同时”处理多个客户端的请求,因此服务器端需要为每一个客户端单独分配一个线程来处理,否则无法实现“同时”。咱们之前学习IO流的时候,提到过装饰者设计模式,该设计使得不管底层IO流是怎样的节点流:文件流也好,网络Socket产生的流也好,程序都可以将其包装成处理流,甚至可以多层包装,从而提供更多方便的处理。案例需求:多个客户端连接服务器,并进行多次通信每一个客户端连接成功后,从键盘输入英文单词或中国成语,并发送给服务器服务器收到客户端的消息后,把词语“反转”后返回给客户端客户端接收服务器返回的“词语”,打印显示当客户端输入“stop”时断开与服务器的连接多个客户端可以同时给服务器发送“词语”,服务器可以“同时”处理多个客户端的请求1、服务器端示例代码package com.atguigu.tcp.many; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Server { public static void main(String[] args) throws IOException { // 1、准备一个ServerSocket ServerSocket server = new ServerSocket(8888); System.out.println("等待连接..."); int count = 0; while(true){ // 2、监听一个客户端的连接 Socket socket = server.accept(); System.out.println("第" + ++count + "个客户端"+socket.getInetAddress().getHostAddress()+"连接成功!!"); ClientHandlerThread ct = new ClientHandlerThread(socket); ct.start(); } //这里没有关闭server,永远监听 } static class ClientHandlerThread extends Thread{ private final Socket socket; private String ip; public ClientHandlerThread(Socket socket) { this.socket = socket; ip = socket.getInetAddress().getHostAddress(); } public void run(){ try(//(1)获取输入流,用来接收该客户端发送给服务器的数据 Scanner input = new Scanner(socket.getInputStream()); //(2)获取输出流,用来发送数据给该客户端 PrintStream ps = new PrintStream(socket.getOutputStream()); socket; ){ // (3)接收数据 while (input.hasNextLine()) { String str = input.nextLine(); //(4)反转 StringBuilder word = new StringBuilder(str); word.reverse(); //(5)返回给客户端 ps.println(word); } System.out.println("客户端" + ip+"正常退出"); }catch(Exception e){ System.out.println("客户端" + ip+"意外退出"); } } } } 2、客户端示例代码package com.atguigu.tcp.many; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) { try( // 1、准备Socket,连接服务器,需要指定服务器的IP地址和端口号 Socket socket = new Socket("127.0.0.1", 8888); // 2、获取输出流,用来发送数据给服务器 OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); // 3、获取输入流,用来接收服务器发送给该客户端的数据 InputStream inputStream = socket.getInputStream(); Scanner input = new Scanner(inputStream); Scanner scanner = new Scanner(System.in); ) { while (true) { System.out.println("输入发送给服务器的单词或成语:"); String message = scanner.nextLine(); if (message.equalsIgnoreCase("stop")) { socket.shutdownOutput(); break; } // 4、 发送数据 ps.println(message); // 接收数据 String feedback = input.nextLine(); System.out.println("从服务器收到的反馈是:" + feedback); } }catch (IOException e){ System.out.println("客户端意外断开连接!"); } }
2024年08月10日
7 阅读
0 评论
0 点赞
2024-08-09
File类与IO流
java.io.File类File类是java.io包下代表与平台无关的文件和目录,也就是说如果希望在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。构造方法序号方法描述1public File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。2public File(String parent, String child)从父路径名字符串和子路径名字符串创建新的 File实例。3public File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例。注意:一个File对象代表硬盘或网络中可能存在的一个文件或者目录。无论该路径下是否存在文件或者目录,都不影响File对象的创建。示例代码如下:package com.atguigu.file; import java.io.File; public class FileObjectTest { public static void main(String[] args) { // 文件路径名 String pathname = "D:\\aaa.txt"; File file1 = new File(pathname); // 文件路径名 String pathname2 = "D:\\aaa\\bbb.txt"; File file2 = new File(pathname2); // 通过父路径和子路径字符串 String parent = "d:\\aaa"; String child = "bbb.txt"; File file3 = new File(parent, child); // 通过父级File对象和子路径字符串 File parentDir = new File("d:\\aaa"); String childFile = "bbb.txt"; File file4 = new File(parentDir, childFile); } }常用方法1、获取文件和目录基本信息的方法序号方法描述1public String getName()返回由此File表示的文件或目录的名称。2public long length()返回由此File表示的文件的长度。 如果此路径名表示一个目录,则返回值是不确定的。3public long lastModified()返回File对象对应的文件或目录的最后修改时间(毫秒值)。4public boolean exists()此File表示的文件或目录是否实际存在。5public boolean isDirectory()此File表示的是否为目录。6public boolean isFile()此File表示的是否为文件。7public boolean isHidden()此File表示的是否为隐藏文件或目录。8public boolean canExecute()测试应用程序是否可以执行此抽象路径名表示的文件。9public boolean canRead()测试应用程序是否可以读取此抽象路径名表示的文件。10public boolean canWrite()测试应用程序是否可以修改此抽象路径名表示的文件。注意:如果File对象代表的文件或目录存在,则File对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File对象的属性赋值,否则除了路径和名称,File对象的其他属性将会保留默认值。示例代码如下:package com.atguigu.file; import java.io.File; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; public class FileInfoMethod { public static void main(String[] args) { File f = new File("d:/aaa/bbb.txt"); System.out.println("文件构造路径:"+f.getPath()); System.out.println("文件名称:"+f.getName()); System.out.println("文件长度:"+f.length()+"字节"); System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai"))); File f2 = new File("d:/aaa"); System.out.println("目录构造路径:"+f2.getPath()); System.out.println("目录名称:"+f2.getName()); System.out.println("目录长度:"+f2.length()+"字节"); System.out.println("文件最后修改时间:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(f.lastModified()),ZoneId.of("Asia/Shanghai"))); } } /* 输出结果: 文件构造路径:d:\aaa\bbb.java 文件名称:bbb.java 文件长度:636字节 文件最后修改时间:2019-07-23T22:01:32.065 目录构造路径:d:\aaa 目录名称:aaa 目录长度:4096字节 文件最后修改时间:2024-07-23T22:01:32.065 */package com.atguigu.file; import java.io.File; public class FileIs { public static void main(String[] args) { File f = new File("d:\\aaa\\bbb.java"); File f2 = new File("d:\\aaa"); // 判断是否存在 System.out.println("d:\\aaa\\bbb.java 是否存在:"+f.exists()); System.out.println("d:\\aaa 是否存在:"+f2.exists()); // 判断是文件还是目录 System.out.println("d:\\aaa 文件?:"+f2.isFile()); System.out.println("d:\\aaa 目录?:"+f2.isDirectory()); } } /* 输出结果: d:\aaa\bbb.java 是否存在:true d:\aaa 是否存在:true d:\aaa 文件?:false d:\aaa 目录?:true */2、创建删除文件和目录序号方法描述1public static File createTempFile(String prefix, String suffix) throws IOException在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。2public static File createTempFile(String prefix, String suffix, File directory) throws IOException在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。3public boolean createNewFile()当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。4public boolean delete()删除由此File表示的文件或==空==目录。5public boolean mkdir()创建由此File表示的目录。6public boolean mkdirs()创建由此File表示的目录,包括任何必需但不存在的父目录。7public boolean renameTo(File dest)重新命名此抽象路径名表示的文件或目录。但是此方法行为的许多方面都是与平台有关的:重命名操作无法将一个文件从一个文件系统移动到另一个文件系统。方法演示,代码如下:package com.atguigu.file; import java.io.File; import java.io.IOException; public class FileCreateDelete { public static void main(String[] args) throws IOException { // 文件的创建 File f = new File("aaa.txt"); System.out.println("aaa.txt是否存在:"+f.exists()); System.out.println("aaa.txt是否创建:"+f.createNewFile()); System.out.println("aaa.txt是否存在:"+f.exists()); // 目录的创建 File f2= new File("newDir"); System.out.println("newDir是否存在:"+f2.exists()); System.out.println("newDir是否创建:"+f2.mkdir()); System.out.println("newDir是否存在:"+f2.exists()); // 创建一级目录 File f3= new File("newDira\\newDirb"); System.out.println("newDira\\newDirb创建:" + f3.mkdir()); File f4= new File("newDir\\newDirb"); System.out.println("newDir\\newDirb创建:" + f4.mkdir()); // 创建多级目录 File f5= new File("newDira\\newDirb"); System.out.println("newDira\\newDirb创建:" + f5.mkdirs()); // 文件的删除 System.out.println("aaa.txt删除:" + f.delete()); // 目录的删除 System.out.println("newDir删除:" + f2.delete()); System.out.println("newDir\\newDirb删除:" + f4.delete()); } } /* 运行结果: aaa.txt是否存在:false aaa.txt是否创建:true aaa.txt是否存在:true newDir是否存在:false newDir是否创建:true newDir是否存在:true newDira\newDirb创建:false newDir\newDirb创建:true newDira\newDirb创建:true aaa.txt删除:true newDir删除:false newDir\newDirb删除:true */3、文件或目录的上下级序号方法描述1public String getParent()返回此抽象路径名父目录的路径名字符串2public File getParentFile()返回此抽象路径名父目录的抽象路径名3public String[] list()返回一个String数组,表示该File目录中的所有子文件或目录。4public File[] listFiles()返回一个File数组,表示该File目录中的所有的子文件或目录。5public File[] listFiles(FileFilter filter)返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(File pathname)方法返回 true 时,该路径名才满足过滤器。6public String[] list(FilenameFilter filter)返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。7public File[] listFiles(FilenameFilter filter)返回返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FilenameFilter .accept(File dir, String name)方法返回 true 时,该路径名才满足过滤器。package com.atguigu.file; import org.junit.Test; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; public class DirListFiles { @Test public void test01() { File dir = new File("d:/atguigu"); String[] subs = dir.list(); for (String sub : subs) { System.out.println(sub); } } @Test public void test02() { File dir = new File("d:/atguigu"); listSubFiles(dir); } public void listSubFiles(File dir) { if (dir != null && dir.isDirectory()) { File[] listFiles = dir.listFiles(); if (listFiles != null) { for (File sub : listFiles) { listSubFiles(sub);//递归调用 } } } System.out.println(dir); } @Test public void test03() { File dir = new File("D:/atguigu"); listByFilenameFilter(dir); } public void listByFilenameFilter(File file) { if (file != null && file.isDirectory()) { File[] listFiles = file.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".java") || new File(dir,name).isDirectory(); } }); if (listFiles != null) { for (File sub : listFiles) { if(sub != null && sub.isFile()){ System.out.println(sub); } listByFilenameFilter(sub);//递归调用 } } } } @Test public void test04() { File dir = new File("D:/atguigu"); listByFileFilter(dir); } public void listByFileFilter(File file) { if (file != null && file.isDirectory()) { File[] listFiles = file.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".java") || pathname.isDirectory(); } }); if (listFiles != null) { for (File sub : listFiles) { if(sub != null && sub.isFile()){ System.out.println(sub); } listByFileFilter(sub);//递归调用 } } } } } 4、各种路径问题序号方法描述1public String getPath()将此File转换为路径名字符串。2public String getAbsolutePath()返回此File的绝对路径名字符串。3String getCanonicalPath()返回此File对象所对应的规范路径名。File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。构造路径:使用File构造器创建File对象时指定的路径名。它可以是绝对路径,也可以是相对路径。绝对路径:从盘符开始的路径,这是一个完整的路径。当构造路径是绝对路径时,那么getPath和getAbsolutePath结果一样。相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。当构造路径是相对路径时,那么getAbsolutePath的路径 = user.dir的路径 + 构造路径规范路径:所谓规范路径名,即对路径中的“..”等进行解析后的路径名。当路径中不包含".."和"/开头"等形式的路径,那么规范路径和绝对路径一样,否则会将..等进行解析。路径中如果出现“..”表示上一级目录,路径名如果以“/”开头,表示从“根目录”下开始导航。window的路径分隔符使用“\”,而Java程序中的“\”表示转义字符,所以在Windows中表示路径,需要用“\”。或者直接使用“/”也可以,Java程序支持将“/”当成平台无关的路径分隔符。或者直接使用File.separator常量值表示。package com.atguigu.file; import org.junit.Test; import java.io.File; import java.io.IOException; public class FilePath { @Test public void test1() throws IOException{ File f1 = new File("d:\\atguigu\\javase\\HelloIO.java"); //绝对路径 System.out.println("文件/目录的名称:" + f1.getName()); System.out.println("文件/目录的构造路径名:" + f1.getPath()); System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath()); System.out.println("文件/目录的规范路径名:" + f1.getCanonicalPath()); System.out.println("文件/目录的父目录名:" + f1.getParent()); } @Test public void test02()throws IOException{ File f2 = new File("/HelloIO.java");//绝对路径,从根路径开始 System.out.println("文件/目录的名称:" + f2.getName()); System.out.println("文件/目录的构造路径名:" + f2.getPath()); System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath()); System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath()); System.out.println("文件/目录的父目录名:" + f2.getParent()); } @Test public void test03() throws IOException { File f3 = new File("HelloIO.java");//相对路径 System.out.println("user.dir =" + System.getProperty("user.dir")); System.out.println("文件/目录的名称:" + f3.getName()); System.out.println("文件/目录的构造路径名:" + f3.getPath()); System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath()); System.out.println("文件/目录的规范路径名:" + f3.getCanonicalPath()); System.out.println("文件/目录的父目录名:" + f3.getParent()); } @Test public void test04() throws IOException{ File f4 = new File("../../HelloIO.java");//相对路径 System.out.println("user.dir =" + System.getProperty("user.dir")); System.out.println("文件/目录的名称:" + f4.getName()); System.out.println("文件/目录的构造路径名:" + f4.getPath()); System.out.println("文件/目录的绝对路径名:" + f4.getAbsolutePath()); System.out.println("文件/目录的规范路径名:" + f4.getCanonicalPath()); System.out.println("文件/目录的父目录名:" + f4.getParent()); } public static void main(String[] args)throws IOException { File f5 = new File("HelloIO.java");//相对路径 System.out.println("user.dir =" + System.getProperty("user.dir")); System.out.println("文件/目录的名称:" + f5.getName()); System.out.println("文件/目录的构造路径名:" + f5.getPath()); System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath()); System.out.println("文件/目录的规范路径名:" + f5.getCanonicalPath()); System.out.println("文件/目录的父目录名:" + f5.getParent()); } }IO流数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input 和输出output ,即流向内存是输入流,流出内存的输出流。Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。File对象不能直接读取和写入数据,如果要操作数据,需要IO流。IO的分类1、输入流和输出流根据数据的流向分为:输入流和输出流。输入流 :把数据从其他设备上读取到内存中的流。以InputStream,Reader结尾输出流 :把数据从内存 中写出到其他设备上的流。以OutputStream、Writer结尾2、字节流和字符流根据数据的类型分为:字节流和字符流。字节流 :以字节为单位,读写数据的流。以Stream结尾字符流 :以字符为单位,读写数据的流。以Reader和Writer结尾3、节点流和处理流根据IO流的角色不同分为:节点流和处理流。(1)节点流:可以从或向一个特定的地方(节点)读写数据。常用的节点流:文 件IO流: FileInputStream、FileOutputStream、FileReader、FileWriter 。字符串IO流: StringReader、StringWriter。数 组IO流: ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter。(2)处理流:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。处理流的特点是其对象的创建需要依赖于另一个IO流。常用处理流:缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter---增加缓冲功能,避免频繁读写硬盘。转换流:InputStreamReader、OutputStreamReader---实现字节流和字符流之间的转换。数据流:DataInputStream、DataOutputStream -提供读写Java基础数据类型功能对象流:ObjectInputStream、ObjectOutputStream--提供直接读写Java对象功能打印流:PrintStream、PrintWriter--提供各种print、println方法输出各种类型的数据管 道IO流:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter--负责两个线程之间的数据交互(3)装饰者设计模式IO流的设计使用了装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern)。装饰模式是使用一种对客户端透明的方式来动态地扩展对象的功能,它是通过继承扩展功能的替代方案之一。在现实生活中你也有很多装饰者的例子,例如:人需要各种各样的衣着,不管你穿着怎样,但是,对于你个人本质来说是不变的,充其量只是在外面加上了一些装饰,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”....四大顶级抽象父类们 输入流输出流字符流字符输入流Reader字符输出流Writer字节流字节输入流InputStream字节输出流OutputStream1、字符输出流【Writer】java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。序号方法描述1public void write(int c)写入单个字符。2public void write(char[] cbuf)写入字符数组。3public void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。4public void write(String str)写入字符串。5public void write(String str, int off, int len)写入字符串的某一部分,off字符串的开始索引,len写的字符个数。6public void flush()刷新该流的缓冲。7public void close()关闭此输出流并释放与此流相关联的任何系统资源。2、字符输入流【Reader】java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。序号方法描述1public int read()从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。2public int read(char[] cbuf)从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。3public int read(char[] cbuf,int off,int len)从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。4public void close()关闭此流并释放与此流相关联的任何系统资源。3、字节输出流【OutputStream】java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。序号方法描述1public void write(int b)将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。2public void write(byte[] b)将 b.length字节从指定的字节数组写入此输出流。3public void write(byte[] b, int off, int len)从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。4public void flush()刷新此输出流并强制任何缓冲的输出字节被写出。5public void close()关闭此输出流并释放与此流相关联的任何系统资源。4、字节输入流【InputStream】java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。序号方法描述1public int read()从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。2public int read(byte[] b)从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。3public int read(byte[] b,int off,int len)从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。4public void close()关闭此输入流并释放与此流相关联的任何系统资源。文件IO流FileWriter类java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。FileWriter(File file): 创建一个新的 FileWriter,给定要写入内容的文件的File对象。FileWriter(String fileName): 创建一个新的 FileWriter,给定要写入内容的文件的文件路径名。FileWriter(String fileName, boolean append): 创建一个新的 FileWriter,可以往某个文件中追加内容。FileWriter(String fileName, Charset charset): 创建一个新的 FileWriter,给定要写入内容的文件的文件路径名 和 文件编码。FileWriter(String fileName, Charset charset, boolean append) :创建一个新的 FileWriter,给定要追加内容的文件的文件路径名 和 文件编码。注意:当你创建一个流对象时,必须传入一个文件路径,否则会报错。对于FileWriter和FileOutputStream流来说,如果文件不存在,则会自动创建。如果文件已经存在,则会清空文件内容,写入新的内容,除非使用追加模式。1、写出字符数据package com.atguigu.fileio; import org.junit.Test; import java.io.FileWriter; import java.io.IOException; public class FWWrite { @Test public void test01()throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 写出数据 fw.write(97); // 写出第1个字符 fw.write('b'); // 写出第2个字符 fw.write('C'); // 写出第3个字符 fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。 /* 【注意】FileWriter与FileOutputStream不同。 如果不关闭,数据只是保存到缓冲区,并未保存到文件。 */ // fw.close(); } @Test public void test02()throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 字符串转换为字节数组 char[] chars = "尚硅谷".toCharArray(); // 写出字符数组 fw.write(chars); // 尚硅谷 // 写出从索引1开始,2个字符。 fw.write(chars,1,2); // 硅谷 // 关闭资源 fw.close(); } @Test public void test03()throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 字符串 String msg = "尚硅谷"; // 写出字符数组 fw.write(msg); //尚硅谷 // 写出从索引1开始,2个字符。 fw.write(msg,1,2); // 硅谷 // 关闭资源 fw.close(); } }2、续写public FileWriter(File file,boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。public FileWriter(String fileName,boolean append): 创建文件输出流以指定的名称写入文件。这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:操作类似于FileOutputStream。package com.atguigu.fileio; import org.junit.Test; import java.io.FileWriter; import java.io.IOException; public class FWWriteAppend { @Test public void test01()throws IOException { // 使用文件名称创建流对象,可以续写数据 FileWriter fw = new FileWriter("fw.txt",true); // 写出字符串 fw.write("尚硅谷真棒"); // 关闭资源 fw.close(); } }3、换行package com.atguigu.fileio; import java.io.FileWriter; import java.io.IOException; public class FWWriteNewLine { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象,可以续写数据 FileWriter fw = new FileWriter("fw.txt"); // 写出字符串 fw.write("尚"); // 写出换行 fw.write("\r\n"); // 写出字符串 fw.write("硅谷"); // 关闭资源 fw.close(); } }4、关闭和刷新【注意】FileWriter与FileOutputStream不同。因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。flush :刷新缓冲区,流对象可以继续使用。close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。代码使用演示:package com.atguigu.fileio; import java.io.FileWriter; import java.io.IOException; public class FWWriteFlush { public static void main(String[] args)throws IOException { // 使用文件名称创建流对象 FileWriter fw = new FileWriter("fw.txt"); // 写出数据,通过flush fw.write('刷'); // 写出第1个字符 fw.flush(); fw.write('新'); // 继续写出第2个字符,写出成功 fw.flush(); // 写出数据,通过close fw.write('关'); // 写出第1个字符 fw.close(); fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed fw.close(); } }小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。5、编码处理package com.atguigu.io; import org.junit.Test; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.Charset; public class TestFileWriterEncoding { @Test public void test1()throws IOException { //当前程序编码是UTF-8 //写一段话到一个GBK的文件中 FileWriter fw = new FileWriter("d:\\gbk.txt", Charset.forName("GBK")); fw.write("尚硅谷的原则是技术为王!"); fw.close(); } @Test public void test2()throws IOException { //当前程序编码是UTF-8 //往一个GBK的文件中续写一段话 FileWriter fw = new FileWriter("d:\\gbk.txt", Charset.forName("GBK"),true); fw.write("让天下没有难学的技术!"); fw.close(); } }FileReader类Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。并且要求文件字符编码与程序字符编码一致,否则会乱码。java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。FileReader(String fileName, Charset charset): 创建一个新的 FileReader ,给定要读取的文件的名称和编码方式。注意:当你创建一个流对象时,必须传入一个文件路径。对于FileReader和FileInputStream 来说,如果该文件不存在,则报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。package com.atguigu.fileio; import org.junit.Test; import java.io.FileReader; import java.io.IOException; public class FRRead { @Test public void test01() throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存数据 int b; // 循环读取 while ((b = fr.read())!=-1) { System.out.println((char)b); } // 关闭资源 fr.close(); /*输出结果: 尚 硅 谷*/ } @Test public void test02()throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存有效字符个数 int len; // 定义字符数组,作为装字符数据的容器 char[] cbuf = new char[2]; // 循环读取 while ((len = fr.read(cbuf))!=-1) { System.out.println(new String(cbuf)); } // 关闭资源 fr.close(); /* 输出结果: 尚硅 谷硅 最后错误数据硅,是因为最后一次流中只有一个字符“谷”,读取一个字符没有覆盖char[]数组cbuf的所有元素 */ } @Test public void test03() throws IOException { // 使用文件名称创建流对象 FileReader fr = new FileReader("read.txt"); // 定义变量,保存有效字符个数 int len; // 定义字符数组,作为装字符数据的容器 char[] cbuf = new char[2]; // 循环读取 while ((len = fr.read(cbuf)) != -1) { System.out.println(new String(cbuf, 0, len)); } // 关闭资源 fr.close(); /* 输出结果: 尚硅 谷 */ } }FileOutputStream类java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。==支持各种类型的文件==。public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。当你创建一个流对象时,必须传入一个==文件==路径。如果传入的是一个目录,则会报IOException异常。如果该文件不存在,会创建该文件。如果这个文件,可以覆盖这个文件或在文件后面续写内容。package com.atguigu.fileio; import org.junit.Test; import java.io.FileOutputStream; import java.io.IOException; public class FOSWrite { @Test public void test01() throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 写出数据 fos.write(97); // 写出第1个字节 fos.write(98); // 写出第2个字节 fos.write(99); // 写出第3个字节 // 关闭资源 fos.close(); /* 输出结果:abc*/ } @Test public void test02()throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串转换为字节数组 byte[] b = "尚硅谷".getBytes(); // 写出字节数组数据 fos.write(b); // 关闭资源 fos.close(); } @Test public void test03()throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt"); // 字符串转换为字节数组 byte[] b = "abcde".getBytes(); // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。 fos.write(b,2,2); // 关闭资源 fos.close(); } }package com.atguigu.fileio; import java.io.FileOutputStream; import java.io.IOException; public class FOSWriteAppend { public static void main(String[] args) throws IOException { // 使用文件名称创建流对象 FileOutputStream fos = new FileOutputStream("fos.txt",true); // 字符串转换为字节数组 byte[] b = "abcde".getBytes(); fos.write(b); // 关闭资源 fos.close(); } } //这段程序如果多运行几次,每次都会在原来文件末尾追加abcdeFileInputStream类java.io.FileInputStream 类是文件输入流,从文件中读取字节。FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。当你创建一个流对象时,必须传入一个文件路径。如果文件不存在,会抛出FileNotFoundException 。如果传入的是一个目录,则会报IOException异常。package com.atguigu.fileio; import org.junit.Test; import java.io.FileInputStream; import java.io.IOException; public class FISRead { @Test public void test() throws IOException { // 使用文件名称创建流对象 FileInputStream fis = new FileInputStream("read.txt"); // 读取数据,返回一个字节 int read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); read = fis.read(); System.out.println((char) read); // 读取到末尾,返回-1 read = fis.read(); System.out.println( read); // 关闭资源 fis.close(); /* 文件内容:abcde 输出结果: a b c d e -1 */ } @Test public void test02()throws IOException{ // 使用文件名称创建流对象 FileInputStream fis = new FileInputStream("read.txt"); // 定义变量,保存数据 int b; // 循环读取 while ((b = fis.read())!=-1) { System.out.println((char)b); } // 关闭资源 fis.close(); } @Test public void test03()throws IOException{ // 使用文件名称创建流对象. FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde // 定义变量,作为有效个数 int len; // 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2]; // 循环读取 while (( len= fis.read(b))!=-1) { // 每次读取后,把数组变成字符串打印 System.out.println(new String(b)); } // 关闭资源 fis.close(); /* 输出结果: ab cd ed 最后错误数据`d`,是由于最后一次读取时,只读取一个字节`e`,数组中,上次读取的数据没有被完全替换,所以要通过`len` ,获取有效的字节 */ } @Test public void test04()throws IOException{ // 使用文件名称创建流对象. FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde // 定义变量,作为有效个数 int len; // 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2]; // 循环读取 while (( len= fis.read(b))!=-1) { // 每次读取后,把数组的有效字节部分,变成字符串打印 System.out.println(new String(b,0,len));// len 每次读取的有效字节个数 } // 关闭资源 fis.close(); /* 输出结果: ab cd e */ } }复制文件一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。复制图片文件,代码使用演示:package com.atguigu.fileio; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileCopy { public static void main(String[] args) throws IOException { // 1.创建流对象 // 1.1 指定数据源 FileInputStream fis = new FileInputStream("D:\\test.jpg"); // 1.2 指定目的地 FileOutputStream fos = new FileOutputStream("test_copy.jpg"); // 2.读写数据 // 2.1 定义数组 byte[] b = new byte[1024]; // 2.2 定义长度 int len; // 2.3 循环读取 while ((len = fis.read(b))!=-1) { // 2.4 写出数据 fos.write(b, 0 , len); } // 3.关闭资源 fos.close(); fis.close(); } }对象流数据流与对象流概述前面学习的IO流,在程序代码中,要么将数据直接按照字节处理,要么按照字符处理。那么,如果读写Java其他数据类型的数据,怎么办呢?String name = “巫师”; int age = 300; char gender = ‘男’; int energy = 5000; double price = 75.5; boolean relive = true; Student stu = new Student("张三",23,89);Java提供了数据流和对象流来处理这些类型的数据:序号IO流描述1DataOutputStream数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流(DataInputStream)将数据读入。2DataInputStream数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。3ObjectOutputStream将 Java 基本数据类型和对象写入字节输出流中。稍后可以使用 ObjectInputStream 将数据读入。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中接收这些数据或重构对象。4ObjectInputStream对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。构造器及其API数据流和对象流都需要包装其他的IO流来创建它们的对象:序号构造器描述1public DataOutputStream(out)指定一个OutputStream字节输出流对象创建DataOutputStream对象2public DataInputStream(in)指定一个InputStream 字节输出流对象创建DataInputStream对象3public ObjectOutputStream(OutputStream out)指定一个OutputStream 字节输出流对象创建ObjectOutputStream对象4public ObjectInputStream(InputStream in)指定一个InputStream 字节输出流对象创建ObjectInputStream对象因为DataOutputStream和DataInputStream只支持Java基本数据类型和字符串的读写,而不支持Java对象的对象。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以下面直接介绍对象流ObjectOutputStream和ObjectInputStream即可。构造举例,代码如下:FileOutputStream fos = new FileOutputStream("game.dat"); ObjectOutputStream oos = new ObjectOutputStream(fos);FileInputStream fis = new FileInputStream("game.dat"); ObjectInputStream ois = new ObjectInputStream(fis);ObjectOutputStream也从OutputStream父类中继承基本方法。另外,还扩展了很多方法:序号ObjectOutputStreamObjectInpuStream描述1public void write(int b)public int read()读写1个字节2public void write(byte[] b)public int read(byte[] b)读写整个字节数组3public void write(byte[] b, int off, int len)public int read(byte[] b,int off,int len)读写一个字节数组的一部分4public void flush() 刷新输出流5public void close()public void close()关闭 以下是扩展的方法 6public void writeBoolean(boolean val)public boolean readBoolean()读写一个boolean值7public void writeByte(int val)public byte readByte()读写一个byte值8public void writeShort(int val)public short readShort()读写一个short值9public void writeChar(int val)public char readChar()读写一个char值10public void writeInt(int val)public int readInt()读写一个int值11public void writeLong(long val)public long readLong()读写一个long值12public void writeFloat(float val)public float readFloat()读写一个float值13public void writeDouble(double val)public double readDouble()读写一个double值14public void writeUTF(String str)public String readUTF()读写一个String值 15public final void writeObject (Object obj)public final Object readObject ()读写一个对象注意:读的顺序和方法与写的顺序和方法要一一对应。读写各种基本数据类型的数据示例代码:package com.atguigu.object; import org.junit.Test; import java.io.*; public class ReadWriteDataOfAnyType { @Test public void save() throws IOException { String name = "巫师"; int age = 300; char gender = '男'; int energy = 5000; double price = 75.5; boolean relive = true; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat")); oos.writeUTF(name); oos.writeInt(age); oos.writeChar(gender); oos.writeInt(energy); oos.writeDouble(price); oos.writeBoolean(relive); oos.close(); } @Test public void reload()throws IOException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat")); String name = ois.readUTF(); int age = ois.readInt(); char gender = ois.readChar(); int energy = ois.readInt(); double price = ois.readDouble(); boolean relive = ois.readBoolean(); System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive); ois.close(); } } 对象的序列化与反序列化1、序列化与反序列化概念Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:2、Serializable序列化接口某个类的对象需要序列化输出时,该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口package com.atguigu.io.object; import java.io.Serializable; public class User implements Serializable { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }package com.atguigu.io.object; import org.junit.Test; import java.io.*; public class TestUserIO { @Test public void test1()throws IOException { User u = new User("chai","123456"); FileOutputStream fos = new FileOutputStream("io/user.dat"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(u); oos.close(); fos.close(); } @Test public void test2()throws Exception{ FileInputStream fis = new FileInputStream("io/user.dat"); ObjectInputStream ois = new ObjectInputStream(fis); Object object = ois.readObject(); System.out.println(object); ois.close(); fis.close(); } } 3、不序列化字段:transient和static对象序列化是指输出对象,即将对象当前的状态值输出。这也就意味着哪些不属于对象的状态值,或者哪些瞬态的、易变的状态值不需要序列化。static修饰的静态变量的值不会序列化。因为静态变量的值不属于某个对象。transient修改的成员变量值不会序列化。因为transient表示该属性是瞬态的、易变的。package com.atguigu.io.object; import java.io.Serializable; public class Employee implements Serializable { public static String company; //static修饰的类变量,不会被序列化 private String name; private String address; private transient int age; // transient瞬态修饰成员,不会被序列化 public Employee(String name, String address, int age) { this.name = name; this.address = address; this.age = age; } @Override public String toString() { return "Employee{" + "company=" + company + ", name='" + name + '\'' + ", address='" + address + '\'' + ", age=" + age + '}'; } } package com.atguigu.io.object; import org.junit.Test; import java.io.*; public class TestEmployeeIO { @Test public void save() throws IOException { Employee.company =" 尚硅谷"; Employee e = new Employee("张三", "宏福苑", 23); // 创建序列化流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io/employee.dat")); // 写出对象 oos.writeObject(e); // 释放资源 oos.close(); System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。 } @Test public void reload() throws IOException, ClassNotFoundException { // 创建反序列化流 FileInputStream fis = new FileInputStream("io/employee.dat"); ObjectInputStream ois = new ObjectInputStream(fis); // 读取一个对象 Employee e = (Employee) ois.readObject(); // 释放资源 ois.close(); fis.close(); System.out.println(e); } }4、反序列化失败问题首先,对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。 其次,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:该类包含未知数据类型该类的序列版本号与从流中读取的类描述符的版本号不匹配package com.atguigu.io.object; import java.io.Serializable; public class Goods implements Serializable { private int id; private String name; private double price; public Goods(int id, String name, double price) { this.id = id; this.name = name; this.price = price; } @Override public String toString() { return "Goods{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; } } package com.atguigu.io.object; import org.junit.Test; import java.io.*; public class TestGoodsIO { @Test public void test1()throws IOException { Goods g = new Goods(1,"鼠标",99.0); // 创建序列化流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io/goods.dat")); // 写出对象 oos.writeObject(g); // 释放资源 oos.close(); } @Test public void test2() throws IOException, ClassNotFoundException { // 创建反序列化流 FileInputStream fis = new FileInputStream("io/goods.dat"); ObjectInputStream ois = new ObjectInputStream(fis); // 读取一个对象 Object e = ois.readObject(); // 释放资源 ois.close(); fis.close(); System.out.println(e); } } 之后修改了Goods类:package com.atguigu.io.object; import java.io.Serializable; public class Goods implements Serializable { private int id; private String name; private double price; private double amount;//数量 public Goods(int id, String name, double price) { this.id = id; this.name = name; this.price = price; } @Override public String toString() { return "Goods{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + ", amount=" + amount + '}'; } } 再次运行反序列化代码,出现异常:java.io.InvalidClassException: com.atguigu.io.object.Goods; local class incompatible: stream classdesc serialVersionUID = -2991413533082785204, local class serialVersionUID = 6624462214759466750Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。如果没有声明serialVersionUID,则每次编译都会产生新的serialVersionUID序列化版本ID值,这样如果在序列化完成之后修改了类导致类重新编译,则原来的数据将无法反序列化。所以通常我们都会在实现Serializable接口时,声明一个serialVersionUID,并为其指定一个值。serialVersionUID必须是static和final修饰的long类型的数据,它的值由程序员随意指定即可。如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。package com.atguigu.io.object; import java.io.Serializable; public class Goods implements Serializable { private int id; private String name; private double price; private double amount;//数量 private static final long serialVersionUID = -2991413533082785204L; public Goods(int id, String name, double price) { this.id = id; this.name = name; this.price = price; } @Override public String toString() { return "Goods{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + ", amount=" + amount + '}'; } } 5、序列化多个对象如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。package com.atguigu.object; import org.junit.Test; import java.io.*; import java.util.ArrayList; public class ReadWriteCollection { @Test public void save() throws IOException { ArrayList<Employee> list = new ArrayList<>(); list.add(new Employee("张三", "宏福苑", 23)); list.add(new Employee("李四", "白庙", 24)); list.add(new Employee("王五", "平西府", 25)); // 创建序列化流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat")); // 写出对象 oos.writeObject(list); // 释放资源 oos.close(); } @Test public void reload() throws IOException, ClassNotFoundException { // 创建反序列化流 FileInputStream fis = new FileInputStream("employees.dat"); ObjectInputStream ois = new ObjectInputStream(fis); // 读取一个对象 ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject(); // 释放资源 ois.close(); fis.close(); System.out.println(list); } } 重新认识System.in,System.out和ScannerSystem类的三个IO流对象System类中有三个常量对象:System.outSystem.inSystem.err查看System类中这三个常量对象的声明:public final static InputStream in = null; public final static PrintStream out = null; public final static PrintStream err = null;奇怪的是,这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。 public static void setOut(PrintStream out) { checkIO(); setOut0(out); } public static void setErr(PrintStream err) { checkIO(); setErr0(err); } public static void setIn(InputStream in) { checkIO(); setIn0(in); } private static void checkIO() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setIO")); } } private static native void setIn0(InputStream in); private static native void setOut0(PrintStream out); private static native void setErr0(PrintStream err);PrintStream类我们每天都在用的System.out对象是PrintStream类型的。它也是IO流对象。PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;另外,PrintStream 可以设置自动刷新。丰富的构造器:序号方法描述1PrintStream(File file)创建具有指定文件且不带自动行刷新的新打印流。2PrintStream(File file, String csn)创建具有指定文件名称和字符集且不带自动行刷新的新打印流。3PrintStream(OutputStream out)创建新的打印流。4PrintStream(OutputStream out, boolean autoFlush)创建新的打印流。 autoFlush如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 ('\n') 时都会刷新输出缓冲区。5PrintStream(OutputStream out, boolean autoFlush, String encoding)创建新的打印流。6PrintStream(String fileName)创建具有指定文件名称且不带自动行刷新的新打印流。7PrintStream(String fileName, String csn)创建具有指定文件名称和字符集且不带自动行刷新的新打印流。除了从OutputStream继承的方法之前,更是提供了丰富的print和println方法。package com.atguigu.systemio; import java.io.FileNotFoundException; import java.io.PrintStream; public class TestPrintStream { public static void main(String[] args) throws FileNotFoundException { PrintStream ps = new PrintStream("io.txt"); ps.println("hello"); ps.println(1); ps.println(1.5); ps.close(); } } Scanner类Scanner类不只是键盘输入。public Scanner(File source):创建Scanner对象,从source文件读取内容public Scanner(File source, String charsetName):创建Scanner对象,从source文件读取内容。文件编码是charsetName。public Scanner(File source, Charset charset):创建Scanner对象,从source文件读取内容。文件编码是charset。public Scanner(InputStream source):创建Scanner对象,从source输入流读取内容。public Scanner(InputStream source, String charsetName):创建Scanner对象,从source输入流读取内容。流中文本编码是charsetNamepublic Scanner(InputStream source, Charset charset):创建Scanner对象,从source输入流读取内容。流中文本编码是charset常用方法:序号方法描述方法描述1public boolean hasNextByte()当且仅当此扫描器的下一个标记是有效的字节值时才返回 truepublic byte nextByte()读取1个字节2public boolean hasNextShort()当且仅当此扫描器的下一个标记是默认基数中的有效的 short 值时才返回 truepublic short nextShort()读取一个boolean值3public boolean hasNextInt()当且仅当此扫描器的下一个标记是有效的 int 值时才返回 truepublic int nextInt()读取一个int值4public boolean hasNextLong()当且仅当此扫描器的下一个标记是有效的 long 值时才返回 true将输入信息的下一个标记扫描为一个 long。读取一个long值5public boolean hasNextFloat()当且仅当此扫描器的下一个标记是有效的 float 值时才返回 truepublic float nextFloat()读取一个float值6public boolean hasNextDouble()当且仅当此扫描器的下一个标记是有效的 double 值时才返回 truepublic double nextDouble()读取一个double值7public boolean hasNextBigInteger()当且仅当此扫描器的下一个标记是有效的 BigInteger 值时才返回 truepublic BigInteger nextBigInteger()读取一个BigInteger值8public boolean hasNextBigDecimal()当且仅当此扫描器的下一个标记是有效的 BigDecimal 值时才返回 truepublic BigDecimal nextBigDecimal()读取一个BigDecimal值9public boolean hasNext()如果此扫描器的输入中有另一个标记,则返回 true。在等待要扫描的输入时,此方法可能阻塞。public String next()读取一个字符串,遇到空格结束10public boolean hasNextLine()当且仅当此扫描器有另一行输入时才返回 truepublic String nextLine()读取一行字符串,遇到换行符结束package com.atguigu.systemio; import org.junit.Test; import java.io.*; import java.util.Scanner; public class TestScanner { @Test public void test01() throws IOException { Scanner input = new Scanner(System.in); PrintStream ps = new PrintStream("1.txt"); while(true){ System.out.print("请输入一个单词:"); String str = input.nextLine(); if("stop".equals(str)){ break; } ps.println(str); } input.close(); ps.close(); } @Test public void test2() throws IOException { Scanner input = new Scanner(new FileInputStream("1.txt")); while(input.hasNextLine()){ String str = input.nextLine(); System.out.println(str); } input.close(); } }JDK1.7新try..catchIO流关闭和异常处理package com.atguigu.io; import org.junit.Test; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class TestIOClose { @Test public void test1() throws IOException { FileOutputStream fos = new FileOutputStream("d:\\data.io"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeInt(8); oos.writeDouble(3.14); oos.writeUTF("hello"); fos.close(); oos.close();//java.io.IOException: Stream Closed // 要求:先new的后关 } @Test public void test2(){ FileOutputStream fos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream("d:\\data.io"); oos = new ObjectOutputStream(fos); oos.writeInt(8); oos.writeDouble(3.14); oos.writeUTF("hello"); } catch (IOException e) { e.printStackTrace(); }finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } JDK1.7之后引入新try..catch语法格式:try(需要关闭的资源对象的声明){ 业务逻辑代码 }catch(异常类型 e){ 处理异常代码 }catch(异常类型 e){ 处理异常代码 } ....它没有finally,也不需要程序员去关闭资源对象,无论是否发生异常,都会关闭资源对象。需要指出的是,为了保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close方法。Closeable是AutoCloseable的子接口。Java7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口…)进行了改写,改写后资源类都是实现了AutoCloseable或Closeable接口,并实现了close方法。写到try()中的资源类的变量默认是final声明的,不能修改。示例代码: @Test public void test3() { try( FileOutputStream fos = new FileOutputStream("d:\\data.io"); ObjectOutputStream oos = new ObjectOutputStream(fos);) { oos.writeInt(8); oos.writeDouble(3.14); oos.writeUTF("hello"); }catch (IOException e) { e.printStackTrace(); } }
2024年08月09日
6 阅读
0 评论
0 点赞
2024-08-09
多线程Thread类
线程的创建与启动java虚拟机是支持多线程的,当运行Java程序时,至少已经有一个线程了,那就是main线程。如何创建和启动一个新的线程:继承Thread类Java中java.lang.Thread是表示线程的类,每个Thread类或其子类的实例代表一个线程对象。通过继承Thread类来创建并启动多线程的步骤:定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。创建Thread子类的实例,即创建了线程对象调用线程对象的start()方法来启动该线程自定义线程类:public class MyThread extends Thread { //定义指定线程名称的构造方法 public MyThread(String name) { //调用父类的String参数的构造方法,指定线程的名称 super(name); } /** * 重写run方法,完成该线程执行的逻辑 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+":正在执行!"+i); } } }测试类:创建线程对象并启动线程public class Demo01 { public static void main(String[] args) { //创建自定义线程对象 MyThread mt = new MyThread("新的线程!"); //开启新线程 mt.start(); //在主方法中执行for循环 for (int i = 0; i < 10; i++) { System.out.println("main线程!"+i); } } }多线程执行情况分析注意事项:手动调用run方法不是启动线程的方式,只是普通方法调用。start方法启动线程后,run方法会由JVM调用执行。不要重复启动同一个线程,否则抛出异常IllegalThreadStateException不要使用Junit单元测试多线程,不支持,主线程结束后会调用System.exit()直接退出JVM;实现Runnable接口Java有单继承的限制,当我们无法继承Thread类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法通过实现Runnable接口创建线程并启动的步骤:定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。调用线程对象的start()方法来启动线程。自定义线程任务类: public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } } }测试类:创建线程对象并启动线程 public class Demo { public static void main(String[] args) { //创建自定义类对象 线程任务对象 MyRunnable mr = new MyRunnable(); //创建线程对象 Thread t = new Thread(mr, "小强"); t.start(); for (int i = 0; i < 20; i++) { System.out.println("旺财 " + i); } } }两种创建线程方式比较Thread类本身也是实现了Runnable接口的,run方法都来自Runnable接口,run方法也是真正要执行的线程任务。public class Thread implements Runnable {}因为Java类是单继承的,所以继承Thread的方式有单继承的局限性,但是使用上更简单一些。实现Runnable接口的方式,避免了单继承的局限性,并且可以使多个线程对象共享一个Runnable实现类(线程任务类)对象,从而方便在多线程任务执行时共享数据。匿名内部类对象创建线程匿名内部类对象的方式创建线程,并不是一种新的创建线程的方式,只是在线程任务只需执行一次的情况下,我们无需单独创建线程类,可以采用匿名对象的方式:new Thread("新的线程!"){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+":正在执行!"+i); } } }.start();new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+":" + i); } } }).start();Thread类构造方法public Thread() :分配一个新的线程对象。public Thread(String name) :分配一个指定名字的新的线程对象。public Thread(Runnable target) :分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。线程使用基础方法public void run() :此线程要执行的任务在此处定义代码。public String getName() :获取当前线程名称。public static Thread currentThread() :返回对当前正在执行的线程对象的引用。public final int getPriority() :返回线程优先级public final void setPriority(int newPriority) :改变线程的优先级每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:MAX_PRIORITY(10):最高优先级MIN _PRIORITY (1):最低优先级NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。public static void main(String[] args) { Thread t = new Thread(){ public void run(){ System.out.println(getName() + "的优先级:" + getPriority()); } }; t.setPriority(Thread.MAX_PRIORITY); t.start(); System.out.println(Thread.currentThread().getName() +"的优先级:" + Thread.currentThread().getPriority()); }线程控制常见方法public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。public static void sleep(long millis) :线程睡眠,使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。void join() :加入线程,当前线程中加入一个新线程,等待加入的线程终止后再继续执行当前线程。 void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。 void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。public final void stop():强迫线程停止执行。 该方法具有不安全性,已被弃用,最好不要使用。调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。public void interrupt():中断线程,实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。public static boolean interrupted():检查线程的中断状态,调用此方法会清除中断状态(标记)。public boolean isInterrupted():检查线程中断状态,不会清除中断状态(标记)示例代码:倒计时 public static void main(String[] args) { for (int i = 10; i>=0; i--) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("新年快乐!"); }示例代码:强行加塞主线程:打印[1,10],每隔10毫秒打印一个数字,自定义线程类:不停的问是否结束,输入Y或N,现在当主线程打印完5之后,就让自定义线程类加塞,直到自定义线程类结束,主线程再继续。import java.util.Scanner; public class TestJoin { public static void main(String[] args) { ChatThread t = new ChatThread(); t.start(); for (int i = 1; i <= 10; i++) { System.out.println("main:" + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //当main打印到5之后,需要等join进来的线程停止后才会继续了。 if(i==5){ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } class ChatThread extends Thread{ public void run(){ Scanner input = new Scanner(System.in); while(true){ System.out.println("是否结束?(Y、N)"); char confirm = input.next().charAt(0); if(confirm == 'Y' || confirm == 'y'){ break; } } input.close(); } }示例代码:友谊赛案例:编写龟兔赛跑多线程程序,设赛跑长度为30米兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒要求:要等兔子和乌龟的线程结束,主线程(裁判)才能公布最后的结果。public class Racer extends Thread { private String name;//运动员名字 private long runTime;//每米需要时间,单位毫秒 private long restTime;//每10米的休息时间,单位毫秒 private long distance;//全程距离,单位米 private long totalTime;//跑完全程的总时间 public Racer(String name, long distance, long runTime, long restTime) { super(); this.name = name; this.distance = distance; this.runTime = runTime; this.restTime = restTime; } @Override public void run() { long sum = 0; long start = System.currentTimeMillis(); while (sum < distance) { System.out.println(name + "正在跑..."); try { Thread.sleep(runTime);// 每米距离,该运动员需要的时间 } catch (InterruptedException e) { return ; } sum++; try { if (sum % 10 == 0 && sum < distance) { // 每10米休息一下 System.out.println(name+"已经跑了"+sum+"米正在休息...."); Thread.sleep(restTime); } } catch (InterruptedException e) { return ; } } long end = System.currentTimeMillis(); totalTime = end - start; System.out.println(name+"跑了"+sum+"米,已到达终点,共用时"+totalTime/1000.0+"秒"); } public long getTotalTime() { return totalTime; } }public class TestJoin { public static void main(String[] args) { Racer rabbit = new Racer("兔子", 30, 100, 10000); Racer turtoise = new Racer("乌龟", 30, 1000, 1000); rabbit.start(); turtoise.start(); //因为要兔子和乌龟都跑完,才能公布结果 try { rabbit.join(); } catch (InterruptedException e) { e.printStackTrace(); } try { turtoise.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("比赛结束"); if(rabbit.getTotalTime()==turtoise.getTotalTime()){ System.out.println("平局"); }else if(rabbit.getTotalTime()<turtoise.getTotalTime()){ System.out.println("兔子赢"); }else{ System.out.println("乌龟赢"); } } }示例代码:冠军赛案例:编写龟兔赛跑多线程程序,设赛跑长度为30米兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒要求:只要兔子和乌龟中有人到达终点,就宣布比赛结束,没到达终点的也停下来。public class Player extends Thread{ private String name;//运动员名字 private long runTime;//每米需要时间,单位毫秒 private long restTime;//每10米的休息时间,单位毫秒 private long distance;//全程距离,单位米 private boolean flag = true; private volatile boolean ended = false; public Player(String name, long distance, long runTime, long restTime) { super(); this.name = name; this.distance = distance; this.runTime = runTime; this.restTime = restTime; } @Override public void run() { long sum = 0; while (sum < distance && flag) { System.out.println(name + "正在跑..."); try { Thread.sleep(runTime);// 每米距离,该运动员需要的时间 } catch (InterruptedException e) { break ; } sum++; try { if (sum % 10 == 0 && sum < distance && flag) { // 每10米休息一下 System.out.println(name+"已经跑了"+sum+"米正在休息...."); Thread.sleep(restTime); } } catch (InterruptedException e) { break ; } } ended = sum == distance ? true : false; System.out.println(name+"跑了"+sum+"米"); } public void setFlag(boolean flag) { this.flag = flag; } public boolean isEnded() { return ended; } }public class TestStop { public static void main(String[] args) { Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Player rabbit = new Player("兔子", 30, 100, 10000); Player turtoise = new Player("乌龟", 30, 1000, 1000); rabbit.start(); turtoise.start(); while(true){ if(rabbit.isEnded() || turtoise.isEnded()){ rabbit.setFlag(false); turtoise.setFlag(false); rabbit.interrupt();//中断休眠 turtoise.interrupt();//中断休眠 //只要有人跑完,就结束比赛,并公布结果 break; } } System.out.println("比赛结束"); if(rabbit.isEnded() && turtoise.isEnded()){ System.out.println("平局"); }else if(rabbit.isEnded()){ System.out.println("兔子赢"); }else{ System.out.println("乌龟赢"); } } }volatile的作用是确保不会因编译器的优化而省略某些指令,volatile的变量是说这变量可能会被意想不到地改变,每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份,这样,编译器就不会去假设这个变量的值了。守护线程public class TestThread { public static void main(String[] args) { MyDaemon m = new MyDaemon(); m.setDaemon(true); m.start(); for (int i = 1; i <= 100; i++) { System.out.println("main:" + i); } } } class MyDaemon extends Thread { public void run() { while (true) { System.out.println("我一直守护者你..."); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }线程安全当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,但是如果多个线程中对资源有读和写的操作,就会出现前后数据不一致问题,这就是线程安全问题。案例:三个窗口售卖共100张火车票。线程安全问题引出局部变量不能共享示例代码:package com.atguigu.safe; public class SaleTicketDemo1 { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.start(); w2.start(); w3.start(); } } class Window extends Thread{ public void run(){ int total = 100; while(total>0) { System.out.println(getName() + "卖出一张票,剩余:" + --total); } } }结果:发现卖出300张票。问题:局部变量是每次调用方法都是独立的,那么每个线程的run()的total是独立的,不是共享数据。不同对象的实例变量不共享package com.atguigu.safe; public class SaleTicketDemo2 { public static void main(String[] args) { TicketSaleThread t1 = new TicketSaleThread(); TicketSaleThread t2 = new TicketSaleThread(); TicketSaleThread t3 = new TicketSaleThread(); t1.start(); t2.start(); t3.start(); } } class TicketSaleThread extends Thread{ private int total = 10; public void run(){ while(total>0) { System.out.println(getName() + "卖出一张票,剩余:" + --total); } } }结果:发现卖出300张票。问题:不同的实例对象的实例变量是独立的。静态变量是共享的示例代码:package com.atguigu.safe; public class SaleTicketDemo3 { public static void main(String[] args) { TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); TicketThread t3 = new TicketThread(); t1.start(); t2.start(); t3.start(); } } class TicketThread extends Thread{ private static int total = 10; public void run(){ while(total>0) { try { Thread.sleep(10);//加入这个,使得问题暴露的更明显 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "卖出一张票,剩余:" + --total); } } }结果:发现卖出近100张票。问题(1):但是有重复票或负数票问题。原因:线程安全问题问题(2):如果要考虑有两场电影,各卖100张票,这场卖完就没票了,新的线程对象也没有票卖了原因:TicketThread类的静态变量,是所有TicketThread类的对象共享。本来成员变量就是run方法共享的数据,再用static不合适。同一个对象的实例变量共享示例代码:多个Thread线程使用同一个Runnable对象package com.atguigu.safe; public class SaleTicketDemo3 { public static void main(String[] args) { TicketSaleRunnable tr = new TicketSaleRunnable(); Thread t1 = new Thread(tr,"窗口一"); Thread t2 = new Thread(tr,"窗口一"); Thread t3 = new Thread(tr,"窗口一"); t1.start(); t2.start(); t3.start(); } } class TicketSaleRunnable implements Runnable{ private int total = 10; public void run(){ while(total>0) { try { Thread.sleep(10);//加入这个,使得问题暴露的更明显 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total); } } }结果:发现卖出近100张票。问题:但是有重复票或负数票问题。原因:线程安全问题线程安全问题原因分析出现重复打印票和负数的问题分析(跟阻塞没关系):总结:线程安全问题的出现因为具备了以下条件多线程执行共享数据多条语句操作共享数据线程安全问题解决方式上述线程安全问题的必备条件1和2是我们需要的,要解决只能从第三个点上想办法。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了线程同步机制来解决。Java中常使用关键字synchronized 来实现同步机制:同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。public synchronized void method(){ 可能会产生线程安全问题的代码 }同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。格式:synchronized(同步锁){ 需要同步操作的代码 }锁对象选择同步锁对象:锁对象可以是任意类型。多个线程对象 要使用同一把锁。1、同步代码块的锁对象静态代码块中:使用当前类的Class对象非静代码块中:习惯上先考虑this,但是要注意是否同一个this代码示例://售票线程任务类 class SaleTicket implements Runnable { //票数 private int count = 100;//共享资源 @Override public void run() { while (true) { //同步代码块,this为锁对象 synchronized (this) { if (count > 0) { System.out.println(Thread.currentThread().getName() + "--" + count); count--; } } } } } //测试类 public class DemoSaleTicket { public static void main(String[] args) { //创建线程任务对象 SaleTicket st = new SaleTicket(ticket); //创建售票线程对象,并启动线程 new Thread(st).start(); new Thread(st).start(); } }代码改进://售票线程任务类 class SaleTicket implements Runnable { private int count = 100; //售票线程任务 @Override public void run() { while (true) { synchronized (this) { sell();//调用售票方法 } } } //提取出售票方法 private void sell() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "--" + count); count--; } } } //测试类 public class DemoSaleTicket { public static void main(String[] args) { //创建线程任务对象 SaleTicket st = new SaleTicket(ticket); //创建售票线程对象,并启动线程 new Thread(st).start(); new Thread(st).start(); } }同步方法的锁对象静态方法:当前类的Class对象非静态方法:this示例代码:public class SaleTicket implements Runnable { private int count = 100; @Override public void run() { while (true) { //直接调用同步方法 sell(); } } //将售票方法改进为:同步方法,非静态的同步方法的锁对象默认为this private synchronized void sell() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "--" + count); count--; } } } 示例改造://共享资源类(将共享数据与同步方法封装到一个类中) class Ticket { private int count=100;//票数 //****同步方法,非静态同步方法的锁对象默认为this public synchronized void sell() { if (count > 0) { System.out.println(Thread.currentThread().getName() + "--" + count--); } } public int getCount() { return count; } } //线程任务类 class SaleTicketRunnable implements Runnable { //共享的资源 private Ticket ticket; //通过构造器传入共享资源 public SaleTicketRunnable(Ticket ticket) { this.ticket = ticket; } //线程任务 @Override public void run() { while (true) { //售票 ticket.sell(); if(ticket.getCount()<=0)//售完跳出循环,结束线程任务 break; } } } //测试类 public class DemoSaleTicket { public static void main(String[] args) { //创建共享资源 Ticket ticket = new Ticket(); //创建资源操作线程对象 SaleTicket st = new SaleTicket(ticket); //创建售票线程对象,并启动线程 new Thread(st).start(); new Thread(st).start(); } }锁的范围太小:不能解决安全问题,要同步所有操作共享资源的语句。锁的范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。如何编写多线程程序?原则:线程操作资源类高内聚低耦合步骤:编写资源类考虑线程安全问题,在资源类中考虑使用同步代码块或同步方法public class TestSynchronized { public static void main(String[] args) { // 2、创建资源对象 Ticket ticket = new Ticket(); // 3、启动多个线程操作资源类的对象 Thread t1 = new Thread("窗口一") { public void run() { while (true) { try { Thread.sleep(10);// 加入这个,使得问题暴露的更明显 ticket.sale(); } catch (Exception e) { e.printStackTrace(); break; } } } }; Thread t2 = new Thread("窗口二") { public void run() { while (true) { try { Thread.sleep(10);// 加入这个,使得问题暴露的更明显 ticket.sale(); } catch (Exception e) { e.printStackTrace(); break; } } } }; Thread t3 = new Thread(new Runnable() { public void run() { while (true) { try { Thread.sleep(10);// 加入这个,使得问题暴露的更明显 ticket.sale(); } catch (Exception e) { e.printStackTrace(); break; } } } }, "窗口三"); t1.start(); t2.start(); t3.start(); } } // 1、编写资源类 class Ticket { private int total = 10; public synchronized void sale() { if(total<=0){ throw new RuntimeException(Thread.currentThread().getName() + "发现没有票了"); } System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + --total); } public int getTotal() { return total; } }单例设计模式的线程安全问题设计模式:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 是软件工程中解决特定问题的最佳实践方案。它们提供了一种在软件开发中遇到常见问题时,可复用的、经过验证的解决方案1、饿汉式没有线程安全问题饿汉式:上来就创建对象package com.atguigu.thread4; public class OnlyOneDemo { public static void main(String[] args) { OnlyOne o1 = OnlyOne.INSTANCE; OnlyOne o2 = OnlyOne.INSTANCE; System.out.println(o1); System.out.println(o2); System.out.println(o1==o2); } } class OnlyOne{ public static final OnlyOne INSTANCE = new OnlyOne(); private OnlyOne(){ } }2、懒汉式线程安全问题public class Singleton { private static Singleton ourInstance; public static Singleton getInstance() { //一旦创建了对象,之后再次获取对象,都不会再进入同步代码块,提升效率 if (ourInstance == null) { //同步锁,锁住判断语句与创建对象并赋值的语句 synchronized (Singleton.class) { if (ourInstance == null) { ourInstance = new Singleton(); } } } return ourInstance; } private Singleton() { } } //测试类 public class Demo { public static void main(String[] args) { //开启多个线程获取单例 new SingletonThread().start(); new SingletonThread().start(); new SingletonThread().start(); } } //线程类 class SingletonThread extends Thread{ @Override public void run() { Singleton instance = Singleton.getInstance(); System.out.println(instance);//打印对象地址,查看每个线程获取的实例是否同一个 } }线程间通信为什么要处理线程间通信:为什么要处理线程间通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。而多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些通信机制,可以协调它们的工作,以此来帮我们达到多线程共同操作一份数据。等待唤醒机制什么是等待唤醒机制这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就是在一个线程满足某个条件时,就进入等待状态(wait()/wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”或者等待时间到,在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中notify:则选取所通知对象的 wait set 中的一个线程释放;notifyAll:则释放所通知对象的 wait set 上的全部线程。注意:被通知线程被唤醒后也不一定能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。总结如下:如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行) 状态;否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态调用wait和notify方法需要注意的细节wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。11.5.3 生产者与消费者问题等待唤醒机制可以解决经典的“生产者与消费者”的问题。生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。生产者与消费者问题中其实隐含了两个问题:线程安全问题:因为生产者与消费者共享数据缓冲区,不过这个问题可以使用同步解决。线程的协调工作问题:要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。一个厨师一个服务员问题案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有1个厨师和1个服务员。package com.atguigu.thread5; public class TestCommunicate { public static void main(String[] args) { // 1、创建资源类对象 Workbench workbench = new Workbench(); // 2、创建和启动厨师线程 new Thread("厨师") { public void run() { while (true) { workbench.put(); } } }.start(); // 3、创建和启动服务员线程 new Thread("服务员") { public void run() { while (true) { workbench.take(); } } }.start(); } } // 1、定义资源类 class Workbench { private static final int MAX_VALUE = 10; private int num; public synchronized void put() { if (num >= MAX_VALUE) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num++; System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐"); this.notify(); } public synchronized void take() { if (num <= 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐"); this.notify(); } } 多个厨师多个服务员问题案例:有家餐馆的取餐口比较小,只能放10份快餐,厨师做完快餐放在取餐口的工作台上,服务员从这个工作台取出快餐给顾客。现在有多个厨师和多个服务员。package com.atguigu.thread5; public class TestCommunicate2 { public static void main(String[] args) { // 1、创建资源类对象 WindowBoard windowBoard = new WindowBoard(); // 2、创建和启动厨师线程 // 3、创建和启动服务员线程 Cook c1 = new Cook("张三",windowBoard); Cook c2 = new Cook("李四",windowBoard); Waiter w1 = new Waiter("小红",windowBoard); Waiter w2 = new Waiter("小绿",windowBoard); c1.start(); c2.start(); w1.start(); w2.start(); } } //1、定义资源类 class WindowBoard { private static final int MAX_VALUE = 10; private int num; public synchronized void put() { while (num >= MAX_VALUE) {//必须循环判断。 try { this.wait();//wait后会释放锁,Cooker1线程释放后,Cooker2线程获得锁(本应Waiter获得)进来后if继续wait,释放锁,Cooker1又快速获得锁,继续向下执行。。。 } catch (InterruptedException e) { e.printStackTrace(); } } num++; System.out.println(Thread.currentThread().getName() + "制作了一份快餐,现在工作台上有:" + num + "份快餐"); this.notifyAll(); } public synchronized void take() { while (num <= 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; System.out.println(Thread.currentThread().getName() + "取走了一份快餐,现在工作台上有:" + num + "份快餐"); this.notifyAll(); } } //2、定义厨师类 class Cook extends Thread{ private WindowBoard windowBoard; public Cook(String name,WindowBoard windowBoard) { super(name); this.windowBoard = windowBoard; } public void run(){ while(true) { windowBoard.put(); } } } //3、定义服务员类 class Waiter extends Thread{ private WindowBoard windowBoard; public Waiter(String name,WindowBoard windowBoard) { super(name); this.windowBoard = windowBoard; } public void run(){ while(true) { windowBoard.take(); } } } 释放锁操作与死锁任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?1、释放锁的操作当前线程的同步方法、同步代码块执行结束。当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。2、死锁不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。public class TestDeadLock { public static void main(String[] args) { Object g = new Object(); Object m = new Object(); Owner s = new Owner(g,m); Customer c = new Customer(g,m); new Thread(s).start(); new Thread(c).start(); } } class Owner implements Runnable{ private Object goods; private Object money; public Owner(Object goods, Object money) { super(); this.goods = goods; this.money = money; } @Override public void run() { synchronized (goods) { System.out.println("先给钱"); synchronized (money) { System.out.println("发货"); } } } } class Customer implements Runnable{ private Object goods; private Object money; public Customer(Object goods, Object money) { super(); this.goods = goods; this.money = money; } @Override public void run() { synchronized (money) { System.out.println("先发货"); synchronized (goods) { System.out.println("再给钱"); } } } }
2024年08月09日
7 阅读
0 评论
0 点赞
1
2