0%

IoC

  • 控制反转(IoC,Inversion of Control) 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现 方式是依赖注入,比如之前文章调用

  • 依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完 成功能,即 classA 对 classB 有依赖。

  • Ioc 的实现:依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些 工作由容器自行完成。

  • idea新建一个maven-quickstart项目

  • pom.xml中加入依赖文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
</dependencies>
  • service层代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package xyz.shi.service;
import xyz.shi.entity.User;
import java.util.ArrayList;
import java.util.List;

public class UserService {
public List<User> getUserList(){
ArrayList<User> users = new ArrayList<>();
User user1 = new User(1, "test1", "123456");
User user2 = new User(2, "test2", "123456");
User user3 = new User(3, "test3", "123456");
users.add(user1);
users.add(user2);
users.add(user3);
return users;
}
}

  • resources/application.xml
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="xyz.shi.service.UserService"></bean>
</beans>
1
2
3
4
<!--单例,默认是单例,可以不写--> 
<bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" scope="singleton"/>
<!--非单例,每次获取都是一个新的实例,不常用-->
<bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" scope="prototype"/>
  • 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import xyz.shi.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.shi.service.UserService;

public class userTest {
public static void main(String[] arg) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// UserService userService =(UserService) context.getBean("userService");
UserService userService1 = context.getBean(UserService.class);
for (User user : userService1.getUserList()) {
System.out.println(user.getName());
}
}
}

ioc注解

  • 使用XML方式管理Bean以及Bean的依赖,非常直观,但是配置相对繁琐一些,尤其是当Bean多了之后,有没有更简便的方式呢?
  • Annotation配置使用注解的方式,可以极大的简化配置
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="xyz.shi.service" />
</beans>
  • 件扫描的作用: 让Spring知道该去哪个包中扫描注解, 以及扫描什么样的注解, 其中用到的标签就是<context:component-scan>, 其属性base-package指定扫描哪个包下的类, 多个包可以用逗号隔开, 比如:
1
<context:component-scan base-package="com.yjzzjy4.learning.beans, com.yjzzjy4.learning.test"/>
  • 关于扫描什么样的注解, 可以使用过滤器定义规则
1
2
3
<context:component-scan base-package="com.yjzzjy4.learning" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

或者示例二:

1
2
3
<context:component-scan base-package="com.yjzzjy4.learning">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
  • 其中<context:component-scan>的use-default-filters属性指定是否使用Spring默认的过滤器, 是一个布尔属性, 默认为true, 即扫描所有注解. 当设为false的时候, 则需要自己配置扫描规则, 通常用<context:include-filter>来包含要扫描的注解类型(示例一), 如果使用默认过滤器, 则通常会使用<context:exclude-filter>注解来排除掉不想要扫描的注解类型(示例二), 其中:
    • org.springframework.stereotype.Component: 即@Component注解;
    • org.springframework.stereotype.Service:即@Service注解;
  • 而type属性值设为annotation即说明过滤器的作用对象是注解.

创建对象

Spring提供了以下四个注解用于创建对象:

  • @Component: 建议用于普通Bean;
  • @Controller: 建议用在Web层的Controller;
  • @Repository: 建议用在DAO层的仓储Bean;
  • @Service: 建议用在Service层的Bean.
  • 实际上这四个注解都是实现相同的功能, Spring将它们区分开来仅仅是为了应用开发的逻辑清晰, 并没有硬性规定这几个注解的使用场合, 这些注解都有一个属性: value, 用于指定Bean的id, 即:
1
2
@Component(value = "employee")
值得注意的是, 默认情况下若不给value设置值, 则value自动取类名首字母小写为值
  • 等价于:
1
<bean id="employee" .../>

练习

resources-application.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描整个目标文件夹 -->
<context:component-scan base-package="xyz.shi" />
</beans>
  • controller注解
1
2
3
4
5
6
7
package xyz.shi.controller;
import org.springframework.stereotype.Controller;

@Controller
public class PersonController {
}

  • Repository 注解dao
1
2
3
4
5
package xyz.shi.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
}
  • Service 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package xyz.shi.service;
import xyz.shi.entity.User;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public static List<User> getUserList(){
ArrayList<User> users = new ArrayList<>();
User user1 = new User(1, "test1", "123456");
User user2 = new User(2, "test2", "123456");
User user3 = new User(3, "test3", "123456");
users.add(user1);
users.add(user2);
users.add(user3);
return users;
}
}
  • Component注解实体类
1
2
3
4
5
6
7
8
9
10
package xyz.shi.entity;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component(value = "userBean")
@Scope(value = "prototype")
public User () {

}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class userTest {
public static void main(String[] arg) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
System.out.println(context.getBean("personController"));
System.out.println(context.getBean("userService"));
System.out.println(context.getBean("userDao"));
System.out.println(context.getBean("userBean"));
System.out.println(context.getBean("role"));
}

}
  • 结果如下
1
2
3
4
5
xyz.shi.controller.PersonController@1cab0bfb
xyz.shi.service.UserService@5e955596
xyz.shi.dao.UserDao@50de0926
xyz.shi.entity.User@2473b9ce
xyz.shi.entity.Role@60438a68

注入属性

IoC的第二个任务就是注入属性, Spring提供了三个注解用于完成此任务:

  • @Autowired: 按类型进行自动装配, 不可以有多个相同类型的Bean;
  • @Qualifier: 按名称进行装配, 要和@Autowired一起使用;
  • @Value: 注入值类型属性.
  • 还有一个注解是由Java提供的:
    • @Resource: 既可以根据类型, 也可以根据名称注入, 相当于@Autowired@Qualifier.
    • 通常建议使用Spring提供的注解, 因为在以后的Spring版本迭代中将会更可控一些.

使用@AutoWired进行自动注入

  • dao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package xyz.shi.dao;
import org.springframework.stereotype.Repository;
import xyz.shi.entity.User;

import java.util.ArrayList;
import java.util.List;

@Repository
public class UserDao {
public List<User> getUserList(){
ArrayList<User> users = new ArrayList<>();
User user1 = new User(1, "test1", "123456");
User user2 = new User(2, "test2", "123456");
User user3 = new User(3, "test3", "123456");
users.add(user1);
users.add(user2);
users.add(user3);
return users;
}
}

  • service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package xyz.shi.service;
import org.springframework.beans.factory.annotation.Autowired;
import xyz.shi.dao.UserDao;
import xyz.shi.entity.User;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> getUserList(){
return userDao.getUserList();
}
}

Autowired 加入后,不用在实例化了

  • controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package xyz.shi.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import xyz.shi.entity.User;
import xyz.shi.service.UserService;

import java.util.List;

@Controller
public class UserController {
@Autowired
private UserService userService;
public List<User> getUserList() {
return userService.getUserList();
}
}

  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.shi.controller.UserController;
import xyz.shi.entity.User;

import java.util.List;

public class userTest {
public static void main(String[] arg) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserController userController = context.getBean("userController", UserController.class);
List<User> users = userController.getUserList();
for(User user: users) {
System.out.println(user.getName());
}
}
}

@Qualifier注解

  • 修改UserControll.java加上@Qualifer注解,指定id为userService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package xyz.shi.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import xyz.shi.entity.User;
import xyz.shi.service.UserService;

import java.util.List;

@Controller
public class UserController {
@Autowired
@Qualifier("userService")
private UserService userService111;
public List<User> getUserList() {
return userService111.getUserList();
}
}

这样运行测试代码是没有问题,如果是@Qualifier(“userService1”),就会报错,因为找不到对应的name

  • AutoWired可以进行定义在方法上
1
2
3
4
@Autowired
public void test(PersonDao personDao){
System.out.println("personDao:"+personDao);
}
  • @Qualifier注解也可以作用在属性上,用来被当作id去匹配容器中的对象,如果没有 此注解,那么直接按照类型进行匹配
1
2
3
public void test1(@Qualifier("personServiceSon") PersonService personService){
personService.getPerson();
}

说明

  • 上篇文章 主要用jsp+jdbc的方式进行了练习,本次采用ORM框架mybatis代替jdbc,实现增删改查

  • java版本为1.8

  • 本次练习源代码

配置

  • pom.xml中加入依赖文件
1
2
3
4
5
6
7
8
9
10
 <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
  • 新增一个实例类 src-main-java-xyz.shi.entity.User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package xyz.shi.entity;
