SpringBoot


SpringBoot:用来简化Spring应用的初始搭建以及开发过程。
使开发人员不再需要定义样板式的配置。

  1. 简化Spring配置
  2. 简化Maven配置
  3. 内嵌Tomcat
  4. 集成其他框架:Redis,zookeeper,Cache,web,mybatis
  5. 进行SpringCloud的开发简化,是SpringCloud开发的基础

SpringCloud:完成RPC(服务注册,发现,调用,负载均衡,路由,网关…)

注解方式配置:

Spring配置文件

//Spring配置文件,作用类似于applicationContext,属性与XML中是对应的
@Configuration
@ComponentScan(basePackages = "com.i",useDefaultFilters = true,//扫描
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
public class SpringConfig {

}

SpringMVC

@Configuration
@ComponentScan(basePackages = "com.i",useDefaultFilters = false,//扫描
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class),
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Configuration.class)})
public class SpringMVCConfig extends WebMvcConfigurationSupport {

    //配置静态资源放行
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
    }

    //编码格式,如用jackson-databind则不用配,fastjson则需要
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd");
        config.setCharset(Charset.forName("UTF-8"));
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converters.add(converter);
    }

    //配置视图解析器
    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/jsp/", ".jsp");
    }
   /* //针对某些URL请求只需要跳转到另外一个URL
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/01").setViewName("01");
    }*/
}

注解配置json
jackson-databind
SpringBoot中一般用这个

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")//格式,时区
private Date birthday;

<dependency>
   	<groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>
