SpringBoot:用来简化Spring应用的初始搭建以及开发过程。
使开发人员不再需要定义样板式的配置。
- 简化Spring配置
- 简化Maven配置
- 内嵌Tomcat
- 集成其他框架:Redis,zookeeper,Cache,web,mybatis
- 进行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-mail | Java 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-neo4j | spring-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-starter | Core 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-narayana | Spring 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
默认的配置文件,有四个位置,顺序如下,优先级以此降低
- config/application.properties
- application.properties
- classpath:/config/application.properties
- 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中的配置:支持两种格式,properties
与yaml
。
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
一致。
如果需要使用gson
和fastjson
,先从spring-boot-starter-web
中移除jackson,然后引入gson
和fastjson
即可。
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());
}
}
gson
和fastjson
:先从spring-boot-starter-web
中移除jackson,然后引入gson
和fastjson
即可
<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 开发
静态资源处理
静态资源一共有五个默认位置,按照如下顺序优先级依次降低:
- classpath:/META-INF/ressources
- classpath:/resources
- classpath:/static
- classpath:/public
- / (即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;
}
}
拦截器
- 创建拦截器
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
原生的整合
- 创建项目
<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>
- 创建 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;
}
}
- 配置 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 和上面的一样
- 配置 Shiro 基本信息
接下来在 application.properties 中配置 Shiro 的基本信息:
- 第一行表示是否允许将sessionId 放到 cookie 中
- 第二行表示开启 shiro,对外环境下启用
- 第三行表示访问未获授权的页面时,默认的跳转路径
- 第四行表示开启 shiro,web环境下启用
- 第五行表示登录成功的跳转页面
- 第六行表示登录页面
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 ,密码则是项目启动时随机生成的字符串,可以从启动的控制台日志中看到默认密码
这个随机生成的密码,每次启动时都会变。对登录的用户名/密码进行配置,有三种不同的方式:
- 在 application.properties 中进行配置
- 通过 Java 代码配置在内存中
- 通过 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 有如下两种语法格式:
- Seconds Minutes Hours Day Month Week Year
- Seconds Minutes Hours Day Month Week
结构
corn 从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
各字段的含义
位置 | 时间域名 | 允许值 | 允许的特殊字符 |
---|---|---|---|
1 | 秒 | 0-59 | , - * / |
2 | 分钟 | 0-59 | , - * / |
3 | 小时 | 0-23 | , - * / |
4 | 日 | 1-31 | , - * / L W C |
5 | 月 | 1-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文件方式就行
- 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服务器的端口,465或587
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 。