public class User {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public User () {

}
public User(int id,String name, String password) {
this.name = name;
this.id = id;
this.password = password;
}

public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}

}
  • 接下来就要创建一个xml文件作为MyBatis的核心配置文件了,src-main-resources-mybatis.config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!-- 注意:environments标签,当mybatis和spring整合之后,这个标签是不用配置的 -->

<!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境
一、development:开发模式
二、work:工作模式-->
<environments default="development">
<!--id属性必须和上面的default一样 -->
<environment id="development">
<!--事务管理器
一、JDBC:这个配置直接简单使用了 JDBC 的提交和回滚设置。它依赖于从数据源得到的连接来管理事务范围
二、MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让容器来管理事务的整个生命周期
比如 spring 或 JEE 应用服务器的上下文,默认情况下,它会关闭连接。然而一些容器并不希望这样,
因此如果你需要从连接中停止它,就可以将 closeConnection 属性设置为 false,比如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
-->
<transactionManager type="JDBC"/>
<!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai" />
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

</configuration>

替换连接信息解决硬编码问题

  • 编写sql映射文件了,src-main-resources-UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usersMapper">

<!-- 根据 id 查询 users 表中的数据
id:唯一标识符,此文件中的id值不能重复
resultType:返回值类型,一条数据库记录也就对应实体类的一个对象
parameterType:参数类型,也就是查询条件的类型
-->
<select id="selectusersById"
resultType="xyz.shi.entity.User" parameterType="int">
<!-- 这里和普通的sql 查询语句差不多,对于只有一个参数,后面的 #{id}表示占位符,里面不一定要写id,写啥都可以,但是不要空着,如果有多个参数则必须写pojo类里面的属性 -->
select * from users where id = #{id}
</select>


<!-- 查询 users 表的所有数据
注意:因为是查询所有数据,所以返回的应该是一个集合,这个集合里面每个元素都是users类型
-->
<select id="selectusersAll" resultType="xyz.shi.entity.User">
select * from users
</select>

<!-- 模糊查询:根据 users 表的usersname字段
下面两种写法都可以,但是要注意
1、${value}里面必须要写value,不然会报错
2、${}表示拼接 sql 字符串,将接收到的参数不加任何修饰拼接在sql语句中
3、使用${}会造成 sql 注入
-->
<select id="selectLikeusersName" resultType="xyz.shi.entity.User" parameterType="String">
select * from users where name like '%${value}%'
<!-- select * from users where usersname like #{usersname} -->
</select>

<!-- 向 users 表插入一条数据 -->
<insert id="insertusers" parameterType="xyz.shi.entity.User">
insert into users(name,password)
value(#{name},#{password})
</insert>

<!-- 根据 id 更新 users 表的数据 -->
<update id="updateusersById" parameterType="xyz.shi.entity.User">
update users set name=#{name},password=#{password} where id=#{id}
</update>

<!-- 根据 id 删除 users 表的数据 -->
<delete id="deleteusersById" parameterType="int">
delete from users where id=#{id}
</delete>
</mapper>

统一管理sql语句,解决硬编码问题

  • src-main-resources-mybatis-config.xml 配置文件中注册 userMapper.xml 文件
1
2
3
4
5
6
...
</environment>
<mappers>
<!-- 注册userMapper.xml文件 -->
<mapper resource="UserMapper.xml"/>
</mappers>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package xyz.shi;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import xyz.shi.entity.User;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class CRUDTest {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql (只有这块需要手写,其他步骤直接复制)
List<User> users = sqlSession.selectList("usersMapper.selectusersAll"); // 命名空间+id,这就是命名空间的作用:便于区分
// System.out.println(users);
for(User user: users) {
System.out.println(user.getId());
System.out.println(user.getName());
System.out.println(user.getPassword());
}

// 插入
User user1 = new User();
user1.setName("haha");
user1.setPassword("111111");
sqlSession.insert("usersMapper.insertusers", user1);
sqlSession.commit();

// 修改
User user2 = new User();
user2.setId(1);
user2.setName("haha");
user2.setPassword("111111");
sqlSession.update("usersMapper.updateusersById", user2);
sqlSession.commit();

//删除
User user3= new User();
user3.setId(24);
sqlSession.delete("usersMapper.deleteusersById", user3);
sqlSession.commit();


sqlSession.close();
}

}

优化架构

mybatis-config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!--读取配置文件中的数据库连接信息-->
<properties resource="db.properties"/>
<settings>
<!--设置日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--开启驼峰命名,映射数据库到对象属性名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>

<!--给实体类取别名,为了后面配置mapper.xml时不用写全限定名,方便使用-->
<typeAliases>
<!--包扫描-->
<package name="xyz.shi.entity"/>
</typeAliases>

<environments default="development">
<!--id属性必须和上面的default一样 -->
<environment id="development">
<!--事务管理器
一、JDBC:这个配置直接简单使用了 JDBC 的提交和回滚设置。它依赖于从数据源得到的连接来管理事务范围
二、MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让容器来管理事务的整个生命周期
比如 spring 或 JEE 应用服务器的上下文,默认情况下,它会关闭连接。然而一些容器并不希望这样,
因此如果你需要从连接中停止它,就可以将 closeConnection 属性设置为 false,比如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
-->
<transactionManager type="JDBC"/>
<!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注册userMapper.xml文件 -->
<mapper resource="UserMapper.xml"/>
</mappers>

</configuration>

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="usersMapper">

<!-- 根据 id 查询 users 表中的数据
id:唯一标识符,此文件中的id值不能重复
resultType:返回值类型,一条数据库记录也就对应实体类的一个对象
parameterType:参数类型,也就是查询条件的类型
-->

<!--通过id(唯一)获取用户-->
<select id="queryById" parameterType="int" resultType="User">
select * from users where id = #{id};
</select>

<select id="queryOne" resultType="User" parameterType="map">
select * from users where name = #{name} and password=#{password};
</select>
<!-- <select id="getUserList" parameterType="map" resultType="User">-->
<select id="getUserList" resultType="User">
select * from users
<!-- <if test="name != null">-->
<!-- and name = #{name}-->
<!-- </if>-->
<!-- <if test="password != null">-->
<!-- and password = #{password}-->
<!-- </if>-->
<!-- <trim prefix="limit">-->
<!-- <if test="curPage != null and pageSize != null">-->
<!-- #{curPage},#{pageSize}-->
<!-- </if>-->
<!-- </trim>-->
</select>

<!-- 向 users 表插入一条数据 -->
<insert id="addUser" parameterType="User">
insert into users(name,password)
value(#{name},#{password})
</insert>

<update id="modify" parameterType="User">
update users set name=#{name},password=#{password} where id=#{id}
</update>

<!-- 根据 id 删除 users 表的数据 -->
<delete id="deleteById" parameterType="int">
delete from users where id=#{id}
</delete>
</mapper>

三层架构

实体层(entity)

  • User.java 代码省略

数据访问层(Dao)

  • 接口类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package xyz.shi.dao;

import xyz.shi.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserDao {
List<User> getUserList();
User queryOne(User user);
User queryById(int id);
// //通过id删除用户
int deleteById(@Param("id")int id);
int addUser(User user);

int modify(User user);
}

  • 实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package xyz.shi.dao.impl;

import org.apache.ibatis.session.SqlSession;
import xyz.shi.dao.UserDao;
import xyz.shi.entity.User;

import java.util.List;
import java.util.Map;
import xyz.shi.utils.MybatisUtils;
public class UserDaoImpl implements UserDao {

@Override
public List<User> getUserList() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 命名空间+id,这就是命名空间的作用:便于区分
List<User> users = sqlSession.selectList("usersMapper.getUserList");
sqlSession.close();
return users;
}

@Override
public User queryOne(User user) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
List<User> users = sqlSession.selectList("usersMapper.queryOne", user);
sqlSession.close();
if (users != null) {
return users.get(0);
} else {
return null;
}
}

@Override
public User queryById(int id) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
User user1 = sqlSession.selectOne("usersMapper.queryById", id);
sqlSession.close();
return user1;
}

@Override
public int deleteById(int id) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
int num = sqlSession.delete("usersMapper.deleteById", id);
sqlSession.commit();
sqlSession.close();
return num;
}

@Override
public int addUser(User user) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
int num = sqlSession.insert("usersMapper.addUser", user);
sqlSession.commit();
sqlSession.close();
return num;
}

@Override
public int modify(User user) {
SqlSession sqlSession = MybatisUtils.getSqlSession();
int num = sqlSession.update("usersMapper.modify", user);
sqlSession.commit();
sqlSession.close();
return num;
}
}