@Configuration
@ComponentScan(basePackages = "com.i",useDefaultFilters = false,//扫描
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
public class SpringMVCConfig extends WebMvcConfigurationSupport {

    //编码格式,如用jackson-databind则不用配,fastjson则需要
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd");
        config.setCharset(Charset.forName("UTF-8"));
        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converters.add(converter);
    }

基础配置

项目创建的三种方式

1.在线创建 https://start.spring.io/

在这里插入图片描述
2.开发工具创建

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
一直点下一步就ok啦

3.通过改造普通Maven创建
pom.xml中加入

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

就ok啦

依赖导入失败

依赖如果失败 选择Maven–>Reimport 重新导入,还失败的话,
去Maven本地仓库搜索.lastup,这是下载失败产生的临时文件,全部删除

项目结构

在这里插入图片描述

resource块:
static:存放静态资源(js,css,image)
templates:模板,类似于jsp的技术(freemark),实现MVC视图,提供数据的展示,用户UI,jsp得放在webapp下
application.properties:SpringBoot的配置文件(配置项目的上下文,Tomcat的端口号,注册中心的地址# )

starter启动器

SpringBoot提供了简化企业级开发绝多数场景的starter pom,只要使用了应用场景所需要的starter pom ,
相关的技术配置将会消除,就可以得到SpringBoot提供的自动化配置的Bean

名称描述
spring-boot-starter-thymeleaf使MVC Web applications 支持Thymeleaf
spring-boot-starter-data-couchbase使用Couchbase 文件存储数据库、Spring Data Couchbase
spring-boot-starter-artemis为JMS messaging使用Apache Artemis
spring-boot-starter-web-services使用Spring Web Services
spring-boot-starter-mailJava Mail、Spring email为邮件发送工具
spring-boot-starter-data-redis通过Spring Data Redis 、Jedis client使用Redis键值存储数据库
spring-boot-starter-web构建Web,包含RESTful风格框架SpringMVC和默认的嵌入式容器Tomcat
spring-boot-starter-activemq为JMS使用Apache ActiveMQ
spring-boot-starter-data-elasticsearch使用Elasticsearch、analytics engine、Spring Data Elasticsearch
spring-boot-starter-integration使用Spring Integration
spring-boot-starter-test测试 Spring Boot applications包含JUnit、 Hamcrest、Mockito
spring-boot-starter-jdbc通过 Tomcat JDBC 连接池使用JDBC
spring-boot-starter-mobile通过Spring Mobile构建Web应用
spring-boot-starter-validation通过Hibernate Validator使用 Java Bean Validation
spring-boot-starter-hateoas使用Spring MVC、Spring HATEOAS构建 hypermedia-based RESTful Web 应用
spring-boot-starter-jersey通过 JAX-RS、Jersey构建 RESTful web applications;spring-boot-starter-web的另一替代方案
spring-boot-starter-data-neo4jspring-boot-starter-data-neo4j
spring-boot-starter-websocket使用Spring WebSocket构建 WebSocket 应用
spring-boot-starter-aop通过Spring AOP、AspectJ面向切面编程
spring-boot-starter-amqp使用Spring AMQP、Rabbit MQ
spring-boot-starter-data-cassandra使用Cassandra分布式数据库、Spring Data Cassandra
spring-boot-starter-social-facebook使用 Spring Social Facebook
spring-boot-starter-jta-atomikos为 JTA 使用 Atomikos
spring-boot-starter-security使用 Spring Security
spring-boot-starter-mustache使MVC Web applications 支持Mustache
spring-boot-starter-data-jpa通过 Hibernate 使用 Spring Data JPA(Spring-data-jpa依赖于Hibernate)
spring-boot-starterCore starter,包括 自动配置支持、 logging and YAML
spring-boot-starter-groovy-templates使MVC Web applications 支持Groovy Templates
spring-boot-starter-freemarker使MVC Web applications 支持 FreeMarker
spring-boot-starter-batch使用Spring Batch
spring-boot-starter-social-linkedin使用Spring Social LinkedIn
spring-boot-starter-cache使用 Spring caching 支持
spring-boot-starter-data-solr通过 Spring Data Solr 使用 Apache Solr
spring-boot-starter-data-mongodb使用 MongoDB 文件存储数据库、Spring Data MongoDB
spring-boot-starter-jooq使用JOOQ链接SQL数据库;spring-boot-starter-data-jpa、spring-boot-starter-jdbc的另一替代方案
spring-boot-starter-jta-narayanaSpring Boot Narayana JTA Starter
spring-boot-starter-cloud-connectors用连接简化的 Spring Cloud 连接器进行云服务就像Cloud Foundry、Heroku那样
spring-boot-starter-jta-bitronix为JTA transactions 使用 Bitronix
spring-boot-starter-social-twitter使用 Spring Social Twitter
spring-boot-starter-data-rest使用Spring Data REST 以 REST 方式暴露 Spring Data repositories
spring-boot-starter-actuator使用Spring Boot Actuator 的 production-ready 功能来帮助你监视和管理应用
spring-boot-starter-undertow使用 Undertow 作为嵌入式服务容器;spring-boot-starter-tomcat的另一替代方案
spring-boot-starter-jetty使用 Jetty 作为嵌入式服务容器;spring-boot-starter-tomcat的另一替代方案
spring-boot-starter-logging为 logging 使用Logback.默认 logging starter
spring-boot-starter-tomcat使用 Tomcat 作为嵌入式服务容器;作为默认嵌入式服务容器被spring-boot-starter-web使用
spring-boot-starter-log4j2使用Log4j2记录日志;spring-boot-starter-logging的另一替代方案

容器配置

  SpringBoot中默认嵌入了Tomcat容器。还支持Jetty和Undertown
  默认的配置文件,有四个位置,顺序如下,优先级以此降低

  1. config/application.properties
  2. application.properties
  3. classpath:/config/application.properties
  4. classpath:/application.properties

  除了这四个位置外,开发人员还可以自定义配置文件位置后,启动通过spring.config.location指定文件位置。
  也可以自定义配置文件名,自定义配置文件名 启动时通过spring.config.name来指定文件名。

在这里插入图片描述
在这里插入图片描述

属性注入

Spring就有属性注入,既可以通过XML文件实现,也可以通过JAVA配置来实现

@Configuration是随容器启动开始加载的,始终存在的单例模式。
@Component是使用一次即实例化一次,多例

user.id=1
user.name=阿猫
user.address=深圳

实体类
@PropertySource(“classpath:XX.properties”): 相当于XML配置中的 place-holder 引入配置文件
@Value("${xx.xx}"): 不通过配置文件的注入属性的情况使用。
           通过@Value将外部配置文件的值动态注入到Bean中

@Component
@PropertySource("classpath:user.properties") //相当于XML配置中的 place-holder 引入配置文件
public class User {
    @Value("${user.id}")	//通过@Value将外部配置文件的值动态注入到Bean中
    private Integer id;

    @Value("${user.name}")
    private String name;

    @Value("${user.address}")
    private String address;

SpringBoot中对属性注入做了改进,引入了类型安全的属性注入

@Component
@PropertySource("classpath:user.properties") //相当于XML配置中的 place-holder 引入配置文件
@ConfigurationProperties(prefix = "user")   将yml文件中的user为前缀的数据,赋值给User类的属性
public class User {

    private Integer id;
    private String name;
    private String address;

application.properties文件的yaml配置形式

  SpringBoot中的配置:支持两种格式,propertiesyaml
  yaml配置在JAVA中的解析方案时snake-yaml。这个工具,在SpringBoot的基础依赖中默认就集成进来了。

  Yml:在项目启动时会自动加载,将里面配置的数据,封装到系统或者自定义类的属性中

.yml = .yaml     .yml是YAML文件格式的文件扩展名

1.创建项目
2.配置application.yml文件

# yml配置格式,主要体现出数据的包含关系,是一种结构化配置
# 项目名称
spring:
  application:
    name: springBoot-demo01

server:
  servlet:
    context-path: /
  port: 8080

# yml配置文件中的内容,在项目启动时加载,植被封装到某些类的属性
# 通过yml文件,给 User类的属性赋值
user:
  id: 1
  username: 阿猫
  address: 深圳

注意:如果自定义类名为User类,则属性不能写 name 否则会把环境变量整合进来,name显示成本地主机名

3.创建实体类

@Configuration  //将User对象放入Spring容器
@ConfigurationProperties(prefix = "user")   //将文件中的user为前缀的数据,赋值给User类的属性
public class User {
    private Integer id;
    private String name;
    private String address;

    public User() {
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

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

    public User(Integer id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
}

在这里插入图片描述显示这个则需要导入jar包

	<!-- 自定义的元数据依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>

4.创建Controller

@RestController //Controller纳入Spring容器,完成异步响应
public class UserController {

    @Autowired
    private User user;

    @RequestMapping("User")
    public String User(){
        return user.getId() + "-----" + user.getName() + "-----" + user.getAddress();
    }
}

测试
在这里插入图片描述
数组类型:

@Component
@ConfigurationProperties(prefix = "redis")
public class RedisCluster {
    private String ip;
    private List<Integer> ports;
	
	get().. set().. 有参,无参()..

yaml文件中:

redis:
  ip: 192.1678.230.172
  ports:
    - 6379
    - 6380
    - 6381

测试
在这里插入图片描述

对象类型:

@Component
@ConfigurationProperties(prefix = "redis2")
public class RedisCluster2 {
    private List<Redis> redisList;

	get().. set().. 有参,无参()..
public class Redis {
    private String ip;
    private Integer port;
    get().. set().. 有参,无参()..

yaml中配置:

redis2:
  redisList:
    - ip: 192.168.230.172
      port: 6379
    - ip: 192.168.230.172
      port: 6380
    - ip: 192.168.230.172
      port: 6381

测试
在这里插入图片描述

JSON处理

SpringBoot中默认集成了jackson
如果需要自定义HttpMessageConverter,配置方式和fastjson一致。
如果需要使用gsonfastjson,先从spring-boot-starter-web 中移除jackson,然后引入gsonfastjson即可。
gson引入后不需要额外配置,fastjson需要手动配置HttpMessageConverter

单个配置

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
    private Date birthday;

全局配置

//如果想集成其他的json框架需要继承WebMvcConfigurer,并重写configureMessageConverters
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    //第一种方式,重写configureMessageConverters,并将Converter设置到系统中
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converter.setObjectMapper(mapper);
        converters.add(0,converter);
    }

    // 第二种方法:注入beanHttpMessageConverters
    @Bean
    public HttpMessageConverters faMessageConverters(){
        return new HttpMessageConverters(new MappingJackson2HttpMessageConverter());
    }
}

gsonfastjson:先从spring-boot-starter-web 中移除jackson,然后引入gsonfastjson即可

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

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

日志

日志

SpringBoot支持Java Util Logging、Log4J、Log4J2和Logback作为日志框架,无论使用哪种日志框架,SpringBoot已为当前使用的日志框架的控制台输出及文件输出做好了配置,默认情况下,SpringBoot使用Logback作为日志框架
application.properties下配置日志级别:

logging.file=c:/tools/log.log
logging.level.org.springframework.web=DEBUG

在这里插入图片描述

在这里插入图片描述

整合 Web 开发

静态资源处理

  静态资源一共有五个默认位置,按照如下顺序优先级依次降低:

  1. classpath:/META-INF/ressources
  2. classpath:/resources
  3. classpath:/static
  4. classpath:/public
  5. / (即webapp目录)
    在这里插入图片描述

自定义静态资源处理

第一种方式:
application.properties文件中配置

# 指定访问URl前缀
spring.mvc.static-path-pattern=/static/**
# 指定访问路径资源
spring.resources.static-locations=classpath:/i/

第二种:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("static/**").addResourceLocations("classpath:/i/");
    }
}

异常处理

1、自定义错误页面
SpringBoot默认的提供了一套处理异常的机制,一旦程序中出现了异常,SpringBoot会向/error的URL发送请求。在SpringBoot中提供了一个BasicExecptionController来处理/error请求,然后跳转到默认显示的页面展示
异常页面查找顺序:先精确后模糊,先动态后静态

统一跳转

如果需要将 所有的异常 统一跳转 到自定义的错误页面, 需 要 在src/main/resources/templates 目录下创建 error.html 页面。也可以在static/error下创建静态的跳转页面
注意:名称必须叫 error
在这里插入图片描述

静态跳转

静态跳转页面是固定的
如需要将某些单独的异常 跳转到 自定义 的 错误页面,需要在src/main/resources/static目录下创建error文件夹,再创建需要跳转的页面
如:创建404.html,出现404错误时会跳转到该页面,500.html同理,
还可以创建4xx.html,5xx.html…,4开头的错误会跳转到4xx.html
在这里插入图片描述
动态跳转
可以获取异常数据并显示
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table>
        <tr>
            <td>error</td>
            <td>${error}</td>
        </tr>
        <tr>
            <td>status</td>
            <td>${status}</td>
        </tr>
        <tr>
            <td>message</td>
            <td>${message}</td>
        </tr>
        <tr>
            <td>path</td>
            <td>${path}</td>
        </tr>
        <tr>
            <td>timestamp</td>
            <td>${timestamp?string('yyyy-MM-dd HH:mm:ss')}</td>
        </tr>
    </table>
</body>
</html>

在这里插入图片描述

2、SimpleMappingExceptionResolver 处理异常
通过SimpleMappingExceptionResolver将具体的异常和错误页面指定对应关系,
这样就不用每个异常都单独写一个方法了。

@Configuration
public class MyExceptionResolver {

    @Bean
    public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        // 参数一:异常的类型,注意必须是异常类型的全名
        // 参数二:视图名称
        mappings.put("java.lang.NullPointerException","error1.html");
        mappings.put("java.lang.ArithmeticException", "error2.html");
        //设置异常与视图映射信息
        resolver.setExceptionMappings(mappings);
        return resolver;
    }
}

3、@ExceptionHandle 注解处理异常

@Controller
public class UserController {

    @RequestMapping("/demo1")
    public String demo1(){
        String str = null;
        str.toString();
        return "/user";
    }
    //出现空指针,由本方法处理
    @ExceptionHandler(value=NullPointerException.class)
    public ModelAndView handleNullPointerException(){
        System.out.println("空指针异常...");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("/error");
        mv.addObject("msg","空指针异常...");
        return mv;
    }
}

4、@ControllerAdvice+@ExceptionHandler 注解处理异常
上个方式中,异常处理的代码和业务代码放在一个类中了,这种方式耦合性太强了,最好是将业务和异常处理的代码分离开,这时我们可以定义一个专门的异常处理类,通过注解@ControllerAdvice来实现。具体如下:

@ControllerAdvice:即把@ControllerAdvice注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。非常简单,不过只有当使用@ExceptionHandler最有用,另外两个用处不大。

异常处理类

@ControllerAdvice
public class GlobalException {

    //出现空指针,由本方法处理
    @ExceptionHandler(value=NullPointerException.class)
    public ModelAndView handleNullPointerException(){
        System.out.println("空指针异常...");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("/abc.html");
        mv.addObject("msg","空指针异常...");
        return mv;
    }
}

controller

@RestController
public class HelloController {

    @GetMapping("/demo1")
    public String demo1(){
        String str = null;
        str.toString();
        return "/user";
    }
}

5、自定义 HandlerExceptionResolver 类处理异常

@Component     //注意该类需要交给Spring容器管理
public class MyExceptionResolver implements HandlerExceptionResolver{
    @Override
    public ModelAndView resolveException(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2,
            Exception ex) {
        System.out.println(ex.getClass().getName());
        ModelAndView mv = new ModelAndView();
        if(ex instanceof NullPointerException){
            System.out.println("空指针...");
            mv.addObject("msg","空指针异常");
        }else if(ex instanceof ArithmeticException){
            System.out.println("算术异常...");
            mv.addObject("msg","算术异常");
        }else{
            System.out.println("其他异常...");
            mv.addObject("msg","其他异常");
        }
        mv.setViewName("/error");
        return mv;
    }
}

拦截器

  1. 创建拦截器
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("-----preHandle 执行了-----");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("-----ModelAndView之前执行-----");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("-----执行完Handler到返回客户端之前执行-----");
    }
}
//完成拦截器的注册,相当于xml文件
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
    //注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor()).addPathPatterns("/**");
    }

    @Bean
    MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }
}

测试
在这里插入图片描述

过滤器

方式一

1.创建过滤器

创建Filter,并且通过@WebFilter注解配置过滤信息,具体如下:

@WebFilter(urlPatterns = "/hello")  //将类声明为过滤器
public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("------------过滤器被创建");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("First过滤器:firstServlet 执行之前");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("First过滤器:firstServlet 执行之后");
    }

    @Override
    public void destroy() {
        System.out.println("---------过滤器被创建");
    }
}

2.创建启动类
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。

@SpringBootApplication
@ServletComponentScan() //在SpringBoot 启动时会扫描@WebServlet,并将该类实例化
public class FilterApplication {

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

}

3.真实方法

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        System.out.println("hello方法执行了");
        return "hello";
    }

}

测试
在这里插入图片描述
在这里插入图片描述

方式二

1.创建过滤器
创建新的过滤器,不用配置@WebFilter注解,我们同样在启动类中注册过滤器。

@SpringBootApplication
public class FilterApplication {

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

    //获取 FilterRegistrationBean对象 注册过滤器并设置拦截的请求地址
    @Bean
    public FilterRegistrationBean getGilterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean(new FirstFilter());
        //配置要拦截的请求
        bean.addUrlPatterns("/hello");
        return bean;
    }
}

2.创建启动类

添加获取FilterRegistrationBean对象的方法,并在该方法中注册Filter及配置拦截的请求的URL

@SpringBootApplication
public class FilterApplication {

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

    //获取 FilterRegistrationBean对象 注册过滤器并设置拦截的请求地址
    @Bean
    public FilterRegistrationBean getGilterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean(new FirstFilter());
        //配置要拦截的请求
        bean.addUrlPatterns("/hello");
        return bean;
    }
}

测试
在这里插入图片描述
在这里插入图片描述

监听器

方式一
1.创建Listener

 创建一个自定义的Listener,监听ServletContext的初始化和销毁的行为,具体如下:

@WebListener
public class FisrtListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("FirstListener:Servlet容器初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("FirstListener:Servlet容器被销毁了");
    }
}

2.创建启动器

@SpringBootApplication
@ServletComponentScan() //在SpringBoot 启动时会扫描@WebServlet,并将该类实例化
public class FilterApplication {

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

测试
在这里插入图片描述
自定义的监听器监控到了Servlet容器加载的过程~

方式二
1.创建Listener

创建自定义的监听器,不要添加@WebListener注解

public class FisrtListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("FirstListener:Servlet容器初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("FirstListener:Servlet容器被销毁了");
    }
}

2.创建启动器

创建启动类,同时创建注册Listener的方法,如下

@SpringBootApplication
public class FilterApplication {

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

   @Bean
    public ServletListenerRegistrationBean getServletListenerRegistrationBean(){
       ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean(new FisrtListener());
       return bean;
   }
}

在这里插入图片描述

整合页面视图

Freemarker

创建项目时引入依赖
在这里插入图片描述或者pom.xml中引入

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

新建freemaker页面
注意:新版的freemarker的文件后缀为.ftlh

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1">
    <tr>
        <td>用户编号</td>
        <td>用户名</td>
        <td>用户地址</td>
    </tr>
    <#list users as user>
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.address}</td>
        </tr>
    </#list>
</table>
</body>
</html>

controller

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String hello(Model m){
        List<User> users = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            User u = new User();
            u.setId(i);
            u.setName("阿猫"+i);
            u.setAddress("临安"+i);
            users.add(u);
        }
        m.addAttribute("users",users);
        System.out.println(users);
        return "user";
    }
}

Thymeleaf

整体步骤:
(1) 在pom.xml中引入thymeleaf;
Spring Boot默认就是使用thymeleaf模板引擎的,所以只需要在pom.xml加入依赖即可

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

或者新建项目时选择依赖
在这里插入图片描述

在这里插入图片描述
(2) 如何关闭thymeleaf缓存
thymeleaf是一个模板引擎,缓存的意思是加载一次模板之后便不会在加载了,对于生产环境应该加上缓存,但是在开发过程中如果打开缓存,不方便开发人员调试。试想一下,改一行html,就需要重启服务器,肯定是不方便的。
总结一下:
本地开发环境下,需要把缓存关闭,否则调试成本太大。其他环境下缓存都需要打开。
只需要在application.properties进行配置即可

spring.thymeleaf.cache=false

(3) 编写模板文件.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table border="1">
        <tr>
            <td>用户编号</td>
            <td>用户名</td>
            <td>用户地址</td>
        </tr>
        <tr th:each="user : ${user}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.address}"></td>
        </tr>
    </table>
</body>
</html>

在这里插入图片描述
测试

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model m){
        List<User> user = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User u = new User();
            u.setId(i);
            u.setName("阿猫"+i);
            u.setAddress("临安"+i);
            user.add(u);
        }
        m.addAttribute("user",user);
        return "user";
    }
}

在这里插入图片描述

整合JSP

导入相关依赖

	<!-- JSP解析器 -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

可以选择配置视图解析器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	//视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/jsp/", ".jsp");
    }
}

测试

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model m){
        m.addAttribute("hello", "123456");
        return "hello";
    }
}

在这里插入图片描述

整合持久层

整合JDBCTemplate

1.创建项目
在这里插入图片描述导入依赖

		<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>
    <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.32</version>
        </dependency>
    <!-- 数据库连接池 可以导druid-start 但后面用多数据源就需要导入druid-spring-boot-starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.添加相关的配置

在application.properties中添加如下配置

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=1234

3.创建业务层

@Service
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void getAllUsers(){
        List<User> list = jdbcTemplate.query("SELECT * FROM users",new BeanPropertyRowMapper<>(User.class));
        System.out.println(list);
    }
 }

测试

@SpringBootTest
class JdbctemplateApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void contextLoads() {
        userService.getAllUsers();
    }
}

在这里插入图片描述

JdbcTemplate 多数据源

开发时,随着业务量的扩大,数据库里存不下所有数据,这时通常会进行数据库拆分或是引入其他数据库
两种解决方案:1.MyCat  2.多数据源
从而我们需要配置多个数据源,下面基于Spring-data-jpa配置多数据源

在application.properties中添加如下配置

spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.one.username=root
spring.datasource.one.password=1234

spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.url=jdbc:mysql://127.0.0.1:3306/classmgr?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.two.username=root
spring.datasource.two.password=1234

配置DataSource数据源

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DataSource deOne(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource deTwo(){
        return DruidDataSourceBuilder.create().build();
    }
}

注入两个JDBCTemplate实例

//提供两个JDBCTemplate实例
@Configuration
public class JdbcTemplateConfig {

    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("deOne")DataSource dsOne){
        return new JdbcTemplate(dsOne);
    }

    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("deTwo")DataSource dsOne){
        return new JdbcTemplate(dsOne);
    }
}

业务层

@Service
public class UserService {

    @Autowired
    @Qualifier("jdbcTemplateOne")
    private JdbcTemplate jdbcTemplateOne;
    @Autowired
    @Qualifier("jdbcTemplateTwo")
    private JdbcTemplate jdbcTemplateTwo;

    public void getAllUsers(){
        List<User> list = jdbcTemplateOne.query("SELECT * FROM users",new BeanPropertyRowMapper<>(User.class));
        System.out.println(list);
        list = jdbcTemplateTwo.query("SELECT * FROM users",new BeanPropertyRowMapper<>(User.class));
        System.out.println(list);
    }
}

测试

在这里插入图片描述

整合MyBatis

1.创建项目
在这里插入图片描述
手动添加数据库与mysql版本依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
    <!-- 数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.43</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.全局配置文件

在main/src/resources目录下创建application.properties文件

# jdbc的相关配置信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=1234
# mybatis给package设置别名
mybatis.type-aliases-package=con.i.model

3.创建user的实体类

public class User {
    private Integer userid;
    private String username;
    private Integer userage;

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

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

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

    public Integer getUserage() {
        return userage;
    }

    public void setUserage(Integer userage) {
        this.userage = userage;
    }

    public User() {
    }

    public User(Integer userid, String username, Integer userage) {
        this.userid = userid;
        this.username = username;
        this.userage = userage;
    }
}

业务层

//@Mapper   //表示这是个mapper,每个mapper上添加 @mapper注解,或者添加扫描
public interface UserMapper {
    @Select("SELECT * FROM users")
    List<User> getAllUsers();
}

Application下添加扫描

@SpringBootApplication
@MapperScan(basePackages = "com.i.mybatis.mapper") //批量扫描
public class MybatisApplication {

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

}

测试

@SpringBootTest
class MybatisApplicationTests {

    @Autowired
    UserMapper userMapper;

    @Test
    void contextLoads() {
        System.out.println(userMapper.getAllUsers());
    }
}

在这里插入图片描述全路径映射
resource下添加全路径
注意:文件之间分隔是/
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.i.mybatis.mapper.UserMapper">

    <select id="getAllUsers" resultType="com.i.mybatis.model.User">
        SELECT * FROM users;
    </select>
</mapper>

指定文件映射

application.properties中添加对应的设置

# 指定映射文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml

在这里插入图片描述
与mapper接口在同一目录下
pom.xml中配置

指我的java的.xml别忽略了,resource也别忽略了

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
 </build>

MyBatis 多数据源

在application.properties中添加如下配置

# jdbc的相关配置信息
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.one.username=root
spring.datasource.one.password=1234

spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.url=jdbc:mysql://127.0.0.1:3306/classmgr?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.two.username=root
spring.datasource.two.password=1234

配置数据源

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DataSource dsOne(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dsTwo(){
        return DruidDataSourceBuilder.create().build();
    }
}

注入两个实例

Configuration
@MapperScan(basePackages = "com.i.mybatis.mapper",
        sqlSessionFactoryRef = "sqlSessionFactoryOne",
        sqlSessionTemplateRef = "sqlSessionTemplateOne")
public class MyBatisConfigOne {

    @Resource(name = "dsOne")
    DataSource dsOne;

    @Bean
    SqlSessionFactoryBean sqlSessionFactoryOne(){
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        try {
            bean.setDataSource(dsOne);
        }catch (Exception e){
            e.printStackTrace();
        }
        return bean;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplateOne(){
        try {
            return new SqlSessionTemplate(sqlSessionFactoryOne().getObject());
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
@Configuration
@MapperScan(basePackages = "com.i.mybatis.mapper2",
        sqlSessionFactoryRef = "sqlSessionFactoryTwo",
        sqlSessionTemplateRef = "sqlSessionTemplateTwo")
public class MyBatisConfigTwo {

    @Resource(name = "dsTwo")
    DataSource dsOne;

    @Bean
    SqlSessionFactoryBean sqlSessionFactoryTwo(){
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        try {
            bean.setDataSource(dsOne);
        }catch (Exception e){
            e.printStackTrace();
        }
        return bean;
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplateTwo(){
        try {
            return new SqlSessionTemplate(sqlSessionFactoryTwo().getObject());
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

测试

@SpringBootTest
class MybatisApplicationTests {

    @Autowired
    UserMapper userMapper;
    @Autowired
    UserMapper2 userMapper2;

    @Test
    void contextLoads() {
        System.out.println(userMapper.getAllUsers());
        System.out.println(userMapper2.getAllUsers());
    }
}

在这里插入图片描述

整合SpringDataJPA

SpringData: 数据访问规范
JPA(Java Persistence API):
JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

1.构建SpringBoot项目
在这里插入图片描述
添加驱动版本与数据库连接池依赖

	<!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

     <!-- 数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.43</version>
        </dependency>
        
     <!-- 测试工具的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.添加相关的配置

在application.properties中添加如下配置

# JDBC相关信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=1234

# 连接的数据库平台
spring.jpa.database=mysql
spring.jpa.database-platform=mysql
# 重启时更新表操作
spring.jpa.hibernate.ddl-auto=update
# 是否在控制台打印自动生成的sql
spring.jpa.show-sql=true
# 使用MySQL57去创建表,才会用InnoDB做为默认引擎
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

3.创建pojo

//@Entity:声明此对象映射到数据库的数据表
@Entity(name="t_book")
public class Book {

    @Id     //设为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //增量方式:自增长
    private Integer id;

    @Column(name = "b_name")    //标识实体类中属性与数据表中字段的对应关系
    private String name;
    private String author;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
    public Book() {
    }
    public Book(String name, String author) {
        this.name = name;
        this.author = author;
    }
}

4.创建dao接口

创建Dao的接口,实现JpaRepository接口
  即使什么也不写,也有一大堆方法

// 参数1 T:当前需要映射的实体
// 参数2 ID:当前映射的实体中的 ID类型
public interface BookDao extends JpaRepository<Book,Integer> {
}

测试

@SpringBootTest
class JpaApplicationTests {

    @Autowired
    BookDao bookDao;

    @Test
    void contextLoads() {
        Book book = new Book();
        book.setName("彷徨");
        book.setAuthor("鲁迅");
        bookDao.save(book);
    }

在这里插入图片描述

//分页数据测试
    @Test
    void test2() {
        PageRequest pageable = PageRequest.of(1, 3);
        Page<Book> page = bookDao.findAll(pageable);
        System.out.println("是否首页: "+page.isFirst());
        System.out.println("是否尾页: "+page.isLast());
        System.out.println("当前数据页的数据量: "+page.getNumberOfElements());
        System.out.println("页码: "+page.getNumber());
        System.out.println("查到的数据: "+page.getContent());
        System.out.println("总页数: "+page.getTotalPages());
        System.out.println("每页数据量: "+page.getSize());
        System.out.println("总记录数: "+page.getTotalElements());
    }

在这里插入图片描述
在这里插入图片描述

自定义方法
虽然SpringDataJPA提供了SQL语句方法,但明显不够用,不够灵活,可以自定义

// 参数1 T:当前需要映射的实体
// 参数2 ID:当前映射的实体中的 ID类型
// 方法的命名可以从三个单词开始  read  find  get
public interface BookDao extends JpaRepository<Book,Integer> {
    //查询该作者的所有信息
    List<Book> findBookByAuthorIs(String author);

    //查询条件:ID大于..并且author为..
    List<Book> findBookByIdGreaterThanAndAuthorStartingWith(Integer id,String author);

    //查询条件:ID大于..或书名为..
    List<Book> findBookByIdGreaterThanOrNameContaining(Integer id,String name);
}

测试

   @Test
    void test3() {

        List<Book> list = bookDao.findBookByAuthorIs("鲁迅");
        System.out.println(list);
        System.out.println("------------");
        
        List<Book> list1 = bookDao.findBookByIdGreaterThanAndAuthorStartingWith(2, "罗");
        System.out.println(list1);
        System.out.println("------------");
        
        List<Book> list2 = bookDao.findBookByIdGreaterThanOrNameContaining(2, "西");
        System.out.println(list2);
    }

在这里插入图片描述
自定义SQL语句方法
@Query里的条件有两种写法:
 1. id=?1,id=?2这种,取第一个参数,第二个参数,但不灵活
 2. id=:id,name=:name,根据属性名,但需要在形参前加@Param("属性名"),表示形参与SQL语句的值是绑定的

// 参数1 T:当前需要映射的实体
// 参数2 ID:当前映射的实体中的 ID类型
public interface BookDao extends JpaRepository<Book,Integer> {
//自定义SQL语句方法
    //查询ID最大的信息
    //nativeQuery = true:可执行原生查询
    @Query(value = "SELECT * FROM t_book WHERE id=(SELECT MAX(id) FROM t_book)",nativeQuery = true) //原生查询
    Book maxIdBook();

    //根据ID修改书名
    @Modifying  //告诉数据库这是条 增加丶修改丶删除操作
    @Transactional  //事务
    @Query(value = "UPDATE t_book SET b_name=:name WHERE id=:id",nativeQuery = true)
    void updateBookById(@Param("name") String name, @Param("id") Integer id);
}

在这里插入图片描述

Jpa 多数据源

在application.properties中添加如下配置

# JDBC相关信息
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.url=jdbc:mysql://127.0.0.1:3306/demo?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.one.username=root
spring.datasource.one.password=1234

spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.url=jdbc:mysql://127.0.0.1:3306/classmgr?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.two.username=root
spring.datasource.two.password=1234

# 连接的数据库平台
spring.jpa.database=mysql
spring.jpa.database-platform=mysql
# 重启时更新表操作
spring.jpa.hibernate.ddl-auto=update
# 是否在控制台打印自动生成的sql
spring.jpa.show-sql=true
# 使用MySQL57去创建表,才会用InnoDB做为默认引擎
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

配置数据源

@Configuration
public class DataSourceConfig {
    @Bean
    @Primary //优先使用
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DruidDataSource dsOne(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DruidDataSource dsTwo(){
        return DruidDataSourceBuilder.create().build();
    }
}

注入两个JDBCTemplate实例

@Configuration
@EnableJpaRepositories(basePackages = "com.i.jpa.dao",
        entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanOne",
        transactionManagerRef = "platformTransactionManagerOne")
public class JpaConfigOne {
    @Autowired
    @Qualifier("dsOne")
    DataSource dsOne;

    @Autowired
    JpaProperties jpaProperties;

    @Bean
    @Primary	//优先使用
    LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsOne)
                .packages("com.i.jpa.model")
                .properties(jpaProperties.getProperties())
                .persistenceUnit("pu1")
                .build();
    }

    //配置事务
    @Bean
    PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder){
        return new JpaTransactionManager(localContainerEntityManagerFactoryBeanOne(builder).getObject());
    }
}

@Configuration
@EnableJpaRepositories(basePackages = "com.i.jpa.dao2",
        entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanTwo",
        transactionManagerRef = "platformTransactionManagerTwo")
public class JpaConfigTwo {
    @Autowired
    @Qualifier("dsTwo")
    DataSource dsOne;

    @Autowired
    JpaProperties jpaProperties;

    @Bean
    LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanTwo(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dsOne)
                .packages("com.i.jpa.model")
                .properties(jpaProperties.getProperties())
                .persistenceUnit("pu2")
                .build();
    }

    //配置事务
    @Bean
    PlatformTransactionManager platformTransactionManagerTwo(EntityManagerFactoryBuilder builder){
        return new JpaTransactionManager(localContainerEntityManagerFactoryBeanTwo(builder).getObject());
    }
}

创建Dao接口

// 参数1 T:当前需要映射的实体
// 参数2 ID:当前映射的实体中的 ID类型
public interface BookDao extends JpaRepository<Book,Integer> {
}
public interface Dao2 extends JpaRepository<Book,Integer> {
}

测试

  @Autowired
    Dao2 dao2;
    @Test
    void test5(){
        System.out.println(bookDao.findAll());
        System.out.println(dao2.findAll());
    }

在这里插入图片描述

文件上传

文件上传
1.表单页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit" value="上传">
    </form>
</body>
</html>

2.控制层处理

添加控制器处理上传的文件信息

@RestController
public class FileUploadController {
    SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/");

    @PostMapping("/upload")
    public String fileupload(MultipartFile file, HttpServletRequest req){
        String format = sdf.format(new Date());
        String realPath = req.getServletContext().getRealPath("/img") + format;
        File folder = new File(realPath);
        if(!folder.exists()){
            folder.mkdirs();
        }
        String oldName = file.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            file.transferTo(new File(folder,newName));
            return req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/img" + format + newName;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

或者

@RestController
@RequestMapping("/user")
public class UserController  {

    @RequestMapping("/upload")
    public String upload(MultipartFile upload,String username) throws IOException {
        System.out.println("账号:"+username+" "+upload.getOriginalFilename());
        upload.transferTo(new File("c:/tools/",upload.getOriginalFilename()));
        return "success:上传成功";
    }
}

3.设置上传参数
在application.properties中添加配置参数

# 上传的单个文件最大大小
spring.servlet.multipart.max-file-size=1MB
# 将文件写往硬盘的临界值
spring.servlet.multipart.file-size-threshold=0KB
# 上传文件总大小
spring.servlet.multipart.max-request-size=10MB
# 临时目录
# spring.servlet.multipart.location=

在这里插入图片描述

在这里插入图片描述

整合 NoSQL

整合Redis

1.导入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.配置application.properties

# 地址信息
spring.redis.host=192.168.230.172
# 端口
spring.redis.port=6379
# 16个库中选择那个缓存库
spring.redis.database=0

# 设置了密码就配置
# spring.redis.password=

测试

 @SpringBootTest
class RedisApplicationTests {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("name","张三");
        Object name = ops.get("name");
        System.out.println(name);
    }

}

在这里插入图片描述

序列化了,可以使用StringRedisTemplate方式

@SpringBootTest
class RedisApplicationTests {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    void contextLoads() {
        ValueOperations ops = stringRedisTemplate.opsForValue();
        ops.set("name","张三");
        Object name = ops.get("name");
        System.out.println(name);
    }

在这里插入图片描述

整合SpringCache

1.项目依赖

	<!-- 使用 Spring caching 支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
    <!-- 使用Redis键值存储数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.配置application.properties

# 地址信息
spring.redis.host=192.168.230.172
# 端口
spring.redis.port=6379
# 16个库中选择那个缓存库
spring.redis.database=0

3.启动类中开启缓存注解

@SpringBootApplication
@EnableCaching  //开启缓存注解
public class RedisApplication {

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

}

业务层

@Service
@CacheConfig(cacheNames = {"MyCache"})
public class UserService {

    @Cacheable
    public User getUserById(Integer userid,String username) {
        System.out.println("---" + userid + "---");
        User user = new User();
        user.setUserid(userid);
        user.setUsername(username);
        return user;
    }
}

测试

@SpringBootTest
class RedisApplicationTests {
    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
       System.out.println(">>>>>"+userService.getUserById(1,"阿猫"));
       System.out.println(">>>>>"+userService.getUserById(2,"阿狗"));
        ;
    }

}

在这里插入图片描述

序列化格式,虽然不影响使用,但我就是想看到我的数据,怎么办?转为JSON格式

配置类

//将缓存数据转为JSON格式
@Configuration
@EnableCaching  //开启缓存注解
public class RedisConfig extends CachingConfigurerSupport {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

    /**
     * 二者选其一即可
     */

//    @Bean
//    public RedisCacheConfiguration redisCacheConfiguration() {
//        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
//        ObjectMapper objectMapper = new ObjectMapper();
//        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//        serializer.setObjectMapper(objectMapper);
//        return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
//    }

}

再次测试
在这里插入图片描述

整合Ehcache

1.创建项目

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 集成ehcache需要的依赖-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.6</version>
        </dependency>

2.配置application.properties

@SpringBootApplication
@EnableCaching      //开启缓存注解
public class EhcacheApplication {

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

3.编写ehcache.xml配置文件

<ehcache>
    <diskStore path="java.io.tmpdir/ehcahe"/>
    <!--
    name:缓存名称。
       maxElementsInMemory:缓存最大个数。
       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
       overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
       maxElementsOnDisk:硬盘最大缓存个数。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
       clearOnFlush:内存数量最大时是否清除。

    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="user"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="600"/>
    <cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
           maxElementsInMemory="100"
           eternal="false"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>
</ehcache>

4.业务层

@Service
public class UserService {

    @Cacheable(cacheNames = "user")
    public User getUserById(Integer userid) {
        System.out.println("---" + userid + "---");
        User user = new User();
        user.setUserid(userid);
        return user;
    }
}

测试

@SpringBootTest
class EhcacheApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void contextLoads() {
        userService.getUserById(1);
        userService.getUserById(2);
    }
}

在这里插入图片描述

Session共享

在使用spring boot做负载均衡的时候,多个app之间的session要保持一致,这样负载到不同的app时候,在一个app登录之后,而访问到另外一台服务器的时候,session丢失。
常规的解决方案都是使用:如apache使用mod_jk.conf,使用Memcached进行共享。
在开发spring boot app的时候可以借助 spring session 和redis或者ehcache,用外置的redis或者ehcache来存储session的状态,这里使用redis进行介绍,ehcache实现是一样的。

1.创建项目
在这里插入图片描述

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

controller层

@RestController
public class HelloController {
    @Value("${server.port}")
    Integer port;

    //存值
    @GetMapping("/set")
    public String set(HttpSession session) {
        session.setAttribute("username","虎啊兄弟");
        return String.valueOf(port);
    }

    //取值
    @GetMapping("/get")
    public String get(HttpSession session){
        return session.getAttribute("username") + ": " + port;
    }

在这里插入图片描述

在这里插入图片描述

D:\IDEA\space1\sessionshare\target>java -jar sessionshare-0.0.1-SNAPSHOT.jar

新建一个窗口启动并更改端口号

D:\IDEA\space1\sessionshare>cd target

D:\IDEA\space1\sessionshare\target>java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8081

在这里插入图片描述
在这里插入图片描述

此时已经完成了Session共享

查看效果
这时候登录系统在不同的app之间跳转的时候,session都是一致了,redis上可以看到
在这里插入图片描述

自动进行请求分发:简化端口,用户不需要输入端口
总结
使用这些代码之后 ,无论你使用nginx或者apache,都无须在关心多个app之间的session一致的问题了。

安全管理

整合 Shiro

原生的整合

  1. 创建项目
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
  1. 创建 Realm
public class MyRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = ((UsernamePasswordToken) token).getUsername();
        if ("i".equals(username)){
            return new SimpleAuthenticationInfo(username, "123", getName());
        }
        return null;
    }
}
  1. 配置 Shiro

在这里进行 Shiro 的配置主要配置 3 个 Bean :

1.  首先需要提供一个 Realm 的实例
2.  需要配置一个 SecurityManager,在 SecurityManager 中配置 Realm
3.  配置一个 ShiroFilterFactoryBean ,在 ShiroFilterFactoryBean 中指定路径拦截规则等
4.  配置登录和测试接口

其中,ShiroFilterFactoryBean 的配置稍微多一些,配置含义如下:

setSecurityManager 表示指定 SecurityManager。
setLoginUrl 表示指定登录页面。
setSuccessUrl 表示指定登录成功页面。
接下来的 Map 中配置了路径拦截规则,注意,要有序。
@Configuration
public class ShiroConfig {

    @Bean
    MyRealm myRealm(){
        return new MyRealm();
    }

    @Bean
    DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauthorizedurl");
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

4.配置登录 Controller

@RestController
public class LoginController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

    @PostMapping("/doLogin")
    public void doLogin(String username,String password){
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            System.out.println("登陆成功");
        }catch (AuthenticationException e){
            System.out.println("登陆失败");
            e.printStackTrace();
        }
    }
}

测试时,首先访问 /hello 接口,由于未登录,所以会报404
在这里插入图片描述
然后调用 /doLogin 接口完成登录
在这里插入图片描述再次访问 /hello 接口,就可以成功访问了
在这里插入图片描述

在这里插入图片描述
使用 Shiro Starter

上面这种配置方式实际上相当于把 SSM 中的 XML 配置拿到 Spring Boot 中用 Java 代码重新写了一遍,除了这种方式之外,我们也可以直接使用 Shiro 官方提供的 Starter 。

1.创建工程,和上面的一样

创建成功后,添加 shiro-spring-boot-web-starter ,这个依赖可以代替之前的 shiro-web 和 shiro-spring 两个依赖,pom.xml 文件如下

2.创建 Realm

这里的 Realm 和上面的一样

  1. 配置 Shiro 基本信息

接下来在 application.properties 中配置 Shiro 的基本信息:

  1. 第一行表示是否允许将sessionId 放到 cookie 中
  2. 第二行表示开启 shiro,对外环境下启用
  3. 第三行表示访问未获授权的页面时,默认的跳转路径
  4. 第四行表示开启 shiro,web环境下启用
  5. 第五行表示登录成功的跳转页面
  6. 第六行表示登录页面
shiro.sessionManager.sessionIdCookieEnabled=false
shiro.enabled=true
shiro.unauthorizedUrl=/unauthorizedurl
shiro.web.enabled=true
shiro.successUrl=/index
shiro.loginUrl=/login

4.配置 ShiroConfig

@Configuration
public class ShiroConfig {

    @Bean
    MyRealm myRealm(){
        return new MyRealm();
    }

    @Bean
    DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterChainDefinition shiroFilterFactoryBean(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        definition.addPathDefinition("/doLogin", "anon");
        definition.addPathDefinition("/**", "authc");
        return definition;
    }
}

这里的配置和前面的比较像,但是不再需要 ShiroFilterFactoryBean 实例了,替代它的是 ShiroFilterChainDefinition ,在这里定义 Shiro 的路径匹配规则即可。

Spring Security

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。

相对于 Shiro,在 SSM/SSH 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有 Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。

自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security。

因此,一般来说,常见的安全管理技术栈的组合是这样的:

SSM + Shiro
Spring Boot/Spring Cloud + Spring Security

注意,这只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的

1.项目创建
在这里插入图片描述pom.xml中

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

2.初次体验

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

访问
在这里插入图片描述
当用户从浏览器发送请求访问 /hello 接口时,服务端会返回 302 响应码,让客户端重定向到 /login 页面,用户在 /login 页面登录,登陆成功之后,就会自动跳转到 /hello 接口。

另外,也可以使用 POSTMAN 来发送请求,使用 POSTMAN 发送请求时,可以将用户信息放在请求头中(这样可以避免重定向到登录页面)

在这里插入图片描述
通过以上两种不同的登录方式可看出,Spring Security 支持两种不同的认证方式:

  • 可以通过 form 表单来认证
  • 可以通过 HttpBasic 来认证

用户名配置
默认情况下,登录的用户名是 user ,密码则是项目启动时随机生成的字符串,可以从启动的控制台日志中看到默认密码
在这里插入图片描述

这个随机生成的密码,每次启动时都会变。对登录的用户名/密码进行配置,有三种不同的方式:

  1. 在 application.properties 中进行配置
  2. 通过 Java 代码配置在内存中
  3. 通过 Java 从数据库中加载

1. 配置文件配置用户名/密码

可以直接在 application.properties 文件中配置用户的基本信息:

spring.security.user.name=iii
spring.security.user.password=123

注意:此处可以直接写明文,会自动转成密文
配置完成后,重启项目,就可以使用这里配置的用户名/密码登录了。

2. Java 配置用户名/密码
首先需要创建一个 Spring Security 的配置类,集成自 WebSecurityConfigurerAdapter 类,如下:

这里我们在 configure 方法中配置了两个用户,用户的密码都是加密之后的字符串(明文是 123),从 Spring5 开始,强制要求密码要加密,如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,但是不建议这么做,毕竟不安全。

Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存盐的字段了,这一点比 Shiro 要方便很多。

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中配置两个用户
        auth.inMemoryAuthentication()
                .withUser("i").password("123").roles("admin")
                .and()
                .withUser("ii").password("456").roles("user");
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

登录配置
对于登录接口,登录成功后的响应,登录失败后的响应,可以在 WebSecurityConfigurerAdapter 的实现类中进行配置。例如:
配置身份访问权限

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //配置身份访问权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()    //开启登陆配置
                .antMatchers("/admin/**").hasRole("admin")  //表示访问 /hello 这个接口,需要具备 admin 这个角色
                .antMatchers("/user/**").hasAnyRole("admin","user") //表示访问 /hello 这个接口,需要具备 admin 或 user角色
                .anyRequest().authenticated()  //表示剩余的其他接口,登录之后就能访问
                .and()
                .formLogin()
                .usernameParameter("username")      //自定义登陆时用户名的Key
                .passwordParameter("password")      //自定义登陆时密码的Key
                .loginProcessingUrl("/doLogin")     //处理登陆的接口
                .loginPage("/login")    //定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        //自定义登陆后状态信息
                        Map<String, Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("msg","登陆成功");
                        map.put("obj", authentication.getPrincipal());

                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        //自定义登陆后状态信息
                        Map<String, Object> map = new HashMap<>();
                        map.put("status",500);
                        map.put("msg","登陆失败");

                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()   //和表单登录相关的接口统统都直接通过
                .and()
                .csrf().disable();	//关掉跨域保护(CSRF),否则postman不能请求过来
    }
}

我们可以在 successHandler 方法中,配置登录成功的回调,如果是前后端分离开发的话,
登录成功后返回 JSON 即可,同理,failureHandler 方法中配置登录失败的回调,
logoutSuccessHandler 中则配置注销成功的回调。

忽略拦截
如果某一个请求地址不需要拦截的话,有两种方式实现:

  • 设置该地址匿名访问
  • 直接过滤掉该地址,即该地址不走 Spring Security 过滤器链

推荐使用第二种方案,配置如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/user");
    }
}

