SpringBoot基础二

扩展SpringBoot

扩展SpringBoot中的SpringMVC的默认配置
SpringBoot默认已经给我们做了很多SpringMVC的配置,哪些配置?
1、视图解析器ViewResolver
2、静态资料的目录
3、默认首页index.html
4、图标名字和图标所在目录,favicon.ico
5、类型转换器Converter,格式转换器的Formatter
6、消息转换器HttpMessageConverters,我们直接向页面返回JSON格式的数据就靠它实现
7、消息代码解析器MessageCodesResolver, 作用配置了错误代码显示规则的一些东西,JSR303数据校验要用到它
-----但是我们在真正的项目开发中,只有这些默认配置,是绝对不够用的,所以,我们必须得学会在它们的基础上扩展更多我们需要的配置,SpringBoot是给我们提供了相应扩展接口WebMvcConfigurer,实现它并且加@Configuretion注解:
举例子:
在原来我们直接使用SpringMVC的时候,我们可以在springmvc.xml配置文件中直接配置:
<mvc:view-controller path="/index.html" view-name="index"/>
可以实现不用在controller类里写映射方法,访问localhost:8080/index直接打开users2.htmlk页面

image.png

注意:这种配置方式是“扩展”SpringBoot对SpringMVC的配置,扩展就是原有的默认配置仍然生效,现在加了我们自己的配置一起生效,但是我们是可以通过一个注解 @EnableWebMvc ,让SpringBoot里默认配置失效的,配置这个注解后,就会只有我们自己做的配置才会生效了,就像我们原来自己使用SpringMVC一样的,什么都需要我们自己配置!

SpringBoot整合JDBC

----讲到这里,基本上我们就可以使用SpringBoot来开发Web项目视图显示和业务逻辑代码,但是要做一个完成案例,我们还差一点点,就是怎么访问数据库,获取数据,接下来我们就看怎么用 SpringBoot整合 我们前面已经讲过的 jdbc,mybatis,springdata jpa,其实本质上SpringBoot底层就是使用SpringData来访问数据库,而前面我们有简介SpringData,知道它不仅仅可以操作关系型数据还可以访问NoSql数据库,所以SpringBoot当然也就是关系和非关系数据库都可以通吃咯,NoSql这块我们后面课程中会详细的讲,
1、导入启动器

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.xiong.spbtjdbc</groupId>
    <artifactId>spbtjdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spbtjdbc</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
       <!-- <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

image.png

注意:刚开始创建项目时,选了parent 3.0.2出错,叫我降低牍本,到2.7.1和2.0.4.RELEASE都出错,后来改成2.7.6有用了不知为什么
2、配置全局配置文件,本人数据库mysql 8.0版本。

spring.datasource.username=root
spring.datasource.password=xiong
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

有关Spring里的数据源啊。。。。如c3p0连接池在SpringBoot都有默认配置的,直接就可以测试了!
3,测试,在test中测试

image.png

@SpringBootTest
class SpbtjdbcApplicationTests {
    @Autowired(required = false)
    private DataSource dataSource;
    @Autowired(required = false)
    private JdbcTemplate jdbcTemplate;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        Connection connection=dataSource.getConnection();
        System.out.println(connection);
        connection.close();
        List<Map<String,Object>> maps = jdbcTemplate.queryForList("select *from et_users");
        System.out.println(maps);
    }

}
------------------------------------------------------------结果-----------------------------------------------------------------------------
HikariProxyConnection@1005400853 wrapping com.mysql.cj.jdbc.ConnectionImpl@119b0892
[{id=1, username=xiongshaow, passwopd=123456}, {id=2, username=xuhuifeng, passwopd=123456}, {id=3, username=熊凌洲, passwopd=123456}]

Hikari为springboot2.0.6后版本使用的数据库连接池传说这个在数据库访问速度上是C3P0的25倍

SpringBoot整合druid数据源

-----SprintBoot默认使用的是HikariDataSource数据源,而且上次课中我们也说了这个数据源访问速度很快,但是这里还要给大家介绍一个第三方的数据源druid,它是阿里开发的一款开源的数据源,被很多人认为是Java语言中最好的数据库连接池,因为Druid能够提供强大的一整套监控和扩展功能,很爽。
1、导入maven的依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.11</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

使用了log4j就要自己在类路径下加配置文件:log4j.properties

log4j.rootLogger = debug,stdout, D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.Threshold = INFO
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ./log4j.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d %p %m%n

注意:druid依赖log4j的日志jar包,但是SpringBoot默认使用的是slf4j+logback,所以这里导入log4j的jar包才行

image.png

2、在全局配置文件通过type的配置改变数据源

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

3、在运行一下测试方法,看看能不能拿到数据源

@SpringBootTest
class SpbtjdbcApplicationTests {
    @Autowired(required = false)
    private DataSource dataSource;
    @Autowired(required = false)
    private JdbcTemplate jdbcTemplate;
    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
    }

}
-------------------------------------------------------后台显示结果有-----------------------------------------
class com.alibaba.druid.pool.DruidDataSource

4、不能用SpirngBoot默认利用反射的机制配置来获取druid数据源,因为数据源特有的配置SpringBoot默认配置是没有的,我们要自己做:
① 首先在全局配置文件中配上 druid 数据源自己的特性配置

spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
# 配置监控统计拦截的 filters ,去掉后监控界面 sql 无法统计, 'wall' 用于防火墙
spring.datasource.filters=stat,wall,log4j
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.useGlobalDataSourceStat=true
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

这些配置可以参考阿里巴巴放到guithu网站上的参数。https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
② 自己创建配置类来创建数据源对象,并且手动获取这些配置,配置到数据源里去

image.png

@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")  //把全局配置文件中的配置注入到对象中
    @Bean   //返回的DruidDataSource对象放到IOC容器中
    public DataSource dataSource(){
        return new DruidDataSource();
    }
}

5、配置druid的监控
① 配置管理后台的servlet,druid提供的这个servlet名字叫:StatViewServlet

② 配置web监控的拦截器filter,druid提供的filter名字叫 :WebStatFilter


image.png
@Configuration
public class DruidConfig {
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource dataSource(){
        return new DruidDataSource();
    }

    //配置对web整个应用的监控,在druid中
    @Bean
    public ServletRegistrationBean getViewServlet(){
        ServletRegistrationBean bean =new ServletRegistrationBean(new StatViewServlet());
        String[] urlArr = new String[]{"/druid/*"};
        bean.setUrlMappings(Arrays.asList(urlArr));
        //还需要设置druid后台页面的一些基本的东西,后台的登录用户名和密码,后台有哪些ip地址可以访问,哪些不可以
        Map<String,String> initParams = new HashMap<>();
        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        initParams.put("allow","");                //假设任何ip地址可访问
        initParams.put("deny","12.122.12.1");      //拒绝某一个ip访问,这里当然是假设的
        bean.setInitParameters(initParams);
        return bean;

    }

    //配置过滤器来寮现对web应用的所有的访问请求进行过滤,从而实现对web应用对数据库的访问监控
    @Bean
    public FilterRegistrationBean getFilter(){
        FilterRegistrationBean bean= new FilterRegistrationBean(new WebStatFilter());
        bean.setUrlPatterns(Arrays.asList(new String[]{"/*"}));
        Map<String,String> initParams = new HashMap<>();
        initParams.put("exclusions","*.js,*.css,*.jpg,*.png,/druid/*");  //不在我们拦截之范围 
        bean.setInitParameters(initParams);
        return bean;
    }
}