业务逻辑(services)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package xyz.shi.service;

import xyz.shi.entity.User;

import java.util.List;
public interface UserService {
List<User> getUserList();
User queryOne(User user);
User queryById(int id);
int deleteById(int id);
int addUser(User user);
int modify(User user);

}

  • 实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package xyz.shi.service.impl;

import xyz.shi.dao.UserDao;
import xyz.shi.dao.impl.UserDaoImpl;
import xyz.shi.entity.User;
import xyz.shi.service.UserService;
import java.util.List;

public class UserServiceImpl implements UserService {
//创建UserDaoImpl对象
private UserDao userDao = new UserDaoImpl();

@Override
public List<User> getUserList() {
return userDao.getUserList();
}

@Override
public User queryOne(User user) {
return userDao.queryOne(user);
}

@Override
public User queryById(int id) {
return userDao.queryById(id);
}

@Override
public int deleteById(int id) {
return userDao.deleteById(id);
}

@Override
public int addUser(User user) {
return userDao.addUser(user);
}

@Override
public int modify(User user) {
return userDao.modify(user);
}
}

servlet层

  • 接受jsp数据,传送数据到jsp页面,比如下面的用户详情页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package xyz.shi.servlet;

import xyz.shi.entity.User;
import xyz.shi.service.UserService;
import xyz.shi.service.impl.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/userFind")
public class FindUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
UserService userService = new UserServiceImpl();
int userId = Integer.parseInt(req.getParameter("id"));
User user = userService.queryById(userId);
if (user != null) {
req.setAttribute("user", user);
req.getRequestDispatcher("find.jsp").forward(req, resp);
} else {
// 登录失败,返回登录页面
System.out.println("查找数据失败");
req.setAttribute("message", "failed");

}
}
}

view层

webapp/下面的login、add、find等jsp文件,用户详情页,并进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<div><a href="login">主页</a></div>
<form action="userUpdate" method="post">
用户名:<br>
<input type="text" name="username" value="${user.getName()}">
<br>
密码:<br>
<input type="password" name="password" value="${user.getPassword()}" >
<input type="hidden" name="id" value="${user.getId()}">
<br><br>
<input type="submit" value="提交" >
</form>

</body>
</html>

页面

image-20231212113144060

说明

  • 上篇主要搭建了servlet,本次主要加入jsp的应用,功能包括增删改查
  • 本次java 版本
1
2
3
4
C:\Users\Administrator>java -version
java version "1.8.0_381"
Java(TM) SE Runtime Environment (build 1.8.0_381-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.381-b09, mixed mode)

简单实例

  • webapp下新增index.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<%
String user=(String)session.getAttribute("user");
if (user != null) {
// 用户已登录
out.println("<a href=\"homePage.jsp\">");
out.println("登录成功主页");
out.println("</a>");
} else {
// 用户未登录
out.println("<a href=\"login.jsp\">");
out.println("登录界面");
out.println("</a>");
}
%>
</body>
</html>
  • 当sesssion数据不存在,可点击进入登录界面

image-20231201173302810

  • webapp/login.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="login" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required><br><br>
<input type="submit" value="登录">
</form>
</body>
</html>
  • 登录的servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package xyz.shi.servlet;
...
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpSession;

@WebServlet("/login")
public class LoginServlet extends HttpServlet{
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");

// 验证用户名和密码
if (username.equals("admin") && password.equals("123456")) {
// 登录成功,创建会话
HttpSession session = request.getSession();
session.setAttribute("username", username);
response.sendRedirect("homePage.jsp");
} else {
// 登录失败,返回登录页面
request.setAttribute("error", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}
  • webapp/homePage.jsp
1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h1>欢迎登录,${username}!</h1>
<a href="logout">注销</a>
</body>
</html>
  • 注销的Servlet
1
2
3
4
5
6
7
8
9
10
11
package xyz.shi.servlet;

...
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
session.invalidate();
response.sendRedirect("login.jsp");
}
}

image-20231201173645669

结合jdbc

  • 基础知识参考这篇文章

  • 首先pom.xml中加入本次需要的依赖文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

jstl主要是在jsp中使用的标签语法

三层架构

  • 表示层:主要是指与用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数 据。

  • 业务逻辑层:UI层(表示)和DAL层(数据访问)之间的桥梁。实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。

  • 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。

  • 实体层:它不属于三层中的任何一层,贯穿于三层,在三层之间传递数据;

实体层

  • xyz.shi.entity.User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package xyz.shi.entity;
public class User {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
// 因为QueryRunner创建Bean对象的过程使用的是newInstance()方法,该方法只能调用无参构
public User () {

}
public User(int id,String name, String password) {
this.name = name;
this.id = id;
this.password = password;
}
public void setId(int id) { this.id = id;}
public String getName() { return name;}
public void setName(String name) { this.name = name;}
public void setPassword(String password) {this.password = password;}
public String getPassword() {return password;}

}

数据访问层

  • 分为接口和实现接口,代码放在xyz.shi.dao 包下

  • 接口层xyz.shi.dao.UserDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package xyz.shi.dao;

import xyz.shi.entity.User;

import java.sql.SQLException;
import java.util.List;

public interface UserDao {
//返回所有对象
List<User> queryAllList(String sql);
// 查找多条记录
List<User> queryList(String sql,Object ...params);
// 查找一条记录
User queryOne(String sql, Object... parameters);
// 修改(删除)一条记录
int update(String sql, Object... parameters) ;
}

  • 实现接口层xyz.shi.dao.impl.UserDaoImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package xyz.shi.dao.impl;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import xyz.shi.dao.UserDao;
import xyz.shi.entity.User;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import xyz.shi.util.jdbcUtil;
public class UserDaoImpl implements UserDao {
private QueryRunner qr = new QueryRunner();
// 返回所有对象
@Override
public List<User> queryAllList(String sql) {
Connection connection = null;
try {
connection =jdbcUtil.getConnection();
return qr.query(connection, sql, new BeanListHandler<User>(User.class));
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
jdbcUtil.closeResource(null,null, connection);
}
}
// 返回多个对象(即查询的结果是多行)
@Override
public List<User> queryList(String sql, Object... params) {
Connection connection = null;
try {
connection =jdbcUtil.getConnection();
return qr.query(connection, sql, new BeanListHandler<User>(User.class), params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
jdbcUtil.closeResource(null,null, connection);
}
}
// 查询单行结果
@Override
public User queryOne(String sql, Object... parameters) {
Connection connection = null;

try {
connection =jdbcUtil.getConnection();
return qr.query(connection, sql, new BeanHandler<User>(User.class), parameters);

} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
jdbcUtil.closeResource(null,null, connection);
}

}
// 修改(增删改通用)
@Override
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
connection =jdbcUtil.getConnection();
return qr.update(connection,sql,parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
jdbcUtil.closeResource(null,null, connection);
}
}
}

业务逻辑层

  • 分为接口和实现接口,代码放在xyz.shi.service 包下

  • 接口层xyz.shi.service.UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package xyz.shi.service;
import xyz.shi.entity.User;
import java.util.List;
public interface UserService {
//根据userId查询一个user
User getUser(String id);
//根据name和password查询一个user
Boolean ableLogin (String name , String password);
//查找多条user对象
List<User> getUserList(Object... parameters);
//查找所有user对象
List<User> getAllUser();
//增加一个user
int addUser(Object... parameters);
//删除一个user
int deleteUser(int userId);
//修改一个user的信息
int updateUser(Object... parameters);
}

  • xyz.shi.service.impl.UserServiceImpl 实现接口,这里就是编写具体的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package xyz.shi.service.impl;

import xyz.shi.dao.UserDao;
import xyz.shi.dao.impl.UserDaoImpl;
import xyz.shi.entity.User;
import xyz.shi.service.UserService;

import java.sql.SQLException;
import java.util.List;