连接数据库
1.依赖

		<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!-- 数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.43</version>
        </dependency>

2.创建实体类
UserDetails => Spring Security基础接口,包含某个用户的账号,密码,权限,状态(是否锁定)等信息。只有getter方法。
相当于定义一个规范,Security这个框架不管你的应用时怎么存储用户和权限信息的。只要你取出来的时候把它包装成一个UserDetails对象给我用就可以了

public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;

    //获取用户的角色
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role:roles){
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    //账号是否未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    //账号是否未锁定
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
    //密码是否未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Integer getId() {
        return id;
    }

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

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

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

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }
}
在这里插入代码片

3.service

@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(s);
        if (user == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        user.setRoles(userMapper.getRoleById(user.getId()));
        return user;
    }
}

4.mapper配置

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);

    List<Role> getRoleById(Integer uid);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.i.security.mapper.UserMapper">

    <select id="loadUserByUsername" resultType="com.i.security.model.User">
        SELECT * FROM user WHERE username=#{username}
    </select>
    
    <select id="getRoleById" resultType="com.i.security.model.Role">
        SELECT r.* FROM role r,user_role ur WHERE r.id=ur.rid AND ur.uid=#{uid}
    </select>
</mapper>
<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>src/main/resources</resource>
        </resources>
</build>