IndexController.java

@Controller
public class IndexController {
    @Autowired(required = false)
    private JdbcTemplate jdbcTemplate;
    @GetMapping("/index.html")
    public String index(Model model){
       List<Map<String,Object>> maps=jdbcTemplate.queryForList("select*from et_users");
       model.addAttribute("users",maps);
        return "index";
    }
}

index.html(thymeleaf视图模板)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>durid_thymeleaf</title>
</head>
<body>
   <div th:each="user:${users}"></div>
</body>
</html>

测试
1.打开监控页
http://localhost:8080/druid/回车输入用户名:admin,密码 :123456,点‘sql监控’项。
2。打开视图页index.html
http://localhost:8080/index.html
3.测试监控有没有效果。
-----当打开视图页,我们进行了一次查询,从监控页的,“sql监控‘项中可以看到查询语句。

image.png

SpringBoot整合Mybatis

  1. 准备工作
    1、建立一个新的工程(我用上一个工程spbtjdbc),把web、jdbc和mybatis的启动器选中
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>1.3.2</version>
</dependency>

----mybatis启动器不是springboot官方定的,是mybatis作者自已做的,所以与其它启动器不相同的。
2、把druid数据源配置出来
-----上面已配置了。
3、把测试用的数据库和数据表建立起来


image.png

4、把javabean建立起来
User.java

public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

  1. mybatis增删改查实现


    image.png

1、在上次课的配置基础上,创建Mapper接口(UserDao.java--->UserDao.xml)
UserDao.java


import cn.xiong.spbtjdbc.model.User;

public interface UserDao {
    //增
    public void addUser(User user);

    //删
    public void delete(int id);

    //改
    public void update(User user);

    //查
    public User get(int id);
}

UserDao.xml

<?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="cn.xiong.spbtjdbc.dao.UserDao">

    <insert id="addUser">
        insert into et_users(username,password) values (#{username},#{password})
    </insert>
   <delete id="delete">
       delete from et_user where id=#{id}
   </delete>
    <update id="update">
        update et_users u set u.username=#{username},u.password=#{password} where u.id=#{id}
    </update>
    <select id="get" resultType="cn.xiong.spbtjdbc.model.User">
       select u.id,u.username,u.password from et_users u where u.id=#{id}
    </select>
</mapper>

2、在resources下创建一个mybatis的文件夹,在这个文件夹下新建mybatis-config.xml全局配置文件
,这里只有一个配置,驼峰命名。

<?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>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

4、在SpringBoot的全局配置文件中配置Mybatis的两个配置文件的位置

spring.datasource.useGlobalDataSourceStat=true
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

#配置mybatis的配置文件的位置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

5、在启动类上加上注解 @MapperScan ({ "cn.ybzy.springbootmybatis.dao" })来告诉SpringBoot我们的映射接口的位置,
SpbtjdbcApplication


@SpringBootApplication
@MapperScan({"cn.xiong.spbtjdbc.dao"})
public class SpbtjdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpbtjdbcApplication.class, args);
    }

}

SpbtjdbcApplicationTests

@SpringBootTest
class SpbtjdbcApplicationTests {
    @Autowired(required = false)
    private UserDao userDao;

    @Test
    void contextLoads() throws SQLException {
        //mybatis增删改查测试
        //insert,update
        User user = new User();
        user.setUsername("熊少华");
        user.setPassword("123456");
        user.setPassword("cexoit");
        userDao.addUser(user);

        //

    }

}

结果:可以从数据库表中看记录的

SpringBoot整合JPA

-------JPA比mybatis方便,它是基于hibernate实现的,有大量的注解用于实现数据持久化,但在手动写sql语句上不灵活。

  1. 新建项目,导入启动器jdbc,jpa,mysql,web
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
     <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
  1. 在全局配置文件中配置jpa的相关属性
spring.datasource.username=root
spring.datasource.password=xiong
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#sprigboot 内部是使用 springdata , springdata 里的 jpa 是使用 hibernate 实现的
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
  1. 创建实体类
mport javax.persistence.*;
import java.util.Date;
@Entity
@Table(name="et_users")
public class User {

    private int id;
    private String username;
    private String password;
    private Date regDate;

    public User() {
    }


    public User(int id, String username, String password, Date regDate) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.regDate = regDate;
    }
    @Id                                                      //让id为主键
    @GeneratedValue(strategy= GenerationType.IDENTITY)   //id自增长
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    @Column(name="regdate")
    public Date getRegDate() {
        return regDate;
    }

    public void setRegDate(Date regDate) {
        this.regDate= regDate;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", regDatel=" + regDate +
                '}';
    }
}

解决@Table(name = “t_user“)出现“Cannot resolve table ‘t_user‘“问题
问题原因:数据源的表还没有被识别进模型类中。
处理:
1-工具栏 View -->Tool windows -->Database–>Data Source中填写数据库的信息(用户名root,密码xiong等等,连接到数据库)
或点最右边的--Database选项卡。
2-工具栏View-->Tool windows-->Persistence后双击“entityManagerFactory”右键选择Assign Data Sources加载刚才建的Database,
4、创建jpa的接口UserDao

public interface UserDao extends JpaRepository<User,Integer> {
    //这里相当于mybatis中的UserDao.java---->UserDao.xml我们可以什么都不用写,
    //它实现了对表的增册改查

什么都用写,基本的操作,人家都给我们写好了,用就是了!
5、测试就行了

@Controller
public class UserController {
    @Autowired
    private UserDao userDao;
    @ResponseBody                      //这里没有视图页面,该注解让save success显示在usersave.html中
    @GetMapping("/usersave.html")
    public String save(){
        User user=new User();
        user.setUsername("马尾农");
        user.setPassword("123456");
        user.setRegDate(new Date());
        userDao.save(user);
        return "save success";
    }

@ResponseBody
    @GetMapping("/get_user/{id}")
    public User getUserById(@PathVariable("id") Integer id){
        User user = userDao.findById(id).get();
        return user;
    }
}
image.png

http://localhost:8080/usersave.html
若记录增加成功,会返加如下图的内容

image.png

http://localhost:8080/get_user/1
可以获取表的1条记录找印的信息。

用SpringBoot做一个web小案例

  1. 搭建开发环境,导入相应的启动器和jar包依赖
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.xiong</groupId>
    <artifactId>spbtapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spbtapp</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  1. 配置数据库基本信息,建立起项目的包结构,并创建model类(数据源等)
spring.datasource.username=root
spring.datasource.password=xiong
spring.datasource.url=jdbc:mysql://124.222.48.147:3306/springbootdemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#sprigboot 内部是使用 springdata , springdata 里的 jpa 是使用 hibernate 实现的
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring boot jpa使用注解方式实例化javabean,放进IOC中,十分方便(比以前的spring 5方便很多很多了),会自动创建对应 javabean的表
User.java

@Entity
@Table(name="t_users")
public class User {
    private int id;
    private String username;
    private String password;
    private Date regDate;
    private Address address;

    public User() {
    }

