SpringBoot
SpringBoot是简化Spring应用开发的一个框架,对整个Spring技术栈的一个大整合,并且可以快速的创建生产级别的Spring应用,可以直接“运行”它们,应为内嵌入了Tomcat
创建基础项目说明
项目创建方式一:使用Spring Initializr 的 Web页面创建项目
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
项目创建方式二:使用 IDEA 直接创建项目
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可,选择您要使用到的组件)
5、填写项目路径
6、等待项目构建成功
配置端口
1、在application.yml中配置
项目包规则
1、项目所有的包,要在主程序的同级目录下,一定要在同级目录下,否则识别不到
编写
package org.david.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "hello SpringBoot"; } }
@RestController与@Controller的区别
- @RestController:返回的是数据
@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,使用@RestController注解返回json数据不需要在方法前面加@ResponseBody注解了, 但使用@RestController这个注解,就不能返回jsp,html页面
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }
- @Controller:返回的是视图
这时候就有疑问了,那我到底是用@RestController这个注解,还是用@Controller这个注解,如果是前后端分离,就使用@RestController这个注解,后端返回数据,前端去得到后端返回的数据,达到前后端分离,如果不是前后端分,就使用@Controller这个注解
配置数据源
1、如果不配置数据源,他就会报错
2、配置数据源
#配置数据源 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/smdms username: liao password: 123456
配置MyBatis
#配置MyBatis mybatis: #配置MyBatis别名 type-aliases-package: org.david.demo.pojo #Mapper 配置路径 mapper-locations: classpath:mappers/*.xml
在主程序上开启@MapperScan注解
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; //通过使用@MapperScan可以指定要扫描Mapper类包的路径,并生成相应的实现类(@MapperScan主要就是扫描得到mybatis的Mapper,并最终在Spring容器种初始化) @MapperScan("org.david.demo.dao") @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
前后端分离
就是前端根据AJAX接受数据,后端返回JSON数据:如
package org.david.demo.controller; import org.david.demo.pojo.User; import org.david.demo.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; @RestController public class UserController { @Resource private UserService userService; @GetMapping("/get/{id}") public User get(@PathVariable int id){ User user=userService.getSingle(id); return user; } }
<html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="jquery-1.8.3.min.js"></script> </head> <body> <div id="info"></div> <button id="load">加载数据</button> </body> <script> $(function () { $("#load").click(function () { $.ajax({ //要写上完整的地址 url:"http://localhost:8000/get/1", dataType:"json", success:function (data) { $("#info").html(data.userCode+"\t"+data.userPassword); } }); }); }); </script> </html>
后端要放开限制,容许前端访问,有2种分为:局部,全局
不然会报错
局部设置:在每个Controller上添加@CrossOrigin注解,但是只作用在当前Controller中,所以要设置全局作用
package org.david.demo.controller; import org.david.demo.pojo.User; import org.david.demo.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; @CrossOrigin @RestController public class UserController { @Resource private UserService userService; @GetMapping("/get/{id}") public User get(@PathVariable int id){ User user=userService.getSingle(id); return user; } }
全局设置:让主程序继承WebMvcConfigurationSupport,然后重写addCorsMappings
package org.david.demo; import com.sun.nio.sctp.PeerAddressChangeNotification; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; //通过使用@MapperScan可以指定要扫描Mapper类包的路径,并生成相应的实现类(@MapperScan主要就是扫描得到mybatis的Mapper,并最终在Spring容器种初始化) @MapperScan("org.david.demo.dao") @SpringBootApplication public class DemoApplication extends WebMvcConfigurationSupport { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override protected void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } }
这时前端就可以访问后端数据了
前后端分离就是多个项目如:一个只存放后端代码,一个只存放前端代码,然后根据AJAX想连,一个发送数据,一个得到数据
日期问题
# 设置JSON的日期格式 jackson: date-format: yyyy-MM-dd # 设置上传的日期格式 mvc: date-format: yyyy-MM-dd
但是不会生效为什么,应为主程序类继承了一个WebMvcConfigurationSupport配置类,这个配置类中有一个addFormatters方法主要作用是用于增加转化器或者格式化器是一个空实现,所以不实现格式化,继承WebMvcConfigurationSupport配置类,要实现格式化,就要对他进行重写
protected void addFormatters(FormatterRegistry registry) { }
但是不继承WebMvcConfigurationSupport配置类,重写addCorsMappings就有前端访问的问题,这时就可以使用自定义配置类,去实现WebMvcConfigurer 接口,实现你要的方法,加上@Configuration注解,然后主程序类就去继承自定义配置类
package org.david.demo.config; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } }
package org.david.demo; import com.sun.nio.sctp.PeerAddressChangeNotification; import org.david.demo.config.CorsConfig; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; //通过使用@MapperScan可以指定要扫描Mapper类包的路径,并生成相应的实现类(@MapperScan主要就是扫描得到mybatis的Mapper,并最终在Spring容器种初始化) @MapperScan("org.david.demo.dao") @SpringBootApplication public class DemoApplication extends CorsConfig { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
文件上传
跟Spring一样,但是SpringBoot不需要配置上传组件,SpringBoot已经帮你加载了上传组件,
@PostMapping("/add") public Map<String,Object> getAdd(User user, MultipartFile file) throws IOException { if (!file.isEmpty()){ file.transferTo(new File("F:/文件"+user.getUserCode()+".jsp")); } Map<String,Object> map=new HashMap<>(); int insert = userService.insert(user); map.put("count",insert); return map; }
$(function () { $("#seve").click(function () { //它是为序列化表表单以及创建与表单格式相同的数据,提供传输便利。 //使用FormData将表单元素转为键值对,他是原生js不能使用Jquery获取表单对象 var form=new FormData($("form")[0]); $.ajax({ url:"http://localhost:8000/add", type:"post", dataType:"json", data:form, /** contentType:发送数据的格式 和 contentType 有个类似的属性是 dataType , 代表的是期望从后端收到的数据的格式,一般会有 json 、text……等 而 contentType 则是与 dataType 相对应的,其代表的是 前端发送数据的格式 默认值:application/x-www-form-urlencoded 代表的是 ajax 的 data 是以字符串的形式 如 id=2019&password=123456 使用这种传数据的格式,无法传输复杂的数据,比如多维数组、文件等 有时候要注意,自己所传输的数据格式和ajax的contentType格式是否一致,如果不一致就要想办法对数据进行转换 把contentType 改成 false 就会改掉之前默认的数据格式,在上传文件时就不会报错了。 */ contentType:false, //processData:处理数据 //默认情况下,processData 的值是 true,其代表以对象的形式上传的数据都会被转换为字符串的形式上传。而当上传文件的时候,则不需要把其转换为字符串,因此要改成false processData:false, success:function (data) { if (data.count>0){ alert("上传成功"); }else { alert("上传失败"); } } }); }); }
使用FormData 将表单元素转为键值对数据,他是原生js不能使用Jquery获取表单对象
@Configuration
@Configuration 用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
注册Baen
随着SpringBoot的流行,基于注解式开发的热潮逐渐覆盖了基于XML纯配置的开发,而作为Spring中最核心的bean当然也能够使用注解的方式进行表示
package org.david.demo.config; import org.david.demo.pojo.People; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeanConfig { /** *使用@Bean 注解表明people需要交给Spring进行管理 *未指定bean 的名称,默认采用的是 "方法名" + "首字母小写"的配置方式 * @Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>,作用为:注册bean对象 * * @return */ @Bean public People people(){ People people=new People(); //注入属性 people.setName("KK"); people.setAge(24); people.setGender("男"); return people; } }
package org.david.demo; import com.sun.nio.sctp.PeerAddressChangeNotification; import org.david.demo.config.CorsConfig; import org.david.demo.pojo.People; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; //通过使用@MapperScan可以指定要扫描Mapper类包的路径,并生成相应的实现类(@MapperScan主要就是扫描得到mybatis的Mapper,并最终在Spring容器种初始化) @MapperScan("org.david.demo.dao") @SpringBootApplication public class DemoApplication extends CorsConfig { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); /* String[] beanDefinitionNames = run.getBeanDefinitionNames(); for (String name:beanDefinitionNames){ System.out.println(name); }*/ //获取Bean People people=run.getBean(People.class); System.out.println(people); } }
但是这种注入属性不过灵活,建议使用配置文件yml,要在实体类上添加@ConfigurationProperties映射
通过注解@ConfigurationProperties(prefix="配置文件中的key的前缀")可以将配置文件中的配置自动与实体进行映射
package org.david.demo.pojo; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; @ConfigurationProperties("people") public class People { private String name; private int age; private String gender; public String getName() { return name; } public void setName(String name) { this.name = name; } ....
注意:使用@ConfigurationProperties方式可以进行配置文件与实体字段的自动映射,但需要字段必须提供set方法才可以,而使用@Value注解修饰的字段不需要提供set方法
Redis
我们目前的系统已经实现了广告后台管理和广告前台展示,但是对于首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。我们今天讨论第一种解决方案。
redis 是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。
Redis应用场景
1.热点数据加速查询(主要场景),如热点商品、热点信息等访问量较高的数据
2.即时信息查询,如公交到站信息、在线人数信息等
3.时效性信息控制,如验证码控制、投票控制等
4.分布式数据共享,如分布式集群架构中的session分离消息队列
数据结构介绍
Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)
注意:Redis采用键值对存储数据,key永远是String类型,五大数据类型指的是value部分
SpringBoot中使用Redis
添加Redis场景依然
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
编写Redis操作工具类
将RedisTemplate实例包装成一个工具类,便于对redis进行数据操作
package org.david.demo.tools; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; @Component public class RedisUtil { @Resource RedisTemplate<String,Object> redisTemplate; /** * 添加 * @param key * @param value */ public void set(String key,Object value){ //opsForValue:用于操作简单的数据类型 //opsForSet:用于操作set类型数据 //opsForHash:用于操作Map类型数据 //opsForList:用于操作liast类型数据 redisTemplate.opsForValue().set(key,value); } //set还可以设置变量值的过期时间,TimeUnit设置时间类型:TimeUnit.DAYS 天 TimeUnit.HOURS 小时 public void set(String key,Object value,long timeout){ redisTemplate.opsForValue().set(key,value,timeout, TimeUnit.HOURS); } /** * 获取 * @param key * @return */ public Object get(String key){ return redisTemplate.opsForValue().get(key); } /** * 根据key删除reids中缓存数据 * @param key * @return */ public boolean delete(String key){ return redisTemplate.delete(key); } /** * 更新 * 获取原来key键对应的值并重新赋新值。 * @param key * @param value */ public void getAndSet(String key, Object value){ redisTemplate.opsForValue().getAndSet(key,value); } /** * 判断key是否存在 * @param key * @return */ public boolean hasKey(String key){ return redisTemplate.hasKey(key); } /** *重新设置过期时间 * @param key * @param timeout */ public void expire(String key,long timeout){ redisTemplate.expire(key,timeout,TimeUnit.HOURS); } }
RedisTemplate的Bean
你可以使用SpringBoot创建的Bean,也可以自己创建一个RedisTemplate的Bean
//SpringBoot创建的Bean @Bean @ConditionalOnMissingBean(name = {"redisTemplate"}) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
package org.david.demo.config; import org.david.demo.pojo.People; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import java.net.UnknownHostException; @Configuration public class BeanConfig { /** * 我们可以自定义一个RedisTemplateBean来替换默认的Bean * @param redisConnectionFactory * @return * @throws UnknownHostException */ @Bean @ConditionalOnMissingBean(name = {"redisTemplate"}) public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<String, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
自定义视图信息对象
package org.david.demo.pojo; public class view<T> { /** * 返回的类型 */ private T data; /** * 状态码 */ private int code; /** * 成功信息 */ private String message; /** * 失败信息 */ private String error; public view(T data, int code, String message, String error) { this.data = data; this.code = code; this.message = message; this.error = error; } public T getData() { return data; } public void setData(T data) { this.data = data; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getError() { return error; } public void setError(String error) { this.error = error; } }
测试
package org.david.demo.controller; import org.david.demo.pojo.User; import org.david.demo.pojo.view; import org.david.demo.service.UserService; import org.david.demo.tools.RedisUtil; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @CrossOrigin @RestController public class UserController { @Resource private UserService userService; @Resource private RedisUtil redisUtil; @PostMapping("/login") public view login(String userCode, String userPassword, HttpServletResponse response){ User login = userService.login(userCode, userPassword); if (login!=null){ redisUtil.set(login.getUserCode(),login); Cookie cookie=new Cookie("token",login.getUserCode()); response.addCookie(cookie); } view<User> view=new view<>(login,login==null?520:200,login==null?"用户名或密码错误":"登录成功",login==null?"用户名或密码错误":null); return view; } }
控制器
控制器写法根Spring一样,去实现HandlerInterceptor接口,在重写preHandle方法,如:验证是否登录代码如下
package org.david.demo.filter; import org.david.demo.tools.RedisUtil; import org.springframework.web.servlet.HandlerInterceptor; import javax.annotation.Resource; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class SysInterceptor implements HandlerInterceptor { @Resource private RedisUtil redisUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Cookie[] cookies = request.getCookies(); //判断是否为空,不为空进入 if (cookies!=null){ //循环Cookie for (Cookie cookie:cookies){ //得到key String name = cookie.getName(); //判断是否有token的key if (name.equals("token")){ //得到值,并判断Redis是否有key if (redisUtil.hasKey(cookie.getValue())){ return true; } } } } //若没有登录跳转至/nologin request.getRequestDispatcher("/nologin").forward(request,response); return false; } }
然后注册控制器,实现 WebMvcConfigurer 接口重写其addInterceptors(InterceptorRegistry registry)方法