public class UserServiceImpl implements UserService {
//创建UserDaoImpl对象
private UserDao userDao = new UserDaoImpl();
//查询所有
@Override
public List<User> getAllUser() {
String sql ="select * from `users`";
try {
return userDao.queryAllList(sql);
} catch (Exception e) {
System.out.println(e);
return null;
}
}
//根据userId查询一个user
@Override
public User getUser(String userId) {
String sql = "select * from `users` where id = ? ";
try {
return userDao.queryOne(sql,userId);
} catch (Exception e) {
System.out.println(e);
return null;
}
}
//根据userId和password查询一个user
@Override
public Boolean ableLogin (String name , String password) {
String sql ="select * from `users` where name = ? and password = ? ";
User user = userDao.queryOne(sql, name, password);

return user != null ? true : false;

}
//根据条件查询多个user
@Override
public List<User> getUserList(Object... parameters) {
String sql ="select * from `users` where ? = ? ";
try {
return userDao.queryList(sql,parameters);
} catch (Exception e) {
System.out.println(e);
return null;
}
}
//增加一个user
@Override
public int addUser(Object... parameters) {
String sql ="insert into `users` (name, password) values (?, ?)";
return userDao.update(sql, parameters);
}
//删除一个user
@Override
public int deleteUser(int userId) {
String sql ="delete from `users` where id = ? ";
return userDao.update(sql,userId);
}
//修改一个user的信息
@Override
public int updateUser(Object... parameters) {
String sql ="update `users` set name = ?,password = ? where id = ? ";
return userDao.update(sql,parameters);
}

}

表示层

  • 也就是用户界面层

  • xyz.shi.servlet 层,主要放jsp文件传过来的数据,然后把服务器数据传给jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package xyz.shi.servlet;

import xyz.shi.entity.User;
import xyz.shi.service.UserService;
import xyz.shi.service.impl.UserServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpSession;

@WebServlet("/login")
public class LoginServlet extends HttpServlet{
// 注意这里get的写法,当其他页面新增或者修改成功后,跳转到/login页面,然后自动读取用户列表数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String session = (String) req.getSession().getAttribute("username");
if (session != null) {
UserService userService = new UserServiceImpl();
List<User> users = userService.getAllUser();
req.setAttribute("userList", users);
req.getRequestDispatcher("homePage.jsp").forward(req, resp);;

}
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UserService userService = new UserServiceImpl();
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean login = userService.ableLogin(username, password);
// 验证用户名和密码
if (login) {
// 登录成功,创建会话
HttpSession session = request.getSession();
List<User> users = userService.getAllUser();
request.setAttribute("userList", users);

session.setAttribute("username", username);
System.out.println("登录成功");
request.getRequestDispatcher("homePage.jsp").forward(request, response);;
} else {
// 登录失败,返回登录页面
System.out.println("登录失败");
request.setAttribute("error", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);

}
}
}
  • 默认打开的是index.jsp界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<%
String user=(String)session.getAttribute("username");
if (user != null) {
// 用户已登录
out.println("<a href=\"homePage.jsp\">");
out.println("登录成功主页");
out.println("</a>");
} else {
// 用户未登录
out.println("<a href=\"login.jsp\">");
out.println("登录界面");
out.println("</a>");
}
%>
</body>
</html>
  • 登录界面login.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="login" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required><br><br>
<input type="submit" value="登录">
</form>
</body>
</html>
  • 登录后的界面homepage.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!DOCTYPE html>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<html>
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h1>欢迎登录,${username}!</h1>
<a href="add.jsp">添加</a>
<table>
<tr>
<td>id</td>
<td>用户名</td>
<td>状态</td>
<td>操作</td>
</tr>
<c:forEach var="item" items="${requestScope.userList}">
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td><a href="userFind?id=${item.id}">修改</a>
<td><a href="<%=request.getContextPath()%>/userDel?id=${item.id}">删 除</a></td>
</tr>
</c:forEach>

</table>
<a href="logout">注销</a>
</body>
</html>

登录成功后LoginServlet把用户列表传给了homePage.jsp,后续的修改,删除逻辑也是一样

servlet层

  • 比如下面这是修改用户名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package xyz.shi.servlet;

import xyz.shi.entity.User;
import xyz.shi.service.UserService;
import xyz.shi.service.impl.UserServiceImpl;
import xyz.shi.utils.GetUsersUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/userUpdate")
public class UpdateUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UserService userService = new UserServiceImpl();
int userId = Integer.parseInt(request.getParameter("id"));
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User(userId, username, password);
int flag = userService.modify(user);
if (flag > 0) {
System.out.println("修改数据成功");
GetUsersUtils.GetUsers(request, response);
} else {
// 登录失败,返回登录页面
System.out.println("修改数据失败");
request.setAttribute("message", "failed");

}
}
}

说明

  • 本编开始学习JavaWeb开发的内容,环境信息如下:IDEA,Maven,win10,java version "1.8.0_381"

搭建Servlet

在IDEA中新建webapp

image-20231129101815913

  • 注意maven设置为本地

image-20231129102658617

  • 在根目录新建:src\main\javasrc\main\resourcessrc\main\webapp

  • 在pom.xml中加入依赖文件

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
  • 手动新建src\main\webapp\WEB-INF\web.xml
1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
</web-app>
  • src-mian-java 新建一个包名为xyz.shi.servlet , 然后在报名下新建MyServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package xyz.shi.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;

@WebServlet("/hello")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get请求");
resp.getWriter().write("hello world");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post请求");
resp.getWriter().write("你好");

}
}


  • @WebServlet()会根据某些GET请求会生效, 然后里面写的 /hello,表示当请求的url是 /hello 的请求才会生效
  • 重写了父类中的get和post方法
  • 虽然打war 复制到Tomcat的webapps里面运行是一种方法,但是这种方法比较麻烦.所以我更推荐使用Smart Tomcat

image-20231130163237555.png

  • 打开编辑配置

image-20231130163329491

  • 并选择本地tomcat的根目录,设置路径等,端口不用改

image-20231201154747879

  • 点击运行按钮

image-20231130163627514

  • 出现如下信息说明启动成功
1
2
3
30-Nov-2023 16:36:32.793 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
30-Nov-2023 16:36:32.807 信息 [main] org.apache.catalina.startup.Catalina.start [594]毫秒后服务器启动
http://localhost:8080/WebTest
  • 浏览器访问http://localhost:8080/WebTest/hello

image-20231130163758294

  • MyServlet中的WebServlet("/hello") 的注解去掉
1
2
3
4
5
6
7
8
9
10
11
12
ackage xyz.shi.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;

public class MyServlet extends HttpServlet {
@Override
....
  • 在web.xml文件下,配置Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!--servlet命名-->
<servlet-name>MyServlet</servlet-name>
<!--servlet类的全限定名(路径+文件名)-->
<servlet-class>xyz.shi.servlet.MyServlet</servlet-class>
</servlet>
<!--定义Servlet的请求映射-->
<servlet-mapping>
<!--要映射的servlet名,与上方定义的servlet-name一致-->
<servlet-name>MyServlet</servlet-name>
<!--请求映射url,必须以/开头-->
<!--之后通过项目上下文路径+该路径,就能访问FirstServlet类-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

  • 重写运行,在浏览器中访问http://localhost:8080/WebTest/hello

总结

有两种运行Servlet的方式

  • 第一种是在类中直接加上WebServlet("/hello")的标识,然后启动
  • 第二种是在web.xml中进行配置,推荐第二种方式更加方便管理

说明