    public User(int id, String username, String password, Date regDate, Address address) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.regDate = regDate;
        this.address = address;
    }
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getRegDate() {
        return regDate;
    }
    @Column(name="reg_date")
    public void setRegDate(Date regDate) {
        this.regDate = regDate;
    }
    @ManyToOne
    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

Address.java

@Entity
@Table(name="t_address")
public class Address {
    private int id;
    private String addressInfo;
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAddressInfo() {
        return addressInfo;
    }
    @Column(name="address_info")
    public void setAddressInfo(String addressInfo) {
        this.addressInfo = addressInfo;
    }

}

  1. 把首页做成登录页面login.html的视图做出来
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login页面</title>
</head>
<body>
<form th:action="@{/login.html}" method="post">
    用户名: <input type="text" name="username" placeholder=" 请输入用户名 "/><br/><br/>
    密 码: <input type="password" name="pasword" placeholder=" 请输入密码 "/><br/><br/>
    <input style="margin-left:60px;" type="submit" value=" 登录 "/>
</form>
</body>
</html>

启动启动类后,没错的话,会生成两张表t_users,t_address.

image.png

  1. 我们在login.html页面输入用户名和密码点击登录,请求访问到/login,然后在login方法里获取User对象,在到UserService的login方法中判断登录,来依照这个逻辑,来完成剩下的功能:
    image.png

    UserDao.java

public interface UserDao extends JpaRepository<User,Integer> {
    //不要写任何方法,增删改查都在JpaRepository中实现了。

    /**
     * 默认接口实现中没有提供根据用户名查询用记录的方法,所以自已做,但命名规则一定要遵守其getBy,find,delet,save开头的名规则
     */
    public User getByUsername(String username);
}

UserService.java


public interface UserService {
    //最基本的增删改查
    public void add(User user);
    public void delete(int id);
    public void update(User user);
    public User get(int id);
    public List<User> getAllUser();

    //判断登录的查询,根据用户名来查询有没有用户记录
    public User login(User user);

}

UserServiceImpl.java

import java.util.List;
@Service("userService")   //把UserServcieImpl实例放进IOC中
public class UserServiceImpl implements UserService{

    @Autowired                //UserDao接口实现JpaRepository注入到了IOC中。
    private UserDao userDao;

    @Override
    public void add(User user) {
        userDao.save(user);
    }

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

    @Override
    public void update(User user) {
        userDao.saveAndFlush(user);
    }

    @Override
    public User get(int id) {
        return userDao.findById(id).get();
        //findById(id)返回一个Optional对象,该对象是一个容器,里面可以只有null,所以再加一个.get()
    }

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

    @Override
    public User login(User user) {
        //判断登录用户,基本思路,登录成功返回一个登录成功的User对象,登录失败返回一个null
        //1.根据用户名查询有没有的对应的用户记录
        User u=userDao.getByUsername(user.getUsername());
        if(u==null) return null;
        //2.进一步判断密码是否对应一致
        if(!u.getPassword().equals(user.getPassword()))
            return null;
        return u;
    }
}

UserController.java
i注:两个/login.html视图,是不一样的,一个是POst请求,一个是Get请求(网页默认的进入请求)

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    //进入登录页面
    @GetMapping("/login.html")
    public String loginPage(){
        return "login";
    }

    //主页面进入方法
    @GetMapping("/main.html")
    public String main(){
        return "main";
    }
    //登录逻辑判断
   @PostMapping("/login.html")
    public String login(User user, HttpSession session, Model model){
        //接收我们的页面的用户名和密码的控制层
        //调用服务层的login方法,判断是否登录成功
        //成功,就将返回的user对象保存到session的域空间里
        //不成功,要给登录页面返回一个错误提示
        User u= userService.login(user);
        if(u!=null){
            session.setAttribute("loginUser",u);
            //跳转。。。
            return "redirect:/main.html";
        }else{
            model.addAttribute("loginErr","用户名或密码有误!");
        }
           return "login";
        }
}


login.html,main.html

-------------------------------------------------------------------------------login.html---------------------------------------------------
<body>
<br><br>
<div style="color:red;" th:text="${loginErr}" th:if="${not #strings.isEmpty(loginErr)}"></div>
<form th:action="@{/login.html}" method="post">
    用户名: <input type="text" name="username" placeholder=" 请输入用户名 "/><br/><br/>
    密 码: <input type="password" name="password" placeholder=" 请输入密码 "/><br/><br/>
    <input style="margin-left:60px;" type="submit" value=" 登录 "/>
</form>
</body>

------------------------------------------------------------------------------------main.html----------------------------------------------
<body>
   你进入了主面页!说明登录成功了。
</body>
  1. 测试
    《1》。先进入登录页,http://localhost:8080/login.html
    《2》。输入用户名,密码,若成功进入
  2. 用SpringBoot做一个web小案例配置拦截器判断登录状态
    功能:如进入了主页,不能再返回到登录页面了。也不能没有登录就进入主页了。
    首先:新建一个springmvc的拦截器:web/LoginInterptor.java,
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       HttpSession session=request.getSession();
       User user = (User) session.getAttribute("loginUser");   //UserController.java中登录成功后存入session的
       if(user!=null){
           return true;   //已登入成功,放行
       }else{
           //访问者没有登录,就不能访问主页
           request.setAttribute("loginErr","你还没有登录,请登录后再访问!");  //页面中类EL表达式@{loginErr}读取。
           request.getRequestDispatcher("/login.html").forward(request,response);
           return false;
       }

    }
}

其次:把拦截器注册进SpringBoot里:config/WebConfig.java思路:首先拦截所有内容,再排除 login.html,index.html

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/login.html","/","/index.html");
    }
}

改一下UserController.java中进入登入页方法。思路:有user进入主页,进入主页后,再输登录页时,自动回到主页上,不显示登录页

 //进入登录页面
    @GetMapping("/login.html")
    public String loginPage(HttpSession session){
        boolean flag = session.getAttribute("loginUser")==null?true:false;
        if(flag) {
            return "login";
        }else{
            return "redirect:/main.html";
        }
    }
  1. 登出功能实现
    main.html中,点击‘登出’,马上响应到控制层的类中的logout.html映射方法。下面是一个通用的主页模板
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title> 案例主页面 </title>
    <style>
        .top{
            width: 100%;
            height: 100px;
            border: 1px #cccccc solid;
            line-height: 100px;
            text-align: center;
        }
        .middle{
            width: 100%;
            height: 450px;
        }
        .middle .sidlebar{
            width: 120px;
            height: 450px;
            float: left;
            border: 1px #cccccc solid;
        }
        .middle .main{
            width: 100%;
            height: 450px;
            border: 1px #cccccc solid;
        }
        .footer{
            width: 100%;
            height: 80px;
            border: 1px #cccccc solid;
            line-height: 80px;
            text-align: center;
        }
    </style>
</head>
<body>
<div class="top">
    spring boot 开发 web 小案例的头部
</div>
<div class="middle">
    <div class="sidlebar">
        <br/>
        <a href="#" th:href="@{/main.html}">main 主页面 </a><br/><br/>
        <a href="#" th:href="@{/userlist.html}"> 用户列表 </a><br/><br/>
        <a th:href="@{/logout.html}">登出</a>
    </div>
    <div class="main">
        main 主页面
    </div>