5.数据库连接信息

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/security?useSSL=true&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=1234

6.添加 Security 配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    //配置身份访问权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()    //开启登陆配置
                .antMatchers("/admin/**").hasRole("admin")  //表示访问 /hello 这个接口,需要具备 admin 这个角色
                .antMatchers("/user/**").hasAnyRole("admin","user") //表示访问 /hello 这个接口,需要具备 admin 或 user角色
                .anyRequest().authenticated()  //表示剩余的其他接口,登录之后就能访问
                .and()
                .formLogin()
                .usernameParameter("username")      //自定义登陆时用户名的Key
                .passwordParameter("password")      //自定义登陆时密码的Key
                .loginProcessingUrl("/doLogin")     //处理登陆的接口
                .loginPage("/login")    //定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        //自定义登陆后状态信息
                        Map<String, Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("msg","登陆成功");
                        map.put("obj", authentication.getPrincipal());

                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        //自定义登陆后状态信息
                        Map<String, Object> map = new HashMap<>();
                        map.put("status",500);
                        map.put("msg","登陆失败");
                        if(e instanceof BadCredentialsException){
                            map.put("msg", "用户名或密码写错");
                        }else if (e instanceof DisabledException){
                            map.put("msg","账号被禁用,登陆失败");
                        }else if (e instanceof LockedException){
                            map.put("msg", "账号被锁定,登陆失败");
                        }
                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()   //和表单登录相关的接口统统都直接通过
                .and()
                .csrf().disable();  //关掉跨域保护(CSRF),否则postman不能请求过来
    }
}