  • 之前用go写过一篇爬虫的实例,本次用java来写
  • 需要掌握http请求的编写

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Craw {
public static void main(String[] args) throws Exception {
for(int i=2;i<3;i++) {
String url= "http://www.52desktop.cn/html/DLZM/KPBZ/20191205/15898_" +Integer.toString(i) + ".html";
String pageStr = String.valueOf(GetPageStr(url));
String regImg = "http://i.52desktop.cn:81/upimg/allimg/[^\\\"]+?(\\.(jpg|png|jpeg|gif|bmp))";
Pattern pattern = Pattern.compile(regImg);
Matcher matcher = pattern.matcher(pageStr);
if (matcher.find()) {
System.out.println(matcher.groupCount());
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
if(matcher.group(0).contains("20191204")) {
downloadImg("d:\\img\\", matcher.group(0));
}
}

}
}
private static StringBuffer GetPageStr(String url) throws Exception {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 发送get请求
con.setRequestMethod("GET");
// 通过con.getInputStream() 获取到与服务器建立的输入流,即服务端返回的数据流
//InputStreamReader将字节流转换为字符流,而BufferedReader提供了缓冲和高效读取的功能,常常用来包装InputStreamReader以提供更好的读取性能。
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
//不断地调用 BufferedReader 的 readLine() 方法,每次读取一行数据,并将其加入到 StringBuffer 中。
// 当 readLine() 方法返回 null 时,表示已经读取完了所有的数据,循环结束
while ((inputLine=in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// 打印服务器的响应
System.out.println("GET Response: " + response.toString());
return response;
}
private static void downloadImg(String savePath, String url) throws IOException {
URL imageUrl = new URL(url);
HttpURLConnection con = (HttpURLConnection) imageUrl.openConnection();
// 发送get请求
con.setRequestMethod("GET");
try(InputStream inputStream= con.getInputStream()) {
// 获取当前图片名称
String fileName = url.substring(url.lastIndexOf("/") +1);
// 获取保存图片路径
Path filePath = Paths.get(savePath, fileName);
// 将输入流中的内容复制到指定的目标文件
Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
System.out.println(fileName + " download is successful");
}

}
}

如上代码的确可以运行,但是发现目标地址基本上无法访问了

  • 爬其他目标网站的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;


public class Craw {
public static void main(String[] args) throws Exception {
String url= "https://sj.zol.com.cn/bizhi/detail_12078_130945.html";
// 读取html页面的内容
String pageStr = String.valueOf(GetPageStr(url));
// 用Jsoup转换为dom格式进行过滤
Document document = Jsoup.parse(pageStr);
// css的语法
Elements elements = document.select("ul#showImg li");
// 循环读取img
for(Element li: elements) {
Element img = li.selectFirst("img");
String imgUrl;
if (img.attr("src").length() > 0) {
imgUrl = img.attr("src");
} else {
imgUrl = img.attr("srcs");
}
System.out.println(imgUrl);
downloadImg("d:\\img\\", imgUrl);

}
}
private static StringBuffer GetPageStr(String url) throws Exception {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 发送get请求
con.setRequestMethod("GET");
// 通过con.getInputStream() 获取到与服务器建立的输入流,即服务端返回的数据流
//InputStreamReader将字节流转换为字符流,而BufferedReader提供了缓冲和高效读取的功能,常常用来包装InputStreamReader以提供更好的读取性能。
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
//不断地调用 BufferedReader 的 readLine() 方法,每次读取一行数据,并将其加入到 StringBuffer 中。
// 当 readLine() 方法返回 null 时,表示已经读取完了所有的数据,循环结束
while ((inputLine=in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// 打印服务器的响应
System.out.println("GET Response: " + response.toString());
return response;
}
private static void downloadImg(String savePath, String url) throws IOException {
URL imageUrl = new URL(url);
HttpURLConnection con = (HttpURLConnection) imageUrl.openConnection();
// 发送get请求
con.setRequestMethod("GET");
try(InputStream inputStream= con.getInputStream()) {
// 获取当前图片名称
// String fileName = url.substring(url.lastIndexOf("/") +1);
// 生成唯一格式图片
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
String fileName = now.format(formatter) + ".jpg";
// 获取保存图片路径
Path filePath = Paths.get(savePath, fileName);
// 将输入流中的内容复制到指定的目标文件
Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
System.out.println(fileName + " download is successful");
}

}
}

本次目标网站采用jsoup的读取图片路径,然后用http下载到本地

需要在pom.xml中加入依赖文件

org.jsoup jsoup 1.14.3
  • 下面代码再次改进,采用多线程的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class ThreadCraw {
public static void main(String[] args) throws Exception {
String url= "https://sj.zol.com.cn/bizhi/detail_12078_130945.html";
// 读取html页面的内容
String pageStr = String.valueOf(GetPageStr(url));
// 用Jsoup转换为dom格式进行过滤
Document document = Jsoup.parse(pageStr);
// css的语法
Elements elements = document.select("ul#showImg li");
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(elements.size());
// 循环读取img
for(Element li: elements) {
Element img = li.selectFirst("img");
String imgUrl;
if (img.attr("src").length() > 0) {
imgUrl = img.attr("src");
} else {
imgUrl = img.attr("srcs");
}
// downloadImg("d:\\img\\", imgUrl);
// 创建并启动下载线程
ImageDownloader downloader = new ImageDownloader("d:\\img\\", imgUrl);
executor.execute(downloader);
}
// 关闭线程池
executor.shutdown();
}
private static StringBuffer GetPageStr(String url) throws Exception {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 发送get请求
con.setRequestMethod("GET");
// 通过con.getInputStream() 获取到与服务器建立的输入流,即服务端返回的数据流
//InputStreamReader将字节流转换为字符流,而BufferedReader提供了缓冲和高效读取的功能,常常用来包装InputStreamReader以提供更好的读取性能。
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
//不断地调用 BufferedReader 的 readLine() 方法,每次读取一行数据,并将其加入到 StringBuffer 中。
// 当 readLine() 方法返回 null 时,表示已经读取完了所有的数据,循环结束
while ((inputLine=in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// 打印服务器的响应
System.out.println("GET Response: " + response.toString());
return response;
}
private static void downloadImg(String savePath, String url) throws IOException {

URL imageUrl = new URL(url);
HttpURLConnection con = (HttpURLConnection) imageUrl.openConnection();
// 发送get请求
con.setRequestMethod("GET");
try(InputStream inputStream= con.getInputStream()) {
// 获取当前图片名称
// String fileName = url.substring(url.lastIndexOf("/") +1);
// 生成唯一格式图片
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
String fileName = now.format(formatter) + ".jpg";
// 获取保存图片路径
Path filePath = Paths.get(savePath, fileName);
try {
// 将输入流中的内容复制到指定的目标文件
Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
System.out.println(fileName + " download is successful");
} catch (FileAlreadyExistsException e) {

}

}
}

static class ImageDownloader extends Thread {

private String savePath;
private String imgUrl;

public ImageDownloader(String savePath, String imgUrl) {
this.savePath = savePath;
this.imgUrl = imgUrl;
}

@Override
public void run() {
try {
downloadImg(savePath, imgUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

关键代码

  • 创建线程池: ExecutorService executor = Executors.newFixedThreadPool(elements.size());

  • 最后执行线程

1
2
ImageDownloader downloader = new ImageDownloader("d:\\img\\", imgUrl);
executor.execute(downloader);
  • 其实更优雅的方式应该是这种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void main(String[] args) throws Exception {
String url = "https://sj.zol.com.cn/bizhi/detail_12078_130945.html";
// 读取html页面的内容
String pageStr = String.valueOf(GetPageStr(url));
// 用Jsoup转换为dom格式进行过滤
Document document = Jsoup.parse(pageStr);
// css的语法
Elements elements = document.select("ul#showImg li");

// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(elements.size());
List<Future<Void>> futures = new ArrayList<>();

// 循环读取img
for (Element li : elements) {
Element img = li.selectFirst("img");
String imgUrl;
if (img.attr("src").length() > 0) {
imgUrl = img.attr("src");
} else {
imgUrl = img.attr("srcs");
}

// 提交下载任务到线程池
Callable<Void> downloadTask = () -> {
downloadImg("d:\\img\\", imgUrl);
return null;
};
Future<Void> future = executor.submit(downloadTask);
futures.add(future);
}

// 等待所有任务完成
for (Future<Void> future : futures) {
future.get();
}

// 关闭线程池
executor.shutdown();
}

说明

  • 进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

  • 多进程的优点:多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。

  • 场景场景:一个进程中至少一个线程,一个进程中多个线程,多个进程中多个线程

  • Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行main()方法。在main()方法中,我们又可以启动其他线程

继承Thread

  • Thread派生一个自定义类,然后覆写run()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Main {
public static void main(String[] args) {
//创建线程
MyThread t01 = new MyThread();
MyThread t02 = new MyThread();
MyThread t03 = new MyThread("线程03");
t01.start();
t02.start();
t03.start();

//设置线程名(补救的设置线程名的方式)
t01.setName("线程01");
t02.setName("线程02");
// 设置主线程名称
// Thread.currentThread().setName("主线程");
for(int i=0;i<50;i++) {
// Thread.currentThread() 获取当前正在执行线程的对象
System.out.println("主线程" + Thread.currentThread().getName() + ":" + i);
}


}
}
class MyThread extends Thread {

public MyThread() {
}
public MyThread(String name) {
super(name);
}
//run方法是每个线程运行过程中都必须执行的方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("子线程" + this.getName() + ":" + i);
}
}
}

  • 打印结果发现:主线程50个,子线程有150个(3*50),可以看出来,主线程在和子线程抢占CPU的过程中,交替打印结果

此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。

启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。

run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

单纯使用run()方法是不能多线程并发的。

实现Runnable

  • 实现Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class demo {

public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
//创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
//创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
Thread tread = new Thread(runnable);
tread.start();

//通过匿名内部类的方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println( "主线程:" + i);
}
}
}).start();
}
}

class MyRunnable implements Runnable {

@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println( "子线程:" + i);
}
}
}

实现Callable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestThread {

public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(()->{
for (int i = 0; i < 50; i++) {
System.out.println("子线程1执行中:"+i);
}
return "线程1执行完毕";
});

FutureTask<String> futureTask2 = new FutureTask<>(()->{
for (int i = 0; i < 50; i++) {
System.out.println("子线程2执行中:"+i);
}
return "线程2执行完毕";
});

new Thread(futureTask).start();
new Thread(futureTask2).start();
//get 获取线程最终返回的值
System.out.println(futureTask.get());
System.out.println(futureTask2.get());
}
}

线程池

  • Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但是便捷不仅隐藏了复杂性,也为我们埋下了潜在的隐患(OOM,线程耗尽)。

  • Executors创建线程池便捷方法列表:

方法名 功能
newFixedThreadPool(int nThreads) 创建固定大小的线程池
newSingleThreadExecutor() 创建只有一个线程的线程池
newCachedThreadPool() 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class TestThread {

public static void main(String[] args) throws ExecutionException, InterruptedException {
testFixedThreadPool();
}

private static void testFixedThreadPool() {
ExecutorService fixThread = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
int finalI = i;
fixThread.execute(new Runnable() {
@Override
public void run() {
//每次只能三个一起执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Thread.currentThread().setName("线程");
System.out.println(Thread.currentThread().getName() + " " + finalI);
}
});
}
}

}