</div>
<div class="footer">
    spring boot 开发 web 小案例的脚部
</div>
</body>
</html>
 //登出
        @GetMapping("/logout.html")
       public String logout(HttpSession session){
          session.invalidate();
          return "redirect:/login.html";
        }

公共代码抽离和复用与切换项显示用户信息

注意:后台的其他页面几乎和main页面一样 ,只是< < div class= "main"> >这个主体区域显示内容不一样,这种情况下,thymeleaf模板是给我们提供了公共代码抽离出来,大家共用的功能!,如头部,底部

  1. thymeleaf提供的抽离和共用模板代码
    ①抽离代码的写法:commons.html


    image.png

    ②调用公共代码的写法有三种


    image.png
<div th:replace="~{commons::top}"></div>

这个写法,用公共代码片段替换掉本div的标签

<div th:include="~{commons::top}"></div>

这个写法,就是把公共代码片段标签里的内容,复制过来放到本div标签里
举例
commons.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>公共代码</title>
</head>
<body>
<div class="top" th:fragment="top">
    spring boot 开发 web 小案例的头部
</div>
<div class="sidlebar" th:fragment="sidlebar">
    <br/>
    <a href="#"  th:href="@{/main.html}">main 主页面 </a><br/><br/>
    <a href="#" th:href="@{/userlist.html}"> 用户列表 </a><br/><br/>
    <a th:href="@{/logout.html}">登出</a>
</div>
<div class="footer" th:fragment="footer">
    spring boot 开发 web 小案例的脚部
</div>
</body>
</html>

main.html,userlist.html等页面调用

<div th:replace="./commons::top"></div>
<div class="middle">
    <div th:replace="commons::sidlebar"></div>
    <div class="main">
        main 主页面/用户列表等页面
    </div>
<div th:replace="./commons::footer"></div>

  1. 切换用户列表并显示用户信息
    1、userlist.html页面用UserController.java的映射方法显示所有用户信息(Model对象注入--映射对象到视图中):
@GetMapping("/userlist.html")
        public String userList(Model model){
           List<User> users = userService.getAllUser();
           model.addAttribute("users",users);
           return "userlist";
        }