测试
在这里插入图片描述
Spring Security 登录使用 JSON 格式数据

在使用 SpringSecurity 中,默认的登录数据是通过 key/value 的形式来传递的,默认情况下不支持 JSON格式的登录数据,如果有这种需求,就需要自己来解决

如果想将用户名密码通过 JSON 的方式进行传递,则需要自定义相关过滤器,通过分析源码可以发现,默认的用户名密码提取在 UsernamePasswordAuthenticationFilter 过滤器中,部分源码如下:

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	private boolean postOnly = true;
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}

	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();

		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}
}

从这里可以看到,默认的用户名/密码提取就是通过 request 中的 getParameter 来提取的,如果想使用 JSON 传递用户名密码,只需要将这个过滤器替换掉即可,自定义过滤器如下:

public class MyFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException {
        if (!req.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + req.getMethod());
        }
        String username = null;
        String password = null;

        //判断是否 JSON形式登陆
        if (!req.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){
            return super.attemptAuthentication(req, resp);
        }
        try {
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);//该方法第一个参数可以直接传InputStream,不用转成字符串
            username = user.getUsername();
            password = user.getPassword();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }
        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        setDetails(req, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

这里只是将用户名/密码的获取方案重新修正下,改为了从 JSON 中获取用户名密码,
然后在 SecurityConfig 中作出如下修改:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   @Autowired
   UserService userService;
   
   @Bean
   MyFilter myFilter() throws Exception {
       MyFilter myFilter = new MyFilter();
       myFilter.setAuthenticationManager(authenticationManagerBean());
       return myFilter;
   }

    //配置身份访问权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()    //开启登陆配置
                .antMatchers("/admin/**").hasRole("admin")  //表示访问 /hello 这个接口,需要具备 admin 这个角色
                .antMatchers("/user/**").hasAnyRole("admin","user") //表示访问 /hello 这个接口,需要具备 admin 或 user角色
                .anyRequest().authenticated()  //表示剩余的其他接口,登录之后就能访问
                .and()
                .addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf().disable();  //关掉跨域保护(CSRF),否则postman不能请求过来
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

将自定义的 CustomAuthenticationFilter 类加入进来即可,接下来就可以使用 JSON 进行登录了,如下:
在这里插入图片描述

整合Swagger2

1.导入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    <!-- 手动导入swagger2依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

2.配置application.properties中开启注解使用

@SpringBootApplication
@EnableSwagger2 //开启Swagger
public class Swagger2Application {

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

}

3.配置Bean

@Configuration
public class Swagger2Config {
    @Bean
    Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.i.swagger2.controller"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(new ApiInfoBuilder()
                        .description("MyAPI文档网站")
                        .title("MyAPI文档")
                        .version("v1.0")
                        .contact(new Contact("i","http://www.i.com","123456@qq.com"))
                        .build());
    }
}