  • 后续实践和分析可以看此文章

总结

  • 看了以上四种分析,我们可以清晰的发现了java中其实创建线程的方式就只有一种就是利用Thread+Runnable来实现多线程。其他多有方式都是对这个实现方式的变种。
  • 当然操作线程还有更多的知识,比如设置线程的一些状态、设置线程同步等,更加详细知识参考这里
1
2
3
4
5
6
setPriority  # 设置线程优先级
sleep # 让线程进入阻塞状态
join # 当一个线程调用join方法时,该线程会强制抢占cpu资源,直到该线程执行完毕其他线程才会继续执行
yield # 释放cpu资源,让当前线程进入就绪状态,礼让操作,重新和其他线程竞争cpu资源,可能不一定成功
interrupt # 中断线程
setDaemon # 将线程设置为守护线程,用来守护用户线程的,虚拟机只会保证用户线程的执行完毕,守护线程会随着虚拟机的关闭而关闭。

添加依赖

  • 首先需要在pom.xml添加依赖文件,本次用的阿里提供的解析json的包
1
2
3
4
5
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.42</version>
</dependency>
  • 常见用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package org.example;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;

public class Main {
public static void main(String[] args) {
String jsonString = "{\"name\":\"Tom\",\"age\":20,\"address\":{\"street\":\"123 Main St\",\"city\":\"New York\",\"state\":\"NY\"},\"phoneNumbers\":[{\"type\":\"home\",\"number\":\"123-456-7890\"},{\"type\":\"work\",\"number\":\"111-222-3333\"}]}";

// 将JSON字符串解析为JSONObject对象
JSONObject jsonObj = JSON.parseObject(jsonString);
// 新增
jsonObj.put("id", 1001);
// 新增一个数组
String[] strings = {"哈哈","忸怩"};
jsonObj.put("test",strings);
// ...{"id":1001,"test":["哈哈","忸怩"]}
System.out.println(jsonObj);

// 从JSONObject中获取属性值
String name = jsonObj.getString("name");
int age = jsonObj.getIntValue("age");
// map里面嵌套map
JSONObject address = jsonObj.getJSONObject("address");
String street = address.getString("street");
String city = address.getString("city");
String state = address.getString("state");
// 循环json中的数组
JSONArray phoneNumbers = jsonObj.getJSONArray("phoneNumbers");
for (int i = 0; i < phoneNumbers.size(); i++) {
JSONObject phone = phoneNumbers.getJSONObject(i);
String type = phone.getString("type");
String number = phone.getString("number");
System.out.println(type + ": " + number);
}
}
}

还有其他更多用法,比如删除、替换、查询是否存在等,不在演示

构建json对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;

public class JsonToBean {
public static void main(String[] args) {
// Json对象中添加的是键值对
JSONObject jsonObject = new JSONObject();
jsonObject.put("1","a");
jsonObject.put("2","b");
jsonObject.put("3","c");
// JSONArray中添加的是Json对象
JSONArray JsonArray = new JSONArray();

jsonObject.put("key", "value");//JSONObject对象中添加键值对
JsonArray.add(jsonObject);//将JSONObject对象添加到Json数组中

System.out.println(jsonObject);
System.out.println(JsonArray);
}
}

结果如下:

{“1”:”a”,”2”:”b”,”3”:”c”,”key”:”value”}
[{“1”:”a”,”2”:”b”,”3”:”c”,”key”:”value”}]

JavaBean转Json

  • user.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.example;

public class User {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getSex() {
return sex;
}
@Override
public String toString() {
return "user:id" + id +"-name:" + name + "-sex:" + sex;
}
}

  • JsonToBean.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;
import com.alibaba.fastjson2.JSONObject;

public class JsonToBean {
public static void main(String[] args) {
User user = new User();
user.setSex(null);
user.setName("行三");
user.setId(1);
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(user));
System.out.println(jsonObject);
}
}

结果

{“id”:1,”name”:”行三”}

sex为null丢失了

Json转JavaBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.example;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;

import java.util.List;

public class JsonToBean {
public static void main(String[] args) {
String jsonString = "{\"name\":\"Tom\",\"sex\":\"男\"}";
// 前面是JSON字符串 后面是java对象类型
User user=JSONObject.parseObject(jsonString,User.class);
// name: Tom---sex: 男
System.out.println("name: "+user.getName()+"---"+"sex: "+user.getSex());
String listStr = "[{\"name\":\"Tom\",\"sex\":\"男\"},{\"name\":\"jay\",\"sex\":\"女\"}]";

// 转Json List
JSONArray jsonArray = JSONArray.parseArray(listStr);
// [{"name":"Tom","sex":"男"},{"name":"jay","sex":"女"}]
System.out.println(jsonArray);
// 转具体的List<实体类> 然后循环
List<User> userList = JSONArray.parseArray(listStr, User.class);
for (User item : userList){
System.out.println(item.toString());
System.out.println(item.getName());
}

}
}

说明

  • 本次主要针对jdbc的知识点进行练习,用到了maven,mysql,idea等知识点

  • 本次的java版本如下

1
2
3
4
C:\Users\Administrator>java -version
java version "1.8.0_381"
Java(TM) SE Runtime Environment (build 1.8.0_381-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.381-b09, mixed mode)

配置

  • 在IDEA里新建一个maven项目,项目名叫做:MavenMysqlJdbc

  • 本地win搭建好mysql服务,本次搭建的的版本为:8.0.13

  • 打开src下的pom.xml文件, 在里面添加Mysql的jdbc包的引用,代码如下

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
  • 自动下载依赖文件中,可能出现卡死,需要maven的内存:-Xms1024m -Xmx2048m修改如下:

image-20231115174256836

反射properties

  • 新建数据库配置文件,获取配置文件信息后,再注册数据库驱动,在src——main——resources目录下,新建db.properties文件
1
2
3
4
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai
user=root
password=123456
  • 新建util包,然后在里面创建JdbcUtil类,利用反射获取db.properties文件信息,最后返回数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package util;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcUtil {
private static String driver;
private static String url;
private static String user;
private static String pwd;

static {
try{
Properties properties = new Properties();
// 通过反射,新建字符输入流,读取db.properties文件
InputStream inputStream = JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties");
// 输入流中读取到的属性,加载到properties属性集对象中
properties.load(inputStream);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
pwd = properties.getProperty("password");


} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
// 注册数据库驱动
Class.forName(driver);
//获取数据库连接(里面内容依次是:主机名和端口、用户名、密码),返回数据库链接
return DriverManager.getConnection(url,user, pwd);
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException(e);
}
}
}

  • 在java(src-main-java)目录下创建LinkMysql类,调用JdbcUtil类返回的数据库连接操作数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import util.JdbcUtil;