<div class="main">
           搜索:<input type="text" name="searchByUsername"/><button>搜索</button><br>
           <table style="width: 600px;" border="1" cellspacing="0" cellpadding="0">
               <tr>
                   <td> 用户 id</td>
                   <td> 用户名 </td>
                   <td> 用户密码 </td>
                   <td> 用户注册时间 </td>
                   <td> 用户住址 </td>
                   <td> 操作 </td>
               </tr>
               <tr th:each="user:${users}">
                   <td>[[${user.id}]]</td>
                   <td>[[${user.username}]]</td>
                   <td>[[${user.password}]]</td>
                   <td>[[${#dates.format(user.regDate,'yyyy-MM-dd HH:mm:ss')}]]</td>
                   <td>[[${user.address.addressInfo}]]</td>
                   <td><a th:href="@{/deleteuser.html(id=${user.id})}"> 删除 </a>
                       <a th:href="@{/updateuser.html(id=${user.id})}"> 修改 </a></td>
               </tr>
           </table>
   </div>

----与主页一样的元素框架。
2、在侧边栏导航切换页面同时切换高亮
----当点击左侧某个项时,红色高亮显示。


image.png

***首先:在各个要切换的页面中写上一个样式,commons.html不用写样式:

<sytle>
.active{
            color:red;
            text-decoration: none;
        }
    </style>

其次:在要被切换的页面里写上如代码(isActive='main.html'/'userlist.html'.....")

<div th:replace="commons::top" ></div>
<div class="middle">
    <div th:replace="commons::sidlebar(isActive='main.html')"></div>
    <div class="main">
        main 主页面
    </div>
</div>
<div th:replace="commons::footer"></div>

再后:在公共页中写上th:class="${isActive=='main.html'?'active':''}"

<div class="sidlebar" th:fragment="sidlebar">
    <br/>
    <a href="#" th:class="${isActive=='main.html'?'active':''}" th:href="@{/main.html}">main 主页面 </a><br/><br/>
    <a href="#" th:class="${isActive=='userlist.html'?'active':''}" th:href="@{/userlist.html}"> 用户列表 </a><br/><br/>
    <a th:href="@{/logout.html}">登出</a>
</div>

3,模糊查询
首先:dao,service层各加两个接口,service实现类中实现查询方法

----------------------------------------------------------UserDao.java接口-------------------------------------------------------------
public interface UserDao extends JpaRepository<User,Integer> {
    //不要写任何方法,增删改查都在JpaRepository中实现了。

    /**
     * 默认接口实现中没有提供根据用户名查询用记录的方法,所以自已做,但命名规则一定要遵守其getBy,find,delet,save开头的名规则
     */
    public User getByUsername(String username);

    /**
     * dao层里建立模糊查询的接口
     *
     */
    public List<User> getByUsernameLike(String str);
}
---------------------------------------------------------UserService.java接口----------------------------------------------------------
/模糊查询
    public List<User> getByUsernameLike(String str);
------------------------------------------------------UserServiceImpl.java实现类----------------------------------------------------
@Override
    public List<User> getByUsernameLike(String str) {
        return userDao.getByUsernameLike(str);
    }

其次:控制类中,映射查询结果到视图层中。

    //模糊查询
    @PostMapping("/userSearch.html")
        public String userSearch(String searchByUsername,Model model){
         List<User> users = userService.getByUsernameLike("%"+searchByUsername+"%");
         model.addAttribute("users",users);
        return "userlist";
        }

最后:视图层中

<div class="main">
   <form th:action="@{/userSearch.html}" method="post">搜索:<input type="text" name="searchByUsername"/><input type="submit" value="模糊查询"></input></form><br>
            <table style="width: 600px;" border="1" cellspacing="0" cellpadding="0">
                <tr>
                    <td> 用户 id</td>
-----

思路:把对象UserServiceImpl对象注入到控制层中,再控制层映射对象到视中。控制层接收视图表单传送的字符串searchByUsername,再查询,注入进视图显示

添加删除用户事务配置

----用户信息有一个关联对象Address,所以我们要建立相应的dao,service层的类。
1.AddressDao.java
它执行JpaRepository接口,不用像以前那样,做AddressDaoImpl实现类,springboot jpa执久化框架自动实例化,实现了基本的增删改查方法,并结合模型类的@Entity,@Table(name="t_addresss")注解去自动创建相应数据源(mysql)的表,并放Spring IOC(控制反转容器中),用时直接@Autowired private AddressDao addressDao即可

import org.springframework.data.jpa.repository.JpaRepository;

public interface AddressDao extends JpaRepository<Address,Integer> {
}

2.AddressService.java,AddressServiceImpl.java
该实现类中没有相应的jpa接口实现,所以用到@Service("addressService")注解,去实例代理对象addressService,放到IOC中。

public interface AddressService {
    //增删改查
    public void add(Address address);

    public void delete(int id);

    public void update(Address address);

    public Address get(int id);

    public List<Address> getAllAddress();


}
-----------------------------------------------------------------------------AddressServiceImpl.java---------------------------------
@Service("addressService")
public class AddressServiceImpl implements AddressService{

    //springboot jpa自动把dao类注入到IOC中,不用写什么注解在头上
    @Autowired
    private AddressDao addressDao;

    @Override
    public void add(Address address) {
        addressDao.save(address);

    }

    @Override
    public void delete(int id) {
       addressDao.deleteById(id);
    }

    @Override
    public void update(Address address) {
        addressDao.saveAndFlush(address);
    }

    @Override
    public Address get(int id) {
        return addressDao.findById(id).get();
    }

    @Override
    public List<Address> getAllAddress() {
        return addressDao.findAll();
    }
}

3.userlist.html,adduser.html
在userlist.html做了一个‘添加’超链接,链到控制层的映射方法上,该方法映射为@GetMapping("/adduser.html"),显示用

<a th:href="@{/adduser.html}">添加用户</a><br>

adduser.html
表单提交,转到@PostMapping("/adduser.html")方法中,

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>添加用户</title>
</head>
<body>
<div class="main">
    <br><br>
    <form th:action="@{/adduser.html}" method="post">
        用户名: <input type="text" name="username" /><br><br>
        用户密码: <input type="text" name="password" /><br><br>
        选中用户地址: <select name="address.id">
        <option th:value="${address.id}"
                th:each="address:${addresses}">[[${address.addressInfo}]]</option>
    </select><br><br>
        <input type="submit" th:value=" 添加 " />
    </form>
</div>

</body>
</html>

4.UserController.java
添加用户(两个方法,一个显示用到get,真正添加用到post)

 //添加用户(两个方法,一个显示用到get,真正添加用到post),有一关联对象地址Address,所以也要建立相应的dao,service类
    @GetMapping("/adduser.html")
    public String adduser(Model model){
        //这里需要获取现在的地址信息
        List<Address> allAddress = addressService.getAllAddress();
        model.addAttribute("addresses",allAddress);
        return "adduser";
    }
    @PostMapping("/adduser.html")
    public String adduser(User user){
        user.setRegDate(new Date());
        userService.add(user);
        return "redirect:/userlist.html";
    }

localhost:8080/login.html---登录成功-----用户列表选项---添加用户

image.png

  1. 删除用户
    UserController.java
 //删除记录
    @GetMapping("/deleteuser.html")
    public String deleteuser(Integer id){
        userService.delete(id);
        return "redirect:/userlist.html";
    }

如果想点删除时有弹出框提示,则可以用script方法来做
首先:userlist.html写上一个del()的script方法。

</style>
    <script>
        function del(){
            var msg="您真的确定要删除吗?";
            if(confirm(msg)==true){
                return true;
            }else{
                return false;
            }
        }

    </script>

然后:userlist.html中的删除链接标签内写上如下代码

<td><a onclick="javascript:return del();" th:href="@{/deleteuser.html(id=${user.id})}"> 删除 </a>
  1. 修改用户
    UserController.java
//修改记录
    //打开修改页面
    @GetMapping("/updateuser.html")
    public String updateUser(Integer id,Model model){
        User user = userService.get(id);
        model.addAttribute("user",user);

        List<Address> allAddress = addressService.getAllAddress();
        model.addAttribute("addresses",allAddress);
        return "updateuser";
    }
    //真正修改
    @PostMapping("/updateuser.html")
    public String updateUser(User user){
        System.out.println("user:"+user);
        userService.update(user);
        return "redirect:/userlist.html";
    }

updateuser.html
注:有一个隐藏表单,用于传递id号,id号不显示在页面上。

<body>
<div class="main">
    <br><br>
    <form th:action="@{/updateuser.html}" method="post">
        <input type="hidden" name="id" th:value="${user.id}">
        用户名: <input type="text" name="username" th:value="${user.username}"/><br><br>
        用户密码: <input type="text" name="password" th:value="${user.password}"/><br><br>
        注册时间: <input type="text" name="regDate" th:value="${user.regDate}"/><br><br>
        选中用户地址: <select name="address.id">
        <option th:value="${address.id}" th:selected="${user.address.id == address.id}"
                th:each="address:${addresses}">[[${address.addressInfo}]]</option>
    </select><br><br>
        <input type="submit" th:value=" 修改 " />
    </form>
</div>
</body>

User.java
发现错误: Field error in object 'user' on field 'regDate'
前端传来的日期时间是 String 类型的,封装为 user 对象的时候,出问题,解决方案,我们前讲 ssh 项目课程里说过,简单一个注解:
---日期型格式化后是字符串类型, 控制层是要用到日期型,所以要转换之,在日期属性上加如下代码

@Entity
@Table(name="t_users")
public class User {
    private int id;
    private String username;
    private String password;
   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date regDate;
  1. 事务配置
    在service层的事务处理:
    在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-
    jpa依赖的时候,框 架会自动默认分别注入DataSourceTransactionManager或
    JpaTransactionManager。所以我们不需要任何额外 配置就可以用@Transactional注解进行事务的使用。
    Spring Boot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问 数据库 的Service方法上添加注解 @Transactional 便可。关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的
    是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager实例。我们可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是PlatformTransactionManager 接口的哪个实现类。
@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的
@SpringBootApplication
public class SpbtappApplication {
    @Bean
    public Object testBean(PlatformTransactionManager
                                   platformTransactionManager){
        System.out.println(">>>>>>>>>>" +
                platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpbtappApplication.class, args);
    }

}
----------------------------------------------------------------------------事务启动成功,控制台可见-------------------------------
>>>>>>>>>>org.springframework.orm.jpa.JpaTransactionManager

这些SpringBoot为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。
在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的PlatformTransactionManager 实现类。然后在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。

@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的
@SpringBootApplication
public class SpbtappApplication {
    // 其中 dataSource 框架会自动为我们注入
    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    @Bean
    public Object testBean(PlatformTransactionManager
                                   platformTransactionManager){
        System.out.println(">>>>>>>>>>" +
                platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpbtappApplication.class, args);
    }

}

SpringBoot异常处理

  1. 自定义统一的错误处理页面
    总体来讲,springboot里处理异常有五种方式,先看第一种:
    1、利用springboot的默认配置,我们自定义统一的错误处理页面:前面说了SpringBoot只是帮助我们做了整合的工作,做配一堆的默认配置工作,异常处理的配置当然会有!
    -----SpringBoot里边儿有个BasicErrorController...等一系列对象来处理异常,很人性化,是浏览器访问,出异常它会返回一张html页面显示异常信息,是其他设备访问出现异常,它会直接返回json格式的异常数据。
    返回的异常信息可以在DefaultErrorAttributes类里查询:
    timestamp:发生异常的时间戳
    status:异常状态码
    error:错误提示信息
    exception:错误对象
    message:异常消息
    errors:JSR303数据校验的异常信息
    ----当系统发生异常时,不希望访问SpringBoot默认的页面,要访问自己的错误页面怎么办?
    SpringBoot内部有关异常方法的设定:
    发生异常后
    ①有模板引擎目录下有错误页面没的? 有,它就会默认去访问,这里的规则是 /模板引擎目录/error/错误代码命名的页面,比如(/templates/error/404.html), 另外SpingBoot把错误分成了两类,客服端异常4xx,服务器端异常5xx,所以我们可也有( ( /templates/error/4xx.html和/templates/error/5xx.html )两个页面!
    ②/模板引擎目录/没有错误页面模板 ,SpringBoot回去静态资源目录static/error/下面找 错误代码命名的 页面
    ③/模板引擎目录/没有错误页面,静态资料目录下页面没有的, 显示SpringBoot自己默认的页面知道内部设计后,那就好办了根据规则自己做错误页面就是了! 看例子:
    image.png

    举例测试:localhost:8080/testerr回车会出现 5xx.html提示信息
    首先:在resources/templates/err目录下创建5xx.html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
    <title>Title</title>
</head>
<body>
自定义的5xx错误页面:错误代码:[[${status}]] ---- [[${exception}]] [[${message}]]
</body>
</html>

其次:控制层中建一个打开页面,当然了设置一个处理不了的问题如:除0.

 //测试5xx错误
    @GetMapping("/testerr")
    public String testError(){
          int i=10;
          int j=0;
          double d= i/j;             //0不能被整除,会报异常信息
          return "/userlist.html";
    }

最后:localhost:8080/login.html------localhost:8080/testerr回车

  1. 异常处理之注解@ExceptionHander和@ControllerAdvice
    ----springboot里处理异常有五种方式,看第2、3种:
    <1>.注解@ExceptionHander
    该处理注解,只处理它所在的控制类中的异常,例:UserController.java,myerror.html可以定义在项目的任何地方,无须指定什么诸如templeats目录中。
//测试5xx错误
    @GetMapping("/testerr")
    public String testError(){
          int i=10;
          int j=0;
          double d= i/j;             //0不能被整除,会报异常信息
          return "/userlist.html";
    }
//异常处理注解方法,下面方法专响应除0的异常
    @ExceptionHandler(value = {java.lang.ArithmeticException.class,})
    public ModelAndView doError(Exception ex){
        ModelAndView md = new ModelAndView();    //md该视图可以存在任何地方,不像html那样要指定地方
        md.addObject("err",ex);
        md.setViewName("myerror");
        return md;
    }

myerror.html显示异常信息, localhost:8080/login.html登录后,再进入 localhost:8080/testerr回车

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
[[${err}]]
</body>
</html>
------------------------------------------------------------除0异常-------------------------------------------------------------------------
java.lang.ArithmeticException: / by zero

<2>.注解@ControllerAdvice
----该注解可以处理所有控制类中所发生的异常错误,我们可以把该注解的方法放在一个控制类中。这样就比<1>灵活多了。
DoAllErrorController.java

@ControllerAdvice
public class DoAllErrorController {
    @ExceptionHandler(value = {java.lang.ArithmeticException.class})
    public ModelAndView testExceptionHander(Exception e){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("err",e);
        modelAndView.setViewName("myerror");
        return modelAndView;
    }
}
  1. 异常处理之SimpleMappingExceptionResolver
    在springmvc的xml配置文件中的配置:
    image.png

    但是springboot没的xml配置文件的,但有对应的配置类,我们可以建一个置类(config层中建)
    MyEerrorConfig.java
@Configuration
public class MyEerrorConfig {
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver ser = new SimpleMappingExceptionResolver();
        Properties mapper = new Properties();
        mapper.put("java.lang.ArithmeticException","myerror");    //把异常注入到myerror视图中
        ser.setExceptionMappings(mapper);
        ser.setExceptionAttribute("err");
        return ser;
    }
}

myerror.html

<body>
[[${exception}]]  或者  [[${err}]]
</body>
  1. 异常处理之接口HandlerExceptionResolver
    也是建一个配置类
    MyExceptionConfig.java
@Configuration
public class MyExceptionConfig implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response
            , Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        if(ex instanceof ArithmeticException){   //数学算术异常
            mv.setViewName("myerror");
        }
        if(ex instanceof NullPointerException){
            mv.setViewName("myerror");
        }
        mv.addObject("exception",ex);
        return mv;
    }
}

myerror.html

<body>
[[${exception}]]  
</body>

SpringBoot中使用表单数据有效性检验

-----在SpringBoot里使用服务器端的数据校验和单独使用SpringMVC中的数据校验采用的是一样的技术JSR303这个规范的实现产品Hibernate-Validate,回顾一下在springMVC里讲过的数据校验内容的基础上来看SpringBoot的具体操作:
一、JSR-303简介
---JSR-303只是一个规范,而Spring也没有对这一规范进行实现,那么当我们在SpringMVC中使用JSR-303的时候就需要我们提供一个对JSR-303规范的实现,Hibernate Validator就是实现了这一规范的具体框架。
JSR-303的校验是基于注解的,它内部已经定义好了一系列的限制注解,我们只需要把这些
注解标记在需要验证的 实体类的属性上或是其对应的get方法上。

二、JSR 303 基本的校验规则
空检查
  @Null 验证对象是否为null
  @NotNull 验证对象是否不为null, 无法查检长度为0的字符串
  @NotBlank 检查约束字符串是不是Null还有被Trim后的长度是否大于0,只对字符串,且会去掉前后空格.
  @NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
  @AssertTrue 验证 Boolean 对象是否为 true
  @AssertFalse 验证 Boolean 对象是否为 false
长度检查
  @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
  @Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
   @Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
  @Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
  @Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正
           则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
  建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
  @Min 验证 Number 和 String 对象是否大等于指定的值
  @Max 验证 Number 和 String 对象是否小等于指定的值
  @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
  @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
  @Digits 验证 Number 和 String 的构成是否合法
  @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
  @Range(min=, max=) 被指定的元素必须在合适的范围内
  @Range(min=10000,max=50000,message=”range.bean.wage”)
  @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
  @CreditCardNumber信用卡验证
  @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
  @ScriptAssert(lang= ,script=, alias=)
  @URL(protocol=,host=, port=,regexp=, flags=)

二、具体操作步骤
(1)SpringBoot已经将相关的jar包整合进来了,不用我们自己导入jar包了,不知什么原因,我的springboot 2.7.6没有导入进来,所以我要手工导入

<dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.11.Final</version>
        </dependency>

(2)在JavaBean的属性上加验证注解。

@Entity
@Table(name = "t_users")
public class User {
   private int id;
  @NotEmpty
  private String username;
  @NotEmpty
  private String pasword;

(3)在Controller的处理方法上的参数前,加@Valid注解,增加出错后,错误信息放置的对象参数Errors/BindingResult errorrs。


image.png

(5)前面把错误信息带回了添加页面,显示出来,前面讲异常处理的时候,已经说了回显
的异常信息会放到errors变量里注入视图

<form th:action="@{/adduser.html}" method="post">
用户名: <input type="text" name="username" /><span style="color: red;" th:errors="${user.username}">ss</span><br><br>
用户密码: <input type="text" name="pasword" /><span style="color: red;" th:errors="${user.password}"></span><br><br>
选中用户地址: <select name="address.id">
<option th:value="${address.id}" th:each="address:${addresses}">[[${address.addressInfo}]]</option>
 </select><br><br>
<input type="submit" th:value=" 添加 " />
</form>
image.png

5)有些系统默认的错误信息不好用,我们一般都自己写错误提示信息
①用注解,在注解中带message参数


image.png

有关Servlet容器的配置

SpringBoot(2.1.0时)默认使用嵌入式Servlet容器,最新版本的SpringBoot默认是选的是Tomcat9.0.12,(SpringBoot默认支持三种Servlet容器:tomcat,jetty,undertow)

1、既然是嵌入的,就没的server.xml配置文件给我们做定制化,所以嵌入的Servlet容器,SpringBoot提供了对应方式来配置:
在SpringBoot的全局配置文件中配置(所有能配置的项目都可以在ServerProperties类里查询-----打一个类文件按ctrl+shift+n---输入ServerProperties)
①用server.xxxx的属性来配置有关Servlet容器的通用配置
②用server.tomcat.xxxx来专门配置tomcat有关的配置
③其他两个Servlet容器以此类推

2、怎么切换SpringBoot里边儿的三个Servlet容器:
一,tomcat:开源,也是比较好用的一个Servlet容器,很多人用,不管是用来做测试还是生产环境,SpringBoot默认
二,jetty:小Serlvet容器,适合做长链接,也就是链接上就不断,长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,很多人开发测试的时候很喜欢用,因为它启动比tomcat要快很多
三,undertow:是一个非阻塞的Servlet容器,但是它不支持JSP
①排除掉默认的tomcat依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
    <artifactId>spring-boot-starter-tomcat</artifactId>
     <groupId>org.springframework.boot</groupId>
   </exclusion>
  </exclusions>
</dependency>

②引入jetty或undertow的启动模块

<dependency>
   <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jetty/undertow</artifactId>
</dependency>

3、在SpringBoot的基础上使用外部的tomcat
①创意一个war工程,就不能用jar了; 注意:一定要选war,否则不会成功!再
‘文件’---Project Settings

image.png

注:上图有一个错误处,webapp\WEB-INF中是有''隔开的。
②生成webapp目录和web.xml配置文件
③添加外部tomcat服务器
注:添加外部tomcat之前,先配好tomcat服务器位置。
image.png

image.png

image.png

image.png
image.png

配置Deployment里artifacts选项:
④配置全局变量
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
最后可以测试一下了
testController.java

@Controller
public class testController {
    @GetMapping({"/","/index.html"})
    public String index(){
        return "index";
    }
}

WEB-INF/jsp/index.jsp

<%@ page import="java.util.Date" %><%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2023/2/22/022
  Time: 10:43
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
jsp页面。  <%=new Date()%>
</body>
</html>

localhost:8080/ 回车 或 localhost:8080/index.html 回车

jsp页面。 Wed Feb 22 11:28:34 CST 2023

SpringBoot的国际化

国际化(internationalization)是设计容易适应不同区域要求的产品的一种方式。
它要求从产品中抽离所有地域语言元素。换言之,应用程序的功能和代码设计考虑了在不同地区运行的需要。开发这样的程序的过程,就称为国际化。
那么当我们使用Spring Boot如何进行国际化呢?我们用一个例子来演示操作的步骤
1、我们写一个thymeleaf模板:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>hello spring boot</title>
</head>
<body>
<p> 欢迎你登录到 ybzy.ke.qq.com 我的课程分享网站 </p>
</body>
</html>

2、写出对应的controller类:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "/hello";
}
}

我们观察hello.html里面的信息直接就是中文显示,但是我们现在的需求是当访问语言是zh的时候显示为中文,当语言为en的时候显示为英文,那么怎么操作呢?
******首先我们先定义国际化资源文件,spring boot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在resources/这个根路径下定义国际化配置文件,文件 名称必须以messages开头 ,注意:配置文件的名字和存放的位置SpringBoot的内部代码里默认的就是messages和classpath,当然我们可以在配置文件中配置spring.messages.basename的值来改变这两个东西。
我们先用默认的配置定义如下几个文件:
1.messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。
2.messages_zh_CN.properties(中文)
3.messages_en_US.properties(英文)
分别写上内容:
1.welcome = 欢迎你登录到 ybzy.ke.qq.com 我的课程分析网站(default)
2.welcome = \u6b22\u8fce\u4f60\u767b\u5f55\u5230 \u963f\u91cc\u5df4\u5df4\u7f51\u7ad9\uff08\u4e2d\u6587\uff09
3.welcome = welcome to login to ybzy.ke.qq.com website(English)
配置信息完毕后,那么在前端展示怎么修改呢,修改hello.html文件,使用#{key}的方式进行使用
messages中的字段信息:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>hello spring boot</title>
</head>
<body>
<p><label th:text= "#{welcome}"></label></p>
</body>
</html>

修改火狐浏览器的国际化信息,看测试效果:
about:config
在搜索框输入accept,然后找到intl.accept_languages修改对应的值,我这里原本是:
zh-CN, zh, zh-TW, zh-HK, en-US, en
为了看到效果,修改为:
en-us, en
通过三个问题,简单说下原理:
第一个问题,为什么命名必须是messages开头,需要看一个源码文件:MessageSourceAutoConfiguration
如果我们没有在application.properties中配置spring.messages.basename属性,那么使用默认的messages,好了这个问题就这么简单解决了。
第二个问题:为什么我看到的是中文(或者英文)呢?
为了让web应用程序支持国际化,必须识别每个用户的首选区域,并根据这个区域显示内容。在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须是实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口创建自己的区域解析器。如果没有做特殊的处理的话,Spring 采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的头部信息accept-language来解析区域。这个头部是由用户的web浏览器底层根据底层操作系统的区域设置进行设定的。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。
第三个问题:怎么根据我们的需要切换语言环境呢?
我们可以自定义我们的区域解析器LocaleResolver,然后将它注册到spring的ioc容器中。

自定义启动器

---------在springboot的学习过程中,我们发现它的最核心的东西,就是帮助我们做了很多配置,这点就是通过starter这个启动器来实现的,这个starter不仅能帮助我们把某一个应用场景需要的jar包一起导入进来,关键还是针对这些jar的主要配置,都做好了,我们只需要简单地配置或修改一点点少量的属性,就可以完成配置工作了!
--------spring boot虽然给我们提供了很多这样的启动器starter,但是在实际的开发中,这些官方的启动器显然是不够用的,所以,需要我们能够自定义出我们自己的starter,这里我们用一个简单的案例来演示一下怎么创建自己的starter,我们要实现一个这样的启动器,这个启动器能帮我们配置一个Service,这个Service里有一个方法是hello,调用这个方法,它可以从全局配置文件中读取一个前缀信息和传入的信息组合起来然后返回,再用注解注入到IOC 中,写相应的关联类,再把两个模块install打包到本地库中,再建一个springboot项目,导包之,启动启动类后,就能把HelloService注入到IOC中,后@Autowired自动生成对象应用之接下来我们看具体的操作步骤:
1、第一步骤:我们来做要给空的启动器的Module
通过空项目来做:

image.png

建好空项目后,再添加模块,第一个模块选maven功程
image.png

image.png

image.png

第二步骤:创建自动配置的Module,第一个模块选spring initializer功程
image.png

image.png

image.png

第三步骤:清理到两个module中的多余的东西,加上依赖,启动器依赖自动配置
自动启动器模块的中的如下图三个文件都可删除,pom.xml中的插件和test启动依整都可删除。
image.png

第一个模块中pom.xml中加上自动配置启动模块中的依赖

<?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>xiong.spring.boot.starter</groupId>
    <artifactId>xiong-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <dependencies>
        <dependency>
            <groupId>xiong.spring.starterdemo</groupId>
            <artifactId>xiong-spring-boot-starter-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

第四步骤:编写自动配置模块
首选 :写一个HelloService.java


image.png
public class HelloService {

    private HelloProperties helloProperties;
    public String hello(String str){
        //return "返回一个前缀,使用starter的时候,项目的全局配置文件中配置的"+"--"+str;
        return helloProperties.getPrefix()+"--"+str;
    }

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }
}