4.controller层定义

@RestController
@Api(tags = "用户相关的接口")  //用于类;表示标识这个类是swagger的资源
public class UserController {

    @GetMapping("/hello")
    public String hello(){
        return "hello swagger";
    }

    @GetMapping("/user")
    @ApiOperation("根据用户ID 查询用户") //@ApiOperation():表示一个http请求的操作:value用于方法描述 ,notes用于提示内容 ,tags可以重新分组
    @ApiImplicitParam(name = "id",value = "用户ID",defaultValue = "1")    //@ApiImplicitParam: 表示单独的请求参数
    public User getUserById(Integer id){
        User user = new User();
        user.setId(id);
        return user;
    }

    @PutMapping("/user")
    @ApiOperation("根据用户ID 更新用户名")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "用户ID", defaultValue = "1"),
            @ApiImplicitParam(name = "username", value = "用户名", defaultValue = "1")
    })
    public User updateUsernameById(String username, Integer id) {
        User user = new User();
        user.setId(id);
        user.setUsername(username);
        return user;
    }

    @DeleteMapping("/user/{id{")
    @ApiOperation("根据用户ID 删除用户")
    @ApiImplicitParam(name = "id", value = "用户ID", defaultValue = "1")
    public void deleteUserById(@PathVariable Integer id){
        //@PathVariable: 接收请求路径中占位符的值
        System.out.println("删除用户ID为:"+id);
    }

    @PostMapping("/user")
    @ApiOperation("添加用户")
    public User addUser(@RequestBody User user){
        //@RequestBody: 接收前端传递给后端的json字符串中的数据
        return user;
    }
}

swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。

  • @Api:修饰整个类,描述Controller的作用
  • @ApiOperation:描述一个类的一个方法,或者说一个接口
  • @ApiParam:单个参数描述
  • @ApiParam:单个参数描述
  • @ApiProperty:用对象接收参数时,描述对象的一个字段
  • @ApiResponse:HTTP响应其中1个描述
  • @ApiResponses:HTTP响应整体描述
  • @ApiIgnore:使用该注解忽略这个API
  • @ApiError :发生错误返回的信息
  • @ApiImplicitParam:一个请求参数
  • @ApiImplicitParams:多个请求参数
  • @ApiModel: 表示对类进行说明,用于参数用实体类接收
  • @ApiModelProperty()用于方法,字段 ,表示对model属性的说明或者数据操作更改

在这里插入图片描述
定义实体类的别名

@ApiModel("用户实体类")
public class User {
    @ApiModelProperty("用户ID")
    private Integer id;
    @ApiModelProperty("用户名")
    private String username;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public User() {
    }
    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }
}

在这里插入图片描述

定时任务

1.Spring中的方案 @Scheduled注解
2.Quartz

Scheduled(定时任务器)

1.创建项目添加依赖

创建一个SpringBoot项目,并添加如下依赖

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

2.启动器开始设置

在启动器头部开启@EnableScheduling注解

@SpringBootApplication
@EnableScheduling  //开启定时任务
public class ScheduledApplication {

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

3.创建定时任务

 创建定时任务的java类,具体如下:

@Service
public class HelloService {

    /*
    *  @Scheduled:设置定时任务
    *  fixedRate  表示定时任务的开启频率,即两个定时任务开启之间的间隔
    *  fixedDelay 表示定时任务的延迟时间间隔,即前一个定时任务结束时间和后一个定时任务开始之间的间隔
    *  cron 属性:cron 表达式。定时任务触发是时间的一个字符串表达形式
    */
    /*@Scheduled(fixedDelay = 3000)
    public void hello(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("定时任务执行了: "+new Date());
    }*/
    @Scheduled(cron = "0/2 * * * * ?")
    public void hello1(){
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("定时任务执行了: "+new Date());
    }
}

在这里插入图片描述
cron 表达式介绍

Cron 表达式是一个字符串,分为 6 或 7 个域,每一个域代表一个含义
Cron 有如下两种语法格式:

  1. Seconds Minutes Hours Day Month Week Year
  2. Seconds Minutes Hours Day Month Week

结构
  corn 从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

各字段的含义

位置时间域名允许值允许的特殊字符
10-59, - * /
2分钟0-59, - * /
3小时0-23, - * /
41-31, - * / L W C
51-12, - * /
6星期1-7, - * ? / L C #
7年(可选)1970-2099, - * /

Cron 表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能,细说如下:

●星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”;
●问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于占位符;
●减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从 10 到 12 点,即 10,11,12;
●逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
●斜杠(/):x/y 表达一个等步长序列,x 为起始值,y 为增量步长值。如在分钟字段中使用 0/15,则表示为 0,15,30 和 45 秒,而 5/15 在分钟字段中表示 5,20,35,50,你也可以使用/y,它等同于 0/y;
●L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L 在日期字段中,表示这个月份的最后一天,如一月的 31 号,非闰年二月的 28 号;如果 L 用在星期中,则表示星期六,等同于 7。但是,如果 L 出现在星期字段里,而且在前面有一个数值 X,则表示“这个月的最后 X 天”,
例如,6L 表示该月的最后星期五;
●W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如 15W 表示离该月 15 号最近的工作日,如果该月 15 号是星期六,则匹配 14 号星期五;如果 15 日是星期日,则匹配 16 号星期一;如果 15 号是星期二,那结果就是 15 号星期二。但必须注意关联的匹配日期不能够跨月,如你指定 1W,如果 1 号是星期六,结果匹配的是 3 号星期一,而非上个月最后的那天。W 字符串只能指定单一日期,而不能指定日期范围;
●LW 组合:在日期字段可以组合使用 LW,它的意思是当月的最后一个工作日;
●井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如 6#3 表示当月的第三个星期五(6表示星期五,#3 表示当前的第三个),而 4#5 表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
● C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如 5C 在日期字段中就相当于日历 5 日以后的第一天。1C 在星期字段中相当于星期日后的第一天。

Cron 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。

例子:

@Scheduled(cron = “0 0 1 1 1 ?”)    //每年一月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 1,6 ?”)  //一月和六月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 1,4,7,10 ?”) //每个季度的第一个月的一号的 1:00:00 执行一次
@Scheduled(cron = “0 0 1 1 * ?”)	//每月一号 1:00:00 执行一次
@Scheduled(cron=“0 0 1 * * *”) 		//每天凌晨 1 点执行一次

Quartz定时任务框架

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.0。

Quartz的使用

一、Quartz 的使用思路

Quartz的使用Quartz的使用
job - 任务你要做什么事?
Trigger - 触发器你什么时候去做?
Scheduler - 任务调度你什么时候需要去做什么事?

1.创建项目添加依赖

创建普通maven项目,添加相关依赖:

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.创建job

public class QuartzDemo implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("quartz任务执行了..."+new Date());
    }
}

不整合SpringBoot,主方法运行

通过主方法测试。

public static void main(String[] args) throws Exception{
     // 1、创建Job对象:你要做什么事?
     JobDetail job = JobBuilder.newJob(QuartzDemo.class).build();
     /**
      *  2、创建trigger
      * 简单的 trigger 触发时间:通过 Quartz 提供一个方法来完成简单的重复调用 cron
      * Trigger:按照 Cron 的表达式来给定触发的时间
      */
     Trigger trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")).build();

     // 3.创建Scheduled对象,在什么时候做什么事?
     Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
     scheduler.scheduleJob(job,trigger);

     // 启动
     scheduler.start();
 }

整合SpringBoot
3.添加Quartz的配置类

@Configuration
public class QuartzConfig {

    //1. 创建 Job对象
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean(){
        JobDetailFactoryBean factory = new JobDetailFactoryBean();
        // 关联自己的 job类
        factory.setJobClass(QuartzDemo.class);
        return factory;
    }

    //2. 创建 Trigger对象
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
        SimpleTriggerFactoryBean factory = new SimpleTriggerFactoryBean();
        //关联 jobDetail对象
        factory.setJobDetail(jobDetailFactoryBean.getObject());
        //该参数表示一个执行的毫秒数
        factory.setRepeatInterval(2000);
        //重复次数
        return factory;
    }

    // Cron Trigger
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
        CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
        factory.setJobDetail(jobDetailFactoryBean.getObject());
        //设置触发时间
        factory.setCronExpression("0/2 * * * * ?");
        return factory;
    }

    //3. 创建 Scheduler对象
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean cronTriggerFactoryBean) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //关联 trigger
        factory.setTriggers(cronTriggerFactoryBean.getObject());
        return factory;
    }
}

4.启动项目

在这里插入图片描述

定义系统启动任务

@Component
@Order(99) //优先级,数字越大,优先级越低
public class MyCommandLineRunner1 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner1---- "+ Arrays.toString(args));
    }
}
@Component
@Order(100) //优先级,数字越大,优先级越低
public class MyCommandLineRunner2 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner2---- "+ Arrays.toString(args));
    }
}

启动后
在这里插入图片描述
如何在不动源码情况下追加数据
在这里插入图片描述在这里插入图片描述

如何在项目启动后继续追加呢?