public class LinkMysql {
public static void main(String[] args) throws SQLException {
// 获取数据库链接
Connection connection = JdbcUtil.getConnection();
//需要执行的sql语句
String sql = "insert into class(name,sex) values(?,?)";
///获取预处理对象,并给参数赋值
PreparedStatement statement = connection.prepareCall(sql);
statement.setString(1, "刘亦菲");
statement.setString(2, "女");
//执行sql语句(插入了几条记录,就返回几)
int num = statement.executeUpdate();
System.out.println(num);

// 删除数据
String sql1 = "delete from class where id=17";
PreparedStatement statement1 = connection.prepareCall(sql1);
int num1 = statement1.executeUpdate();
System.out.println(num1);
// 关闭sql链接
statement.close();
connection.close();
}
}


ResourceBundle类获取properties

  • 在util包里面创建创建JdbcUtil2类,ResourceBundle类获取db.properties文件信息,最后返回数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package util;

import java.sql.*;
import java.util.ResourceBundle;

public class JdbcUtil2 {
private static String url;
private static String user;
private static String pwd;

static {
try {
ResourceBundle resourceBundle = ResourceBundle.getBundle("db");
url = resourceBundle.getString("url");
pwd = resourceBundle.getString("password");
user = resourceBundle.getString("user");
} catch (Exception e) {
throw new RuntimeException(e);
}
}


public static Connection getConnection() {
try {
//获取数据库连接(里面内容依次是:主机名和端口、用户名、密码),返回数据库链接
return DriverManager.getConnection(url,user, pwd);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

// 关闭结果集
public static void closeResultSet(ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
// 关闭预处理对象
public static void closeStatement(Statement statement) throws SQLException {
if (statement != null) {
statement.close();
}
}
// 关闭数据库连接
public static void closeConnection(Connection connection) throws SQLException {
if (connection != null) {
connection.close();
}
}
// 关闭如上三个
public static void closeResource(ResultSet resultSet, Statement statement, Connection connection) throws SQLException {
closeResultSet(resultSet);
closeStatement(statement);
closeConnection(connection);
}
}

  • 在java目录下创建LinkMysql2类,调用JdbcUtil2类返回的数据库连接操作数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import util.JdbcUtil2;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LinkMysql2 {
public static void main(String[] args) throws SQLException {
// 获取数据库链接
Connection connection = JdbcUtil2.getConnection();
//需要执行的sql语句
String sql = "insert into class(name,sex) values(?,?)";
///获取预处理对象,并给参数赋值
PreparedStatement statement = connection.prepareCall(sql);
statement.setString(1, "刘亦菲");
statement.setString(2, "女");
//执行sql语句(插入了几条记录,就返回几)
int num = statement.executeUpdate();
System.out.println(num);

// 删除数据
String sql1 = "delete from class where id=17";
PreparedStatement statement1 = connection.prepareCall(sql1);
int num1 = statement1.executeUpdate();
System.out.println(num1);
// 关闭sql链接
JdbcUtil2.closeResource(null, statement, connection);
}
}

DButils工具包

  • 打开src下的pom.xml文件, 在里面添加DButils的引用,代码如下
1
2
3
4
5
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.5</version>
</dependency>
  • 创建src-java-main-Dbutils类,往数据库内进行增删改查据,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import util.JdbcUtil2;

import java.sql.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

public class Dbutils {

public static void main(String[] args) throws SQLException {
//创建dbUtils里面的QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "insert into class(name,sex) values(?,?)";
Object[] object = {"自然", "男"};
Connection connection = JdbcUtil2.getConnection();
// 增加
int num = queryRunner.update(connection,sql,object);

System.out.println("新增数据行:" + num);

String updateSql = "update class set name=? where id=?";
Object[] object2 = {"自然2", 20};
// 修改
int num2 = queryRunner.update(connection,updateSql,object2);
System.out.println("修改数据行:" + num2);

String delSql = "delete from class where id=?";
Object[] object3 = {20};
// 删除
int num3 = queryRunner.update(connection,delSql,object3);
System.out.println("删除行:"+num3);

String sqlSelect = "select * from class";
// 执行查询,并以数组的形式返回查询结果(new ArrayHandler()只会返回第一条记录)
Object[] objects = queryRunner.query(connection,sqlSelect,new ArrayHandler());
System.out.println("单行查询结果:" + Arrays.toString(objects));
System.out.println("\n");
//执行查询,并以数组的形式返回查询结果(new ArrayListHandler()返回所有查询到的记录)
List<Object[]> lists = queryRunner.query(connection,sqlSelect,new ArrayListHandler());
for(Object[] item: lists) {
System.out.println("多行查询:" + Arrays.toString(item));
}
connection.close();
}
}

转换为实体

  • 新建一个表的实体类,src-java-main-Classs(当初我新建这个表瞎编的。。。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Classs {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getSex() {
return sex;
}
@Override
public String toString() {
return "class:id" + id +"name:" + name + "sex:" + sex;
}
}

  • 新建一个src-main-java-DbutilsBean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import util.JdbcUtil2;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

public class DbutilsBean {
public static void main(String[] args) throws SQLException {
//创建dbUtils里面的QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
Connection connection = JdbcUtil2.getConnection();

String sqlSelect = "select * from class";
// 执行查询,并以数组的形式返回查询结果(new BeanHandler()只会返回第一条记录,并转成对象)
Classs classs = queryRunner.query(connection,sqlSelect,new BeanHandler<Classs>(Classs.class));
System.out.println("单行查询结果:" + classs);

System.out.println("\n");
//执行查询,并以数组的形式返回查询结果(new BeanListHandler()返回查询到的所有记录,并转成对象)
List<Classs> lists = queryRunner.query(connection,sqlSelect,new BeanListHandler<Classs>(Classs.class));
for(Classs item: lists) {
System.out.println("多行查询:" + item.getName());
}
System.out.println(lists);
connection.close();
}
}

  • 得到结果
1
2
3
4
多行查询:综艺一般
多行查询:五年一班
多行查询:王大炮
多行查询:刘大师
  • 更多用法
1
2
3
4
5
6
7
8
9

String sql = "select * from stu where age>?";
...
Object[] params = {16};
// 会返回结果中指定的列,比如返回name
List<Object> strs = queryRunner.query(connection,sql, new ColumnListHandler<>("name"),params);
//查询单数据
int age = queryRunner.query(connection,sql, new ScalarHandler<>(),params);
System.out.println(age);

连接池

  • 在scr——pom.xml文件里,引入c3p0包
1
2
3
4
5
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
  • 在src——main——resources下增加c3p0-config.mxl文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 扩展配置 -->
<!-- 获取连接超时设置,默认是一直等待,单位毫秒 -->
<property name="checkoutTimeout">30000</property>
<!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">30</property>
<!-- 初始化连接池的连接数 -->
<property name="initialPoolSize">10</property>
<!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">30</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">100</property>
<!-- 连接池中保留的最小连接数 -->
<property name="minPoolSize">10</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements">200</property>
<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
<property name="maxStatementsPerConnection">200</property>
<!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
<property name="numHelperThreads">3</property>
</default-config>
</c3p0-config>
  • 在src——main——java——util里添加DataSourceUtils类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;

public class DataSourceUtils {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

/**
* 获取数据源
* @return 连接池
*/
public static DataSource getDataSource() {
return dataSource;
}
}

  • 新建src-main-java-C3p0Select类,用数据库连接池的方式查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import util.DataSourceUtils;

import java.sql.SQLException;
import java.util.List;

public class C3p0Select {

public static void main(String[] args) throws SQLException {
// 创建dbUtils里面的QueryRunner对象,并获取数据库连接
QueryRunner queryRunner = new QueryRunner(DataSourceUtils.getDataSource());
// String sqlSelect = "select * from class where id>?";
String sqlSelect = "select * from class";
Object[] params = {16};

//执行查询,并以数组的形式返回查询结果(new BeanListHandler()返回查询到的所有记录,并转成对象)
// List<Classs> lists = queryRunner.query(sqlSelect,new BeanListHandler<Classs>(Classs.class), params);
List<Classs> lists = queryRunner.query(sqlSelect,new BeanListHandler<Classs>(Classs.class));
for(Classs item: lists) {
System.out.println("多行查询:" + item.getName());
}
System.out.println(lists);
}
}

  • 得到结果
1
2
3
4
多行查询:综艺一般
多行查询:五年一班
多行查询:王大炮
多行查询:刘大师

注意

  • 此练习代码主要来自这里

配置

  • 打开官网 下载bin.zip压缩文件,解压到本地后,设置环境变量
1
2
3
4
5
6
C:\Users\Administrator>mvn -version
Apache Maven 3.9.5 (57804ffe001d7215b5e7bcb531cf83df38f93546)
Maven home: E:\exe\apache-maven-3.9.5
Java version: 1.8.0_381, vendor: Oracle Corporation, runtime: E:\app\Java\jdk-1.8\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
  • 在文件中我们要在文件中对下载相对于的jar包进行镜像配置,一般是使用阿里云镜像。
1
2
3
4
5
6
7
8
// apache-maven-3.9.5\conf\setting.xml
<!-- 拉去阿里镜像-->
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
  • 们还需要在里面配置本地的Maven仓库。下面这句话就是我的本地仓库位置。
1
<localRepository>D:\Maven\mm</localRepository>
  • 打开Idea然后选择File =》New =》 Project 得到下面的界面

image-20231114165504799

  • 在文件的创建过程中我们要选择修改Maven的仓库为本地的仓库

image-20231114170122121

  • 一个使用Maven管理的普通的Java项目,它的目录结构默认如下,如果没有就手动新建
1
2
3
4
5
6
7
8
9
10
testMvn
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── target
  • pom.xml就是引用依赖包

  • 存放Java源码的目录是src/main/java

  • 存放资源文件的目录是src/main/resources

  • 存放测试源码的目录是src/test/java,存放测试资源的目录是src/test/resources

  • 最后,所有编译、打包生成的文件都放在target目录里

分析pom

  • 分析pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId> <!--类似于Java的包名,通常是公司或组织名称 -->
<artifactId>testMvn</artifactId> <!--类似于Java的类名,通常是项目名称 -->
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

</project>
  • 一个Maven工程就是由groupIdartifactIdversion作为唯一标识。我们在引用其他第三方库的时候,也是通过这3个变量确定。例如,依赖commons-logging
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>testMvn</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>

使用<dependency>声明一个依赖后,Maven就会自动下载这个依赖包并把它放到classpath中

在D:\Maven\mm 本地仓库中可以看到自动下载的依赖文件

依赖关系

Maven定义了几种依赖关系,分别是compiletestruntimeprovided

scope 说明 示例
compile 编译时需要用到该jar包(默认) commons-logging
test 编译Test时需要用到该jar包 junit
runtime 编译时不需要,但运行时需要用到 mysql
provided 编译时需要用到,但运行时由JDK或某个服务器提供 servlet-api
  • 其中,默认的compile是最常用的,Maven会把这种类型的依赖直接放入classpath。

  • test依赖表示仅在测试时使用,正常运行时并不需要。最常用的test依赖就是JUnit:

1
2
3
4
5
6
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>

搜索第三方组件

后一个问题:如果我们要引用一个第三方组件,比如okhttp,如何确切地获得它的groupIdartifactIdversion?方法是通过search.maven.org搜索关键字,找到对应的组件后,直接复制:

image-20231114174716157

常用命令

  • mvn clean:清理所有生成的class和jar;

  • mvn clean compile:先清理,再执行到compile

  • mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile

  • mvn clean package:先清理,再执行到package

更多详解参考这里,后续这里还有很多关于maven的知识等能用到的时候回头继续学习

socker

  • Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或者应答网络请求
  • 常用的Socket类型有两种:流式Socket和数据报式Socket,流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,数据报式Socket是一种无连接的Socket,针对于无连接的UDP服务应用
  • TCP:比较靠谱,面向连接,比较慢
  • UDP:不是太靠谱,比较快
  • 举个例子:TCP就像货到付款的快递,送到家还必须见到你人才算一整套流程。UDP就像某快递快递柜一扔就走管你收到收不到,一般直播用UDP。

TCP编程

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread {
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
// serverSocket.setSoTimeout(1000);

}
public void run() {
try {
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
// 连接服务器
Socket server = serverSocket.accept();
System.out.println("客户端已连接:" + server.getInetAddress());
//构建IO流
while(true){
// 接受客户端
BufferedReader br = new BufferedReader(new InputStreamReader(server.getInputStream()));
String mess = br.readLine();
System.out.println("接受客户端:" + mess);

// 发送信息到客户端
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(server.getOutputStream()));
//如果客户端发送内容q,那么服务器就断开连接
if (mess.equals("q")) {
String str1 = "服务器返回>>bye\n";
bw.write(str1);
bw.flush();
System.out.println("服务器关闭");

break;
} else {
String str = "服务器返回>>"+mess+"\n";
bw.write(str);
bw.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try{
Thread t = new GreetingServer(6666);
t.run();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}


如果客户端发送内容q,那么服务器就断开连接

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 文件名 GreetingClient.java

import java.net.*;
import java.io.*;
import java.util.Scanner;

public class GreetingClient
{
public static void main(String [] args)
{

try {
Socket s = new Socket("127.0.0.1",6666);
System.out.println("客户端IP:"+s.getLocalAddress()+"端口"+s.getPort());
//构建IO流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

//建立键盘输入:
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入发送消息内容:");
bw.write(scanner.nextLine()+"\n");
bw.newLine();
bw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//读取服务器返回的消息数据
String ser_msg = br.readLine();
System.out.println(ser_msg);
System.out.println(s.getInetAddress().getLocalHost()+":"+s.getPort()+">>"+ser_msg);
// 服务器发送bye的内容后,服务器就主动断开链接,客户端也要关闭
if (ser_msg.contains("bye")) {
System.out.println("客户端关闭");
break;
}
}

} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
}

客户端输入q后,接收到服务器发送bye的内容后,服务器就主动断开链接,客户端也要关闭

http

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

public class SimpleHttpServer {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/get", new GetHandler());
server.createContext("/post", new PostHandler());
server.setExecutor(null); // creates a default executor
server.start();
System.out.println("Server is listening on port 8000");
}

// get请求处理
static class GetHandler implements HttpHandler {
// 重写handle方法
@Override
public void handle(HttpExchange exchange) throws IOException {
String requestParamValue = null;
if ("GET".equals(exchange.getRequestMethod())) {
requestParamValue = handleGetRequest(exchange);
}
String response = "GET request received with parameter: " + requestParamValue;
sendResponse(exchange, response);
}
}
// post请求处理
static class PostHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String requestParamValue = null;
if ("POST".equals(exchange.getRequestMethod())) {
requestParamValue = handlePostRequest(exchange);
}
String response = "POST request received with JSON data: " + requestParamValue;
sendResponse(exchange, response);
}
}
// 获取get请求的参数
private static String handleGetRequest(HttpExchange exchange) {
return exchange.getRequestURI().getQuery();
}

// 获取post请求的参数
private static String handlePostRequest(HttpExchange exchange) throws IOException {
// 读取客户端发送的JSON数据
StringBuilder sb = new StringBuilder();
int i;
while ((i = exchange.getRequestBody().read()) != -1) {
sb.append((char) i);
}
return sb.toString();
}
// 发送请求到客户端
private static void sendResponse(HttpExchange exchange, String response) throws IOException {
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class SimpleHttpClient {
public static void main(String[] args) throws Exception {
sendGetRequest();
sendPostRequest();
}

private static void sendGetRequest() throws Exception {
String url = "http://localhost:8000/get?param1=value1&param2=value2";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// 发送get请求
con.setRequestMethod("GET");
// 通过con.getInputStream() 获取到与服务器建立的输入流,即服务端返回的数据流
//InputStreamReader将字节流转换为字符流,而BufferedReader提供了缓冲和高效读取的功能,常常用来包装InputStreamReader以提供更好的读取性能。
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
//不断地调用 BufferedReader 的 readLine() 方法,每次读取一行数据,并将其加入到 StringBuffer 中。
// 当 readLine() 方法返回 null 时,表示已经读取完了所有的数据,循环结束
while ((inputLine=in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// 打印服务器的响应
System.out.println("GET Response from server: " + response.toString());
}

private static void sendPostRequest() throws Exception {
String url = "http://localhost:8000/post";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");

String jsonInputString = "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}";

con.setDoOutput(true);
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}

BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();

while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();

// 打印服务器的响应
System.out.println("POST Response from server: " + response.toString());
}
}

  • BufferedReader 和InputStreamReader用法,一般都是BufferedReader 包装InputStreamReader。

  • StringBuffer 是一个可变字符串,作用是在处理大量字符串拼接操作时提供更高效的性能

  • 运行服务端和客户端代码得到结果:
1
2
GET Response from server: GET request received with parameter: param1=value1&param2=value2
POST Response from server: POST request received with JSON data: {"name":"John", "age":30, "city":"New York"}