其次:编写一个HelloProperties.java

@ConfigurationProperties(prefix="hello")
@Component
public class HelloProperties {
    private String prefix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}

然后:编写一个HelloServiceAutoconfiguration.java
该类,为HelloService与HelloProperties建立串联关系

/**
 * 该类,为HelloService与HelloProperties建立串联关系
 */
 @Configuration
 @ConditionalOnWebApplication   //只有在web应用中引入自定义的启动器的时候,这个启动配置类才会生效(引用者是另一个项目)
 @EnableConfigurationProperties(HelloProperties.class)   //这个注解可以把此类与HelloProperties类建立关联,并把Properties类注入IOC中
public class HelloServiceAutoconfiguration {

     @Autowired
    private HelloProperties helloProperties;
     @Bean                  //此处是,把HelloService对象注入到spring IOC容器中
     public HelloService helloService(){
         HelloService helloService = new HelloService();
         helloService.setHelloProperties(helloProperties);      //非常关键,把HelloService与HelloProperties串联了
         return helloService;
     }
}

再然后:建如下图所示的文件


image.png
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.ybzy.starterdemo.HelloServiceAutoconfiguration

文件内容是从以下两图示范的地方复制过来的。

image.png

image.png

最后:打包到本地仓库或私服中,一般最先会到本地中,如果配了私服,也会同时打包到私服中。
打包顺序:先自动启动配置module,再启动配置module,从最右边---maven项---Lifecyle---install
第五步骤:测试
首先:建一个springboot项目,一定要有web启动器。
其次:pom.xml中,导入启动配置module包