第一种方式:将项目打包后在命令页面输入数据
在这里插入图片描述在这里插入图片描述
K/V对
在这里插入图片描述在这里插入图片描述

导入SpringXML配置文件

了解即可,知道SpringBoot也可以使用XML文件方式就行

  1. resouorces下 创建配置文件
<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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- 自定义的bean -->
    <bean class="com.i.commandlinerunner.MyInterceptor" id="myInterceptor"></bean>

</beans>
//完成拦截器的注册,相当于xml文件
@Configuration
@ImportResource(locations = "classpath:beans.xml")  //导入xml配置项
public class SpringMVCConfig implements WebMvcConfigurer {
    @Autowired
    private MyInterceptor myInterceptor;

    //注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }
}

生产监控

创建项目
在这里插入图片描述

@SpringBootApplication
@EnableAdminServer
public class AdminserverApplication {

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

}

注册服务

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

}

application.properties中配置

spring.boot.admin.client.url=127.0.0.1:8080
management.endpoints.web.exposure.include=*
server.port=8081

在这里插入图片描述

邮件发送

邮箱里设置服务随便开启一个
在这里插入图片描述1.创建项目
在这里插入图片描述

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.配置邮箱基本信息

在 application.properties 中配置邮箱的基本信息:

# 配置SMTP服务器地址
spring.mail.host=smtp.qq.com
# SMTP服务器的端口,465587
spring.mail.port=587
# 配置邮箱用户名
spring.mail.username=1135827553@qq.com
# 配置密码,指授权码
spring.mail.password=khhhbavtcsngjfcd
# 默认的邮件编码
spring.mail.default-encoding=UTF-8
# 配置SSL加密工厂
spring.mail.properties.sslFactory=javax.net.ssl.SSLSocketFactory
# 开启DEBUG,打印日志,方便排查
spring.mail.properties.debug=true
# 协议
spring.mail.protocol=smtp

发送简单邮件
简单邮件就是指邮件内容是一个普通的文本文档

@Autowired
    JavaMailSender mailSender;

    @Test
    void contextLoads() {
        //1.构建一个邮件对象
            SimpleMailMessage message = new SimpleMailMessage();
        //2.邮件的主题
            message.setSubject("这是一封测试邮件");
        //3.邮件的正文
            message.setText("这是测试邮件的正文");
        //4.邮件的发送日期
            message.setSentDate(new Date());
        //5.邮件的发送者
            message.setFrom("113xxx@qq.com");
        //6.邮件的接收者,可以多个
            message.setTo("80xxx@qq.com");
        //7.邮件的抄送人,可以多个
            message.setCc("15xxx@qq.com");
        //8.邮件的隐秘抄送人,可以多个
            message.setBcc("24xxxx@qq.com");
        //9.发送邮件
            mailSender.send(message);
    }

测试
在这里插入图片描述
发送带附件的邮件
邮件的附件可以是图片,也可以是普通文件,都是支持的

@Test
    void test() throws MessagingException {
        //创建复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //true表示这是封复杂邮件
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setSubject("这是一封测试邮件");
        helper.setText("这是测试邮件的正文");
        helper.setSentDate(new Date());
        helper.setFrom("113XXX@qq.com");
        helper.setTo("8077XXX@qq.com");
        helper.addAttachment("dog.jpg", new File("C:\\Users\\user\\Pictures\\Saved Pictures\\dog.jpg"));
        mailSender.send(mimeMessage);
    }

注意这里在构建邮件对象上和前文有所差异,这里是通过 javaMailSender 来获取一个复杂邮件对象,然后再利用 MimeMessageHelper 对邮件进行配置,MimeMessageHelper 是一个邮件配置的辅助工具类,创建时候的 true 表示构建一个 multipart message 类型的邮件,有了 MimeMessageHelper 之后,我们针对邮件的配置都是由 MimeMessageHelper 来代劳。

最后通过 addAttachment 方法来添加一个附件
在这里插入图片描述
发送带图片资源的邮件
图片资源和附件有什么区别?
图片资源是放在邮件正文中的,即一打开邮件,就能看到图片。
但是一般来说,不建议使用这种方式,一些公司会对邮件内容的大小有限制(因为这种方式是将图片一起发送的)

@Test
    void test() throws MessagingException {
        //创建复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //true表示这是封复杂邮件
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setSubject("这是一封测试邮件");
        helper.setSentDate(new Date());
        helper.setFrom("1135XXX@qq.com");
        helper.setTo("8077XXX@qq.com");

        helper.setText("<p>这是封测试邮件,包含两张图片,如下</p><p>第一张图片:</p><img src='cid:dog1'/><p>第二张图片:</p><img src='cid:dog2'/>",true);
        helper.addInline("dog1",new FileSystemResource(new File("C:\\Users\\user\\Pictures\\Saved Pictures\\dog.jpg")));
        helper.addInline("dog2",new FileSystemResource(new File("C:\\Users\\user\\Pictures\\Saved Pictures\\dog2.jpg")));
        mailSender.send(mimeMessage);
    }

这里的邮件 text 是一个 HTML 文本,里边涉及到的图片资源先用一个占位符占着,setText 方法的第二个参数 true 表示第一个参数是一个 HTML 文本。

setText 之后,再通过 addInline 方法来添加图片资源。

最后执行该方法,发送邮件,效果如下:

在这里插入图片描述

在公司实际开发中,第一种和第三种都不是使用最多的邮件发送方案。因为正常来说,邮件的内容都是比较的丰富的,所以大部分邮件都是通过 HTML 来呈现的,如果直接拼接 HTML 字符串,这样以后不好维护,为了解决这个问题,一般邮件发送,都会有相应的邮件模板。最具代表性的两个模板就是 Freemarker 模板和 Thyemeleaf 模板了。

使用 Thymeleaf 作邮件模板
推荐在 Spring Boot 中使用 Thymeleaf 来构建邮件模板。
因为 Thymeleaf 的自动化配置提供了一个 TemplateEngine,通过 TemplateEngine 可以方便的将 Thymeleaf 模板渲染为 HTML ,同时,Thymeleaf 的自动化配置在这里是继续有效的

1.引入 Thymeleaf 依赖:

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

2.在 resources/templates 目录下创建 Thymeleaf 邮件模板

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    欢迎<span th:text="${username}"></span>加入XX大家庭,你的入职信息如下:
<table border="1">
    <tr>
        <td>姓名</td>
        <td th:text="${username}"></td>
    </tr>
    <tr>
        <td>职位</td>
        <td th:text="${position}"></td>
    </tr>
    <tr>
        <td>薪资</td>
        <td th:text="${salary}"></td>
    </tr>
</table>
    <div style="color: #ff1a0e">一起努力创造辉煌</div>
</body>
</html>

3.发送邮件:

//模板引擎,可自动渲染
    @Autowired
    TemplateEngine templateEngine;

    @Test
    void test() throws MessagingException {
        //创建复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //true表示这是封复杂邮件
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        Context context = new Context();
        context.setVariable("username","虎啊兄弟");
        context.setVariable("position","Java攻城狮");
        context.setVariable("salary",9999999);
        String mail = templateEngine.process("mail", context);
        System.out.println(mail);

        helper.setSubject("这是一封测试邮件");
        helper.setText(mail,true);
        helper.setSentDate(new Date());
        helper.setFrom("1135827553@qq.com");
        helper.setTo("8077XXX@qq.com");
        mailSender.send(mimeMessage);
    }

在这里插入图片描述
使用 Freemarker 作邮件模板
1.引入 Freemarker 依赖

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

2.在 resources/templates 目录下创建一个 mail.ftl 作为邮件发送模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    欢迎<span>${username}</span>加入XX大家庭,你的入职信息如下:
<table border="1">
    <tr>
        <td>姓名</td>
        <td>${username}</td>
    </tr>
    <tr>
        <td>职位</td>
        <td>${position}</td>
    </tr>
    <tr>
        <td>薪资</td>
        <td>${salary}</td>
    </tr>
</table>
    <div style="color: #ff1a0e">一起努力创造辉煌</div>
</body>
</html>

3.将邮件模板渲染成 HTML ,然后发送即可

	@Test
    void text() throws MessagingException, IOException, TemplateException {
        //创建复杂邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        //true表示这是封复杂邮件
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

        //构建Freemarker的基本配置
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_29);
        //配置模板位置
        ClassLoader loader = MailApplication.class.getClassLoader();
        configuration.setClassLoaderForTemplateLoading(loader, "templates");
        //加载模板
        Template template = configuration.getTemplate("mail2.ftlh");
        StringWriter out = new StringWriter();
        Map<String, Object> map = new HashMap<>();
        map.put("username", "虎啊兄弟");
        map.put("position", "JAVA高级攻城狮");
        map.put("salary", 99999);
        //模板渲染,渲染的结果将被保存到 out 中 ,将out 中的 html 字符串发送即可
        template.process(map, out);

        helper.setText(out.toString(),true);
        helper.setSubject("这是一封测试邮件");
        helper.setFrom("11358XXX@qq.com");
        helper.setTo("8077XXX@qq.com");
        helper.setSentDate(new Date());
        mailSender.send(mimeMessage);
    }

需要注意的是,虽然引入了 Freemarker 的自动化配置,但是我们在这里是直接 new Configuration 来重新配置 Freemarker 的,所以 Freemarker 默认的配置这里不生效,因此,在填写模板位置时,值为 templates 。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值