1 SpringBoot
1.1 关于
-
约定大于配置:简化M-V-C开发模式;使编码、配置、部署、监控变得简单
-
为基于Spring的开发提供更快的体验,创建可以独立运行的Spring应用
-
直接内嵌Servlet容器:Tomcat或Jetty服务器,可以打包成war文件,也可以以jar包形式独立运行:java -jar xxx.jar
-
提供pom.xml文件来简化Maven配置:starters中自动依赖与版本控制;尽可能的根据项目依赖来自动配置Spring框架
-
提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查;提供http,ssh,telnet等对运行时项目进行监控
-
开箱即用,无需xml文件配置,不借助代码生成来实现;还可以修改默认值来满足特定需求
-
能与主流框架集成;其他大量项目如SpringCloud都基于SpringBoot
-
-
缺点:
-
依赖太多;缺少服务的注册和发现等解决方案;缺少监控集成方案,安全管理方案
-
1.2 主程序/启动类
-
spring-boot-starter:场景启动器,用于帮助导入各个场景所需依赖的组件(jar),如spring-boot-starter-web,aop,cache,data-jdbc,data-jpa,data-redis,data-rest,freemarker,jdbc,json,test等;这样免去了在xml中定义bean对象
-
@SpringBootApplication:标注启动类,该程序是一个SpringBoot应用
-
@SpringBootConfiguration:标注配置类,同Spring的@Configuration
-
@EnableAutoConfiguration:开启自动配置,其原理(核心):(为什么配置文件能定义那么多属性,为什么它们能生效)SpringBoot启动时,加载主配置类,开启自动配置功能;利用@Import导入各种组件,包括配置类
-
@Import({AutoConfigurationImportSelector.class}):导入组件,一个包含了众多自动配置类xxxAutoConfiguration的List;如AopAutoConfiguration,开发无需再手动编写配置和注入这些组件的工作;组件从jar包下MATA-INF/spring.factories中读取@EnableAutoConfiguration标注的组件,如视图解析器等
-
@ConditionalOnXXX:扩展的Spring底层注解@Conditional,用于判断条件是否满足,满足则该类可用;如在配置类上的@ConditionalOnWebApplication,当为web应用时本配置类有效;(配置debug=true可以在日志中看生效了的配置类)
-
配置类的作用:创建bean,即为容器添加各种组件(@Bean),这些组件的属性来自对应的properties类,这些属性就对应配置文件中的每一个key(同自定义配置后,映射到自定义类中属性一样)
-
-
@AutoConfigurationPackage:自动配置包;其下@Import({Registrar.class});两个注解就把启动类所在包及子包中的组件扫描到容器,所以特别注意其位置
-
-
-
main():运行SpringApplication.run();启动流程:
-
run()中调用构造器,初始化SpringApplication实例:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // main方法中的args参数即主类;可接收命令行启动时添加的参数 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 确认当前应用程序的类型;一般都是Servlet this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 加载ApplicationContextInitializer初始化类获取Spring工厂实例 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 加载ApplicationListener类,监听类 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 获取main方法所在类 this.mainApplicationClass = deduceMainApplicationClass(); } private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
-
执行run():
-
从同路径下获取SpringApplicationRunlisteners,启动它们的starting()
-
配置环境ConfigurableEnvironment;回调listener的environmentPrepared()
-
创建打印Banner,即控制台输出的那个图案
-
创建ApplicationContext,即IoC容器;初始化容器
-
-
-
javax.servlet.ServletContainerInitializer:封装web.xml
-
这个类会在web容器启动阶段被回调,可以在onStartup方法里做一些servlet、filter、listener的注册等操作。springboot在META-INF/services下配置了这个类,让整个web容器启动后可以找到并启动这个类,即它把web.xml封装成了配置类
-
1.3 配置文件与配置类
-
全局配置文件:.yml或.properties;修改SpringBoot自动配置的默认值
-
语法:
-
以数据为中心,比xml(标签)和json更适合做配置文件
-
默认无引号,如果加了双引号,特殊字符依旧有效,如/n换行,而单引号则直接原样输出
-
集合/数组:元素前使用-列举;或者数组用逗号分隔开的枚举,集合再用[]包含数组
-
占位符:
-
配合使用预定义方法,如${random.int}或${random.uuid}随机数
-
引用已经存在的key,如${key};若key不存在,则key为默认值;使用:可自定义默认值;如${key: value}
-
-
properties配置文件可能乱码,与IDEA默认UTF8编码不符,需勾选file encoding下的transparent native-to-acscii convension即转码本地到ASICC
-
-
使用配置:
-
@Value():Spring单一注入,用于属性或setter()上;需要一个一个的注入;支持SpringEL;适用于业务中获取某个值
-
@ConfigurationProperties(prefix=“配置文件中属性”):SringBoot把配置和Bean相关联来实现;修改SpringBoot自动配置的默认值有效就是此理,所以定义值后能自动注入;支持:
-
批量注入;适用于映射整个Bean
-
松散绑定,如userName与user-name一致
-
JSR303数据校验,如@Validated开启校验支持后,属性上可使用@Null,@Min,@Email等控制输入规范
-
对象、集合等复杂类型如对象封装
-
-
-
添加组件:
-
@ImportResource(loactions={"classpth:bean.xml}):加载Spring的配置文件
-
@Configuration:推荐使用配置类;配合@Bean
-
-
多环境配置:
-
文件名前缀固定的前提下,不同标识指定不同环境,如application-test.yml与application-dev.yml;默认的application.yml中spring.profiles.active=dev激活指定文件
-
yml支持一个文件中的配置块:---分开的配置,如配置两个server.port;每个块中单独指定激活的文件
-
命令行指定激活文件:edit configuratuin设置中设置program arguments值为--spring.profiles.active=dev;或者启动最终打包项目时的启动命令后加此命令
-
-
多数据源配置:
-
dataSource下配置多个数据源的属性信息
-
分别编写两个配置类对应两个数据源,mapper包扫码各自的,service层调用各自的dao层
# thymeleaf配置:即ThymeleafAutoConfiguration配置类 #server: # servlet: # context-path: test: id: 1 name: user max: true # 占位符:${SpEL},当SpEL不存在,则将他解析为等值的字符串,后接冒号可自定义默认值 # name: zg${test.id}h #=========================================== # 多profile配置文件 # 默认为此文件;当需要切换环境如测试环境,开发环境,生产环境时,可定义多个profile(注意命名) # 然后在此指定使用目标配置文件激活: # (另一种激活方法:命令行参数:在edit Configurations-spring boot里面的Program arguments中输入命令:--spring.profiles.active=dev) # (另另一种激活方法:虚拟机参数:在edit Configurations-spring boot里面的VM options中输入:-Dspring.profiles.active=dev) # (另另另一种激活方法:命令行参数:在项目打包后,在命令窗口启动项目时,在最后添加同上的命令行参数命令) #spring: # profiles: # active: dev # yml文件还有文档块写法:---隔开 #server: # port: 8080 #spring: # profiles: # active: dev #--- #server: # port: 8081 #spring: # profiles: dev #--- #server: # port: 8082 #spring: # profiles: prod #================================================================ # 配置文件加载位置: # springboot的4个默认有效位置:优先级依次降低;内容互补,高优先级配置会覆盖低优先级中存在的,而不是有高优先级就不加载低优先级的 #-file:/config/,即放在与src同级目录config中的文件 #-file:/,即放在与src同级的文件 #-classpath:/config,即放在resources下config下的文件 #-classpath:/,即放在resources下的文件,项目最初始时的默认模样 # 指定任意文件为配置文件:配置spring.config.location=绝对路径;运维常用此方式,在打包项目后启动命令加此命令;(命令行参数不仅这些,还可以是任意配置,如加--server.prot=8081) #项目内部配置 # 加载指定的配置文件:@PropertySource(value="classthpath:xxx");application.yaml/properties是默认能识别的配置文件,自定义的不能识别,需要手动指定加载 #spring: # config: # location: #项目外部配置:参考官方文档 logging: file: name: log/springboot.log spring: messages: basename: i18n/message #禁用模板引擎的缓存,默认为true;开发时关闭,应用上线时开启以降低数据库压力 thymeleaf: cache: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/dbtest?useUnicode=true&characterEncoding=utf-8 username: root password: 123456 #使用自定义数据源druid type: com.alibaba.druid.pool.DruidDataSource # schema: # - classpath: sql/user.sql # schema用来从IDE中生成数据库中的表:user.sql是位于templates/sql下的一张表 #黄色背景的说明是自定义属性,不能与Bean绑定;代码提示出来的都是默认的hikari,dbcp2,tomcat连接池; #使用自定义的Druid,需要向容器添加映射组件@Configuration #如与DataSource相关的DataSourceProperties中存在上面无背景的属性,不存在下面黄色背景的属性;需要自定义映射 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 # 文件上传:单个文件最大大小;上传的文件总大小 servlet: multipart: max-file-size: 200MB max-request-size: 500MB #初始大小;最小;最大;连接等待超时时间(毫秒);检测(需要关闭的空闲连接)一次的间隔,;个连接在池中的最小生存时间 #检测数据库连通性(不同数据库的validationQuery值不同,返回一条不为空的SQL);获取连接时测试连接是否有效(都为true时,testOnBorrow优先级高) #是否打开PSCache(PreparedStatements 缓存);打开时设置缓存大小;监控统计拦截的filters(监控统计stat,防御SQL注入wall,日志log4j(2.0版本后不要这个了?)) mybatis: config-location: classpath:mybatis/config/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml
-
1.4 数据访问
-
SpringBoot默认的DataSource:org.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource
-
自定义DataSource:配置返回DataSource的Bean,return dataSourceProperties.initializeDataSourceBuilder().build()
-
Druid:引入依赖;配置spring.dateSource.type为druid(屏蔽默认的Tomcat.jdbc);配置该类Bean;配置它的数据监控/拦截器
-
Mybatis:MybatisAutoConfiguratuin;@Mapper定义或@MapperScan扫描
-
Spring Data JPA:
-
@Entity标注映射到数据库表的Bean;@Table映射表名;@Id标注Id;@GeneratedValue标注主键增长策略
-
编写Dao接口:继承JpaRepository;已经有了基本的CURD方法
-
复杂的CURD及SQL语句编写
-
-
1.5 国际化
-
文件:i18n目录下配置默认的、en_US、zh_CN三个文件;页面切换语言环境后会自动适配对应文件中的值
-
ResourceBundleMessageSource:文件管理;MessageSourceAutoConfiguration:配置类
-
获取:前端使用#{key}
-
Locale:区域信息对象;默认区域信息解析器根据请求头中环境信息来设置;可自定义
1.6 WebMvcAutoConfiguration
-
静态资源:
-
可直接页面访问;如导入依赖bootstrap
-
静态资源的访问路径:classpath/下的/resources或/static或/public下(优先级依次降低),或/webjars/下即maven依赖的META-INF/resources/webjars下的jar包可访问,因此pom也可导入前端jar包且页面访问时,打开了源码;可配置文件中自配置资源访问路径
-
引用:如引用css文件,< link th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}">
-
首页/欢迎页:源码getIndexHtml()设置为"访问路径+index.html"
-
-
动态资源/模板引擎:
-
位于/templates下的文件,需要编译(模板渲染)而不能直接打开;只能通过Controller访问且默认为index.html
-
1.7 拦截器
-
定义:实现HandlerInterceptor的preHandle(),postHandler()等方法,如登录拦截器,在登录之前的判断等
-
注册:实现HandlerInterceptorAdapter的addInterceptor(拦截器),addPathPatterns("拦截路径"),excludePathPatterns(”忽略的路径“)等
1.8 控制器
- springboot自动配置了:
- ViewResolver视图解析器,它根据方法返回值映射到视图对象View,View决定如何渲染(重定向,转发)
- Converter转换器:页面数据提交都是文本(如true),需要类型转为Integer,boolean等
- Formatter格式化器:格式化时间等(包括类型转换和格式化)
- 可以自定义各种器,如请求处理器,类型转换器(如前端form表单数据转换到MVC中的对象),视图解析器,格式化器,网页图标,消息转换器(如转换http请求和响应),外部数据初始化以及扩展MVC等:实现ViewResolver,然后配置组件@Bean
- SpringMVC扩展:@Configuration配置类代替配置文件;即自定义MVC功能,同Spring中定义配置类一样;特殊的,@Configuration标注类后,实现接口WebMvcConfigurer,以此自定义SpringMVC的配置类功能,如自定义视图解析器;不能添加@EnableWebMvc,它会让默认配置失效,即全面接管了SpringMVC
- restful风格CURD:以请求方式区别,且不体现请求动作如add:user(get),user(post),user/{id}(put),user/{id}(delete)
- 普通CURD:getUser、addUser?xxx、updateUser?id=xxx、deleteUser?id=xxx
- SpringBoot返回资源页面规则:prefix:templates/,suffex:/.html,方法返回值拼接在中间
- 针对post请求乱码问题:SpringMVC提供了过滤器(SpringBoot不同):CharacterEncodingFilter,配置filter
@Controller
public class SpringMVCController {
@Autowired
private JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/hello")
public String hello() {
return "hello world!";
}
// 如果不写方法,则默认访问的是static下的index.html
// @GetMapping({"/", "/login.html"})
// public String getIndex() {
// return "login";
// }
@GetMapping("/user")
public String toLogin() {
return "login";
}
@PostMapping("/login")
public String login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
Map<String, String> message) {
if (!StringUtils.isEmpty(userName) && "123456".equals(password)) {
return "user/main";
}
message.put("message", "用户名或密码错误,请检查!");
return "ymldefinetest";
}
/**
* 使用原生JDBC连接和操作数据库:查询所有后直接以文本显示在页面上
*/
@ResponseBody
@GetMapping("/users")
public List<Map<String, Object>> queryUser() {
String sql = "select * from user";
List<Map<String, Object>> users = jdbcTemplate.queryForList(sql);
return users;
}
// 文件上传:由于引入了spring-security,post请求会被拦截,页面403;暂时未处理
// 批量上传,将参数改为数组,命名和上传功能放到对该数组的循环中
@ResponseBody
@PostMapping("/upload")
public String uploadFile(HttpServletRequest request, MultipartFile multipartFile) { // 注意参数名和前端中name值一致
// 指定文件上传后在服务器的存储路径
// 创建upload文件夹
String direction = request.getServletContext().getRealPath("/upload");
File file = new File(direction);
if (!file.exists()) {
file.mkdirs();
}
// 指定文件保存在服务器时的名称,判重
String fileSuffix = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf("."));
String fileName = UUID.randomUUID().toString() + fileSuffix;
// 创建文件
File files = new File(file + "/" + fileName);
// 上传
try {
multipartFile.transferTo(files);
} catch (IOException e) {
e.printStackTrace();
return "上传失败!";
}
return "上传成功!";
}
// 会话管理之自设置Cookie:服务器发送到浏览器并保存在浏览器端的一小段数据;浏览器下次访问此服务器时自动携带该cookie;仅存字符串类型数据
@GetMapping("/cookie")
public String setCookie(HttpServletResponse response) {
// 创建cookie,设置cookie的有效范围(访问哪些路径时有效),生存时间;发送cookie等
Cookie cookie = new Cookie("cookie", "cookie");
cookie.setPath("/somePath");
cookie.setMaxAge(600);
response.addCookie(cookie);
return "/cookies";
}
// 会话管理之自设置Session:服务器端记录客户端信息;安全但增加了内存消耗;
// 浏览器cookie中保存sessionId(页面上显示为jsessionid),发送到服务器匹配session;可存任意类型数据
@GetMapping("/session")
@ResponseBody
public String getSession(HttpSession session) {
String id = (String) session.getAttribute("id");
String name = (String) session.getAttribute("name");
return "/sessions";
}
}
1.9 错误处理
-
ErrorMvcAutoConfiguration:错误处理的自动配置类;添加的组件中设置了响应规则,如产生错误后去到页面template/error下,DefaultErrorViewResolver映射到指定错误页面,设置错误响应页面的样式;信息包含了时间戳、状态码、错误提示、异常对象及信息等;错误页面不在templates下,则查找statics下,都没有则得到SpringBoot默认错误提示页面(一段写在后端的页面代码)
-
错误处理:
-
使用默认规则:即错误页面放于/error下,命名以4或5开头;优先匹配精确的状态码如404.html
-
自定义异常处理器:@ControllerAdvice标注的类,@ExceptionHandler标注错误处理逻辑,包括写入异常信息(异常信息也可自定义实现Exception),返回的视图等;实现DefaultErrorAttributes的getErrorAttributes()自定义错误属性,可可以包含异常信息
-
1.10 日志记录
-
实现类logback + 调用顶层抽象slf4j
-
导入spring-boot-starer-logging
-
日志级别:.trace()、.debug()、.info()默认、.warn()、.error();仅打印该级别及以后的信息
1.11 单元测试
-
@RunWith(SpringRunner.class)与@SpringBootTest:可导入Junit做单元测试
1.12 部署
-
pom.xml中添加插件< build>< plugin>spring-boot-maven-plugin,即将应用打包为可执行jar
-
执行maven-package,最终打包在target下;执行java -jar xxx.jar启动
1.13 缓存
-
@EnableCaching开启缓存(bean需要实现可序列化)
-
Spring注解;使用注解时,会触发一个post processor,这会扫描每一个bean,查看是否已经存在注解对应的缓存。如果找到,则会自动创建一个代理拦截方法调用,使用缓存的bean执行处理
-
-
使用SpringBoot Data Redis:lettuce替换了jedis,集成Redis
-
配置各种属性(实质就是RedisTemplate的属性)即可
spring: redis: host: 127.0.0.1 port: 6379
-
开发中自定义RedisTemplate,如自定义序列化机制;然后编写工具类RedisUtil,注册该类即可,而不是使用原生API
-
-
@Cacheable(value=“sampleCache”), key="")
-
用于方法上;调用方法时会从value为sampleCache的缓存(缓存本质是一个map)中查询key为id的值,如果不存在,则执行实际的方法(即查询数据库等服务逻辑),并将执行的结果存入缓存中,否则返回缓存中的对象。key默认是只有一个参数时方法的参数,多参数时需指定
-
1.14 定时器/任务调度
-
core:设置定时执行的表达式,如 0 0/5 * * * ?每隔5分钟执行一次
-
zone:执行时间所在时区
-
fixedDelay / fixedDelayString:固定延迟时间执行,上个任务完成后,延迟多久执行
-
fixedRate / fixedRateString:固定频率执行,上个任务完成后,多久后开始执行\
-
initialDelay / initialDelayString:初始延迟时间
-
定时任务表达式:秒、分、时、天、月...,取值为各自默认范围,如秒为0-59
-
2 * * * * ?:每一分钟的第2秒执行;其他同理,如2 35 18 * * ?为每天的18时35分2秒时执行
-
0/2 * * * * ?:等步长序列,从0秒开始,每隔2秒执行,即步长为2;如1/15为从1秒开始,1,16,31执行;其他同理
-
1-2 * * * * ?:范围,每分钟的1-2秒内执行
-
1,2 * * * * ?:枚举,每分钟的1秒和2秒执行
-
*匹配任意,?匹配占位
-
-
Spring3.0后自带的Scheduled定时任务器
-
@EnableScheduling:开启对定时任务的支持
-
@Scheduled:在方法上声明需要执行的定时任务
@Component @Configuration @EnableScheduling public class ScheduleUtil { /** * 测试定时任务:每隔2秒执行 * 由控制台输出,可知每隔两秒执行一次方法 */ @Scheduled(cron = "0/2 * * * * ?") public void testScheduled() { System.out.println("自带触发定时器任务:" + new Date()); } }
-
-
第三方定时任务调度框架Quartz(复杂)
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; /** * 定时任务类:第三方任务调度框架Quartz */ public class ScheduleQuartzUtil implements Job { /** * 唯一重写的方法:任务触发时执行 * @param jobExecutionContext * @throws JobExecutionException */ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("第三方触发定时器任务:" + new Date()); } // Quartz的简单使用;与SpringBoot整合参见配置类 public static void main(String[] args) throws SchedulerException { // 1. 创建Job对象,即定义定时任务的内容 JobDetail jobDetail = JobBuilder.newJob(ScheduleQuartzUtil.class).build(); // 2. 创建Trigger对象,即定义触发时间,等同于@Schedule()的cron // Quartz提供了简单的时间定义封装SimpleScheduleBuilder Trigger trigger = TriggerBuilder.newTrigger().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(2)).build(); // 复杂的自定义定时时间CronScheduleBuilder // Trigger trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")).build(); // 3. 创建Scheduler对象,联结定时任务和时间 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.scheduleJob(jobDetail, trigger); // 4. 启动定时器 scheduler.start(); } } /** * 第三方定时调度任务框架Quartz的复杂应用:整合SpringBoot */ @Configuration @EnableScheduling public class QuartzConfig { // 1. 创建Job对象,即定义定时任务的内容 @Bean public JobDetailFactoryBean getJobDetailFactoryBean() { JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); factoryBean.setJobClass(ScheduleQuartzUtil.class); return factoryBean; // 注意此处,ScheduleQuartzUtil的创建交给了Factory,其源码采用的是反射机制构建,即没有被Spring容器管理,容器中不存在该实例 // 因此当此config注入其他Bean即有@Autowired属性时,报空指针异常; // 解决方案:重写创建定时器实例的类AdaptableJobFactory的方法,在返回该实例之前,手动将其注入到Spring容器 // @Component("myAdaptableJobFactory") // class MyAdaptableJobFactory extends AdaptableJobFactory { // @Autowired // private AutowireCapableBeanFactory autowireCapableBeanFactory; // 此对象帮助将自定义Bean注入到容器 // @Override // protected Object createJonInstance(TriggerFiredBundle bundle) throws Exception { // // return super.createJobInstance(bundle); // 修改此处为下面的 // Object object = super.createJobInstance(bundle); // this.autowireCapableBeanFactory.autowireBean(object); // return object; // } // } // 然后在第三步,即创建Scheduler对象时,传入此实例,即factoryBean.setTriggers(myAdaptableJobFactory); } // 2. 创建Trigger对象,即定义触发时间,等同于@Schedule()的cron // Quartz提供了简单的时间定义封装SimpleScheduleBuilder @Bean public SimpleTriggerFactoryBean getSimpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) { SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); factoryBean.setJobDetail(jobDetailFactoryBean.getObject()); // 关联定时任务 factoryBean.setRepeatInterval(2000); // 设置执行的毫秒数 factoryBean.setRepeatCount(5); // 设置执行的次数 return factoryBean; } // 复杂的自定义定时时间CronScheduleBuilder @Bean public CronTriggerFactoryBean getCronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) { CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean(); factoryBean.setJobDetail(jobDetailFactoryBean.getObject()); factoryBean.setCronExpression("0/2 * * * * ?"); return factoryBean; } // 3. 创建Scheduler对象,联结定时任务和时间 @Bean public SchedulerFactoryBean getSchedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean) { SchedulerFactoryBean factoryBean = new SchedulerFactoryBean(); factoryBean.setTriggers(simpleTriggerFactoryBean.getObject()); // factoryBean.setTriggers(cronTriggerFactoryBean.getObject()); return factoryBean; } // 4. 启动定时器:开启@EnableScheduling }
1.14 前后端数据交互
-
常见数据接收和发送,参见SpringMVC
-
系统中发送http请求并传递json数据(常见场景为调用第三方请求并写入请求参数)
-
RestTemplate / HttpClient / OkHttpClient:同步的web客户端http请求工具,前者不再更新和维护;HttpClient常用于网络爬虫中,系统模拟浏览器发送请求获取数据;OkHttpClient高效低损
-
使用:注册Bean到容器即可,如@Bean public RestTemplate restTemplate() { return new RestTemplate(); }
-
请求及方法:
-
get请求无限制;post请求及方法:传递的参数如果用map类型,则必须是MultivalueMap
-
getForObject() / postForObject():获取对象;返回类型为对象
-
getForEntiry() / postForEntiry():获取对象,状态码,请求头信息等;返回类型为ResponseEntity封装的泛型对象
// 注意上面说的Get或Post指的时url指向的第三方请求的请求方式 public Map<String, object> getResult() { String url = "http://localhost:8080/add/..."; return restTemplate.getForObject(url, Map.class); // 后面可以指定请求需要的参数列表 } @GetMapping(...) public Map<String, object> getResult() { String url = "http://localhost:8080/add/..."; Map<String, Object> params = new HashMap<>(); ResponseEntity<HashMap> responseEntity = restTemplate.getForEntity(url, HashMap.class, params); // 后面可以指定请求需要的参数列表 HttpStatus statusCode = responseEntity.getStatusCode(); // 状态码 HttpHeaders headers = responseEntity.getHeraders(); // 请求头信息 return responseEntity.getBody(); } // 注意返回类型 @GetMapping(...) public User getResult() { String url = "http://localhost:8080/add/..."; MultivalueMap<String, Object> params = new LinkedMultivalueMap<>(); params.put("age", 1); params.put("name", "MyName"); // 注意,使用了MultivalueMap,则add()方法中的参数接收不能用User对象,而应该用@ReqeustParam即具体的属性参数 return restTemplate.postForObject(url, params, User.class); // 如果解析出来转换失败,可以使用ObjectMapper工具类 } @GetMapping(...) public User getResult() { String url = "http://localhost:8080/add/..."; // 如果add()中参数接收用了对象,则需要写入请求体并设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType("application/json"); User user = new User(); // 当使用json数据传递的时候,封装为JSONObject即可 user.setAge(age); user.setName(myName); HttpEntity<User> entityParam = new HttpEntity<User>(user, headers); // 写入 return restTemplate.postForObject(url, entityParam, User.class); // 注意传参不同 }
-
-
HttpClient应用之简单的网络爬虫(详情参见其他.md):
import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; /** * 网络爬虫-模拟浏览器发送请求 */ @Slf4j public class HttpClientUtil { public static void main(String[] args) throws IOException { // 1、创建HttpClient实例,模拟打开浏览器 CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); // 2、创建HttpGet实例;模拟发送请求一般都为get请求 HttpGet httpGet = new HttpGet("http://www.baidu.com"); // 3、执行get请求 CloseableHttpResponse response = closeableHttpClient.execute(httpGet); // 4、解析响应 if (200 == response.getStatusLine().getStatusCode()) { HttpEntity entity = response.getEntity(); // 获取返回实体 String content = EntityUtils.toString(entity, "utf8"); log.info("获取请求响应数据:" + content); // 实体内容以字符串返回 } response.close(); closeableHttpClient.close(); } }
-
-
2 模板引擎
2.1 Thymeleaf
-
依赖基于starter启动器;源码类ThymeleafProperties
-
语法:
-
导入名称空间:xmlns:th="http://www.thymeleaf.org";然后th:接管所有他html元素
-
/**
* thymeleaf模板引擎用法:
* requestMapping:一般都为资源文件HTML名称
* 引入th名称空间xmlns(xml namespace);HTML任意属性都可用th:替换
* 特殊的标签内容:th:utext,它不转义特殊字符;如"<h1>内容</h1>"
* 内容:用th:text得到并显示"<h1>内容</h1>",即<被转义,类似Java中的/后边的特殊字符被转义;用th:utext得到并显示"内容",即特殊字符<没被转义,解析为HTML了
* 片段包含:th:insert,th:replace;
* 遍历:th:each;
* 条件判断:th:if;th:unless;th:switch;th:case;
* 变量声明:th:object;th:with;
* 属性修改:th:attr;th:attrprepend;th:attrappend;th:value;th:href;th:src;。。。。。。
* 片段声明:th:fragment;
* 移除:th:remove;
* 配合表达式取值:
* ${...}:变量表达式
* 获取变量值;调用方法;
* 使用内置基本对象如#ctx,#vars,#locale,#request,#response,#session,#servletContext
* 使用内置工具(类)对象如#strings,#numbers,#dates...等工具类
* 另一种获取方式:[[]]或[()]转义字符行内转义,如<span th:text="${user"}></span> 等同于<span>[[${user}]]</span>
* *{...}:变量表达式:${...}的补充使用
* 如${session.user}获取user,${session.user.name}获取user中的name
* 而使用*{name}也可以获取user中的name(但注意作用域,在${session.user}所在标签及字标签内),即此时*等同于${session.user}
* #{...}:获取国际化内容
* @{...}:定义URL
* 参数列表用小括号包裹,key-value形式,逗号隔开,value还可用变量表达式获取;若以/开头,表示路径为当前项目下
* ~{...}:片段引用表达式:尤其是引用公用的fragment
* 字面量:字符串,数字,布尔值,null
* 文本操作:字符串拼接:+;字符串替换:|...|
* 数学运算:+,-,*,/,%
* 布尔运算:and,or,!,not
* 比较运算:>,<,==,!=等,及gt,lt,ge,le,eq,ne
* 条件运算:if ? then :else;value ? defaultValue
*
*/
@Controller
public class ThymeleafUsageController {
// 相应处理:一种返回页面路径+名称,方法参数为Model,map,RedirectAttributes等类型
@GetMapping("/prompt")
public String promptSuccess(Map<String, Object> map) {
map.put("text", "<h1>提示:成功!</h1>");
map.put("utext", "<h1>提示:成功!</h1>");
map.put("users", Arrays.asList("user1","user2","user3"));
return "ymldefinetest";
}
// 另一种响应方式:HTML数据,即返回类型为ModelAndView或者Model,将页面路径+名称写入其中
@GetMapping("/prompt2")
public ModelAndView promptSuccess() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("text", "<h1>提示:成功!</h1>");
modelAndView.addObject("utext", "<h1>提示:成功!</h1>");
modelAndView.addObject("users", Arrays.asList("user1","user2","user3"));
modelAndView.setViewName("ymldefinetest");
return modelAndView;
}
/**
* 与http请求和响应相关的数据
* @param request
* @param response
*/
@RequestMapping("/http")
public void http(HttpServletRequest request, HttpServletResponse response) {
// request get...
System.out.println(request.getMethod());
System.out.println(request.getServletPath());
Enumeration<String> headerNames = request.getHeaderNames();
// response set...
response.setContentType("text/html;charset=utf-8");
}
}
3 myblog项目-未整理
-
以后写项目,可以不用管前端了,直接百度bootstarap模板后导入
-
项目功能规划:
-
后端管理:管理员/用户登录,博客管理(增删查改),分类管理(增删查改),标签管理(增删查改)
-
前端展示:首页(博客分页列表,top标签,top分类,最新推荐,博客详情),分类(分类列表,分类下的博客列表,博客详情),标签(标签列表,标签下的博客列表,博客详情),归档(按年度归档展示博客列表,博客详情)
-
-
项目搭建:
-
选择Spring initializr,选择预先加载的依赖:DevTools热部署,Web(Spring web),thymeleaf,mysql,data jpa等
-
检查依赖是否正确;配置application.yml文件;检查单元测试(测试类中引入junit来做单元测试)
-
前端框架SemanticUI
-
遇到的坑:
-
MySQL的driver-class-name改了:com.mysql.cj.jdbc.Driver
-
启动SpringBoot报错MySQL存在问题:原因可能是前面选的MySQL下载的版本太高;需要在pom文件中指定version
-
启动SpringBoot报错Error creating bean with name 'inMemoryDatabaseShutdownExecutor:还不清楚错误原因;解决方案是在启动入口的@SpringBootApplication注解后面加上(exclude = {DataSourceAutoConfiguration.class })
-
-
彩蛋-自定义启动时图像:网上搜索SpringBoot启动时Banner生成
-
异常:
-
SpringBoot中有static和templates两个路径,分别用于存放静态页面和动态页面(模板如themeleaf等);静态页面可直接访问,动态页面则会请求后台;例如分别在其下建hello.html文件;输入localhost:8080/hello.html,则始终返回static下的文件;使用controller访问即输入localhost:8080/hello,static下也可访问
@GetMapping("/hello") public String hello() { return "hello.htnl"; }
但templates下的则报错;原因:SpringBoot默认推荐thymeleaf,访问动态资源需要请求后台,即框架会分配一个ViewResolver去解析,因此需要书写controller,注意return中没有.html后缀;且访问的path不能与视图名重合,否则会抛出Circular view path异常,如此处不能写为@GetMapping("/hello")
@GetMapping("/hi") public String hello() { return "hello"; }
-
记一个遇到的访问不了templates下文件的巨坑:页面一直报错:
Writelabel Error Page
This application has no explicit mapping for /error,so you are seeing this as a fallback.
网上找了的很多方案(基本都是spring boot启动类因该放在controller外层的问题,于我不适用);最后发现代码中提示:Cannot resolve MVC View 'xxx';不能处理视图,那大概就是引入的thymeleaf依赖有冲突或不匹配;检查代码,引入的dependency没有问题,但这个地方的version和layout-dialect的version不匹配(下面是匹配的,不过我是直接将这两行代码删除了,可行)
<properties> <java.version>1.8</java.version> <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version> <!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 --> <!-- thymeleaf2 layout1--> <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version> </properties>
-
-
定义错误页面、全局异常处理与日志处理:
-
404/500:在templates下新建/error目录用于存储自定义错误页面,页面起名404.html/500.html,Springboot会在后台发生异常时去寻找这个目录,并跳转到对应页面
-
自定义error:自定义标注为@ControllerAdvice的拦截器ExceptionHandler,拦截所有标注@Controller的控制器,使产生异常时跳转到自定义的error page
-
自定义Exception:继承RuntimeException,此异常可由一些注解标注为何种异常,如@ResponseStatus(HttpStatus.Not_Found)标注为404;在出现异常的地方throw new MyException()
-
AOP方式以切面@Aspect拦截请求:配合@Pointcut,@Before,@After等,在日志输出一些信息如url,ip,args等,涉及的类HttpServletRequest;或者事务处理,缓存,权限校验等
-
-
将写好的前端静态页面整合进来:因为项目使用的thymeleaf模板引擎(默认不支持JSP),需要修改适配:
-
fragment:将公用部分(包括JS)提取到模板,在需要的页面引用
-
由此生成的网页都是动态网页,需要服务器启动后才能访问(直接浏览器打开可看到不起作用)
-
一些前端插件:编辑器Markdown:推荐editor.md;内容排版typo.css:;动画animate.css:;代码高亮prism: ;滚动侦测waypoints:;平滑滚动:jquery.scrollTo:;目录生成Tocbot:;二维码生成qrcode.js
-
-
生成实体类:
-
使用jpa自动生成数据库表:(似乎需要配置idea到mysql的datasource????引入数据源,一直刷不出来数据表);曾经生成不了表,只能手动导入SQL
-
记录一个巨坑:
-
书写实体bean,并且@ManyToOne等一系列注解配置好数据表之间的关联关系后,启动springboot,正常情况是会在数据库中自动生成对应的数据表,包括中间表,有自联关系的,表字段也会体现出来;但是我的一直没有生成数据表,并且@Autowired注解标注的bean也在启动时报错找不到;查阅很多资料都说是路径问题导致扫描不到,但我确定我的路径没有问题。后来终于在网上找到答案:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}):作用是排除自动注入数据源的配置(取消数据库配置)!!!!如果只需要简单的启动运行,则客家exclude,否则需要在配置文件中配置DataSource;因此不需要括号中的内容,于是数据表能够生成,且自动注入的bean也能找到了!汗!!!!!
-
-
-
dao-service-repository:
-
各种数据操作CURD;定义Repository,方法命名严格遵守jpa规范;service使用CURD的结果作业务处理
-
一个坑:查询时,jpa规范方法名findXXX()如findOne(id),但Springboot版本原因,可能的方法是其他如getOne(id)
-
-
controller:
-
登录:用户合法验证,未登录用户拦截;输入非空验证;错误提示;密码MD5加密;登录成功与失败的页面跳转
-
博客管理,分类管理,标签管理,归档管理,关于我,前端博客详情,评论回复;
-
记一个坑:遇到这么个问题:解析三目运算符的时候,有id的(编辑)路径正常,没有id(即新增)的报错ip不存在
<!--注意这里访问到的是controller中post方式的/types--> <!--此处先判断id是否存在,存在则调用controller中的编辑,否则为新增;id的获取使用一个隐藏的input--> <!--th:action="*{id}==null ? @{/admin/types} : @{/admin/types/{id}(id=*{id})}"--> <!--但是不知道为什么这个三目元算符运行时会报错;现在复制了一份文件为types-update,让controller去到不同的路径--> <form action="#" method="post" th:object="${type}" th:action="*{id}==null ? @{/admin/types} : @{/admin/types/{id}(id=*{id})}" class="ui form"> <input type="hidden" name="id" th:value="*{id}"> <div class="required field"> <div class="ui left labeled input"> <label class="ui teal basic label">名称</label> <!--th:value="*{name}"在点击编辑的时候传递过来原始值并显示,其他时候为空,即新增--> <input type="text" name="name" placeholder="分类名称" th:value="*{name}"> </div> </div> ......
-
原先采用了复制一份页面分开写;后来找到原因:在Controller中,去到新增的请求中要实例化一个对象:
// 点击新增,去到添加type的页面 @GetMapping("/types/input") public String goToAddType(Model model) { model.addAttribute("type", new Type()); //这里需要添加这一行代码 return "admin/types-input"; } // ...... // 点击编辑去到修改Type页面 @GetMapping("/types/{id}/input") public String editType(@PathVariable Long id, Model model) { model.addAttribute("type", typeService.queryType(id)); return "admin/types-input"; }
-
-
项目打包与部署运行:
-
右侧maven中package,日志中可看到XXX-0.0.1-SNAPSHOT.jar的打包路径(一般都在target目录下)
-
复制此jar到任意目录下,运行命令java -jar jar包名来启动(当报错Unable to access jarfile FileEnDecrypt.jar时,可能环境变量配置有问题,jar包名使用绝对路径即可)
-
-
-
SpringBoot整合JSP:https://blog.csdn.net/hp_yangpeng/article/details/80521866