<dependencies>
        <dependency>
           <groupId>ybzy.spring.boot.starter</groupId>
          <artifactId>ybzy-spring-boot-starter</artifactId>
         <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

再次:全局配置文件中application.properties

hello.prefix=helloxiongshaowen

最后:建一个控制类IndexController.java,再localhost:8080/回车
HelloService.java对象已被导入的自定义的启动器配注入到了IOC中

@Controller
public class IndexController {
    @Autowired
    private HelloService helloService;
    @ResponseBody    //因为没有建视图橨板页,所以它可以把返回的内容以json格式显示到index中
    @GetMapping({"/","/index"})
    public String index(){
        return helloService.hello("自已定义的启动器starter测试");
    }
}

本示例的两候个项目,如下图所示,在网盘中可以看到,其中两个是模块
本示例的本个项目,如下图所示,在网盘中可以看到

image.png

springboot中遇到的问题

  1. java: 程序包javax.validation不存在
    之前,有位同学反馈说,在运行newbee-mall-api项目时遇到了下面这个问题,无法正常编译项目,错误截图如下:
    看了一下应该是@NotEmpty、@Valid这几个验证注解引起的,因为这几个注解都是定义在javax.validation包中的。再了解下去发现这位同学升级了Spring Boot的版本号。
    Spring Boot 2.2.* 版本里是有这些代码的,在这位同学升级的Spring Boot 2.3版本中是没有对应代码的,需要自己再把validation相关的包引入进来,代码如下:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
      
        </dependency>
  1. STS pom.xml Unknown error
    这个问题的原因是2.2.0.RELEASE中==maven插件升级到了3.1.1,有些IDE插件不兼容。解决这个问题只需要在pom.xml 文件的properties中加入maven jar插件的版本号。
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
    </properties>
  1. STS中出现如下图错误:
    image.png

    由于最近在弄elastic-job ,在下载源码后会报一些错误,变量log 找不到,没有getter/setter 方法之类的。
    之前没有接触过lombok ,这次看了些帖子,也只是暂时解决了这样的问题。
    1.前往官网下载:https://projectlombok.org/download lombok.jar
    -----本人已经下载放在网盘STS_svn目录中。
    2.将lombok.jar放在eclipse的安装目录下。
    3.在eclipse的安装目录下。shift+右键 ->在此处打开shell窗口,执行命令

    5.点击Specify location.. 选择eclipse 的安装目录。然后点击install/update
    6.安装成功,退出,在eclipse.ini(sts.ini)中会看到新增lombok.jar一行
    7.有时后可能这样自动安装的路径导致eclipse无法启动,可以将eclipse.ini文件自动追加的一行修改为下所示
-Xbootclasspath/a:lombok.jar 
-javaagent:lombok.jar

8.启动eclipse ,clean。错误消失。

  1. springboot路径问题
    一。获取web(servlet)获取文件的真实(服务器)路径 方法:,获取target类路径的目录
public String upload(MultipartFile uploadFile, HttpServletRequest req){
        String realPath=req.getSession().getServletContext().getRealPath("/uploadFile/");
            //getRealPath:  C:\Users\Administrator\AppData\Local\Temp\tomcat-docbase.8080.596262946748719422/uploadFile/
        //String realPath = ClassUtils.getDefaultClassLoader().getResource("").getPath();
            //getRealPath:  /C:/Users/Administrator/Desktop/idea/fileupload/target/classes/
        System.out.println("getRealPath:  "+realPath);

二。SpringBoot 读取classPath 路径下文件几种方式及
spring boot默认加载文件的路径:

/META-INF/resources/
/resources/
/static/
/public/
Classpath含义
存放各种资源配置文件 eg.init.properties log4j.properties struts.xml
存放模板文件 eg.actionerror.ftl
存放class文件对应的是项目开发时的src目录编译文件
一般java项目中 classpath存在与 WEB-INFO/目录。
当我们需要某个class时,系统会自动在CLASSPATH里面搜索,如果是jar,就自动从jar里面查找,如果是普通的目录,则在目录下面按照package进行查找。
但与PATH不同的是,默认的CLASSPATH是不包含当前目录的,这也是CLASSPATH里面要包含一个点的道理了。
Tomcat下的Web应用有两个预置的classpath : WEB-INF/classes 和WEB-INF/lib启动项目,项目就会加载这两个目录里的数据。这是war包的规范.要改变预置的classpath比较麻烦,在Tomcat的配置文件里没有发现类似的配置,要实现自己的classloader才能达到目的。
一个在tomcat中运行的web应用.它的classpath都包括如下目录:
我知道的有:

%tomcat%/lib
web-inf/lib
web-inf/classes
       //方式1: 流的方式
        ClassPathResource classPathResource = new ClassPathResource(pathfileName);
        InputStream inputStream = classPathResource.getInputStream();
        //方式2: 流的方式
        String s = this.getClass().getResource("/") + pathfileName;
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(pathfileName);
        //方式3: 流的方式
        InputStream resourceAsStream1 = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathfileName);
        //方式4: 文件的方式
        File file = org.springframework.util.ResourceUtils.getFile("classpath:"+pathfileName);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,578评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,701评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,691评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,974评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,694评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,026评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,015评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,193评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,719评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,442评论 3 360
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,668评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,151评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,846评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,255评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,592评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,394评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,635评论 2 380

推荐阅读更多精彩内容