
本文共 38293 字,大约阅读时间需要 127 分钟。
SpringBoot
什么是Spring:Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
如何简化Java的开发的
- 基于POJO的轻量级和最小侵入性编程
- 通过IOC,依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样式代码
SppringBoot主要优点:
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码和生成XML配置的要求
- 让所有的Spring开发者更快的入门
什么是微服务:微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成一系列小服务的组合,可以通过http的方式进行互通,要说微服务架构,可以先看看我们以前的单体应用架构。
单体应用架构:我们将一个应用中的所有应用服务都封装在一个应用中,数据访问,web访问等放在一个war包中。
- 这样做的好处是,易于开发和测试,也十分方便部署,当要拓展时,只要将war复制多份,然后放到多个服务器上,在做个负载均衡就可以了。
- 单体架构应用的缺点是,哪怕要修改一个非常小的地方,都要停掉整个服务,重新打包,再部署这个应用的war包,特别是对于一个大型应用,我们不可能把所有内容放在一个应用里面,如何维护,如何分工合作都是问题。
微服务架构就是打破 all in one 的架构方式,把每个功能元素独立出来,把独立出来的功能元素动态组合,节省调用资源,每个功能元素的服务器都是一个可替换的,可独立升级的代码。
第一个SpringBoot
- 可以从官网新建项目后下载,解压后使用 IDEA 打开
- IDEA中新建 spring initiali 项目,选中 spring web 直接创建
导入 jar 包即可
自动装配
pom.xml
- Spring Boot Dependencies 核心依赖在父工程中
- 我们在写或者引入一些 SpringBoot 依赖的时候,不需要指定版本,就是因为父工程中已经写了。
启动器
org.springframework.boot spring-boot-starter
比如:spring-boot-starter-web,它就会自动帮我们导入web项目所有的依赖。
springboot会将所有的的功能场景,都变成一个个的启动器。
需要什么功能,找到对应的启动器就可以了(官网找) 。
主程序
//标注这是一个springboot的应用@SpringBootApplicationpublic class Springboot01HelloworldApplication { public static void main(String[] args) { //启动springboot应用 SpringApplication.run(Springboot01HelloworldApplication.class, args); }}
注解
@SpringBootConfiguration //springboot的配置 @Configuration //spring配置类 @Component //说明这也是一个spring的组件@EnableAutoConfiguration //自动配置 @AutoConfigurationPackage //自动配置包 @Import({Registrar.class}) //导入注册器 @Import({AutoConfigurationImportSelector.class}) //导入选择器 //获取所有的配置 Listconfigurations = this.getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
protected ListgetCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations;}
META-INF/spring.factories:自动配置的核心文件
SpringBoot所有装配是在启动时扫描并加载,所有的自动配置类都在spring.factories,但只有导入了对应的start,有了启动器,对应的配置才会生效,然后自动装配。
SpringBoot在启动的时候,从类路径下META-INF/spring.factories获取指定的值。
将这些自动配置的类导入容器,自动配置类就会自动生效,帮我们自动配置。
以前我们需要自动配置的东西,现在SpringBoot帮我们做了。
整合JavaEE,解决方案和自动配置的东西都在springframework.boot.autoconfigure:2.4.0包下。
它会把所有需要导入的组件以类名的方式返回,这些组件就会被添加到容器。
容器中也会存在非常多的 ***AutoConfigure 的文件,就是这个类给容器导入了这个场景所需要的所有组件。
有了自动装配,免去了我们手动编写配置注入功能组件的工作。
SpringApplication这个类主要是:
- 推断应用的类型是普通的项目还是web项目。
- 查找并加载所有可用初始化器,设置的initializers属性中。
- 找出所有的应用程序监听器,设置到listeners中。
- 推断并设置main方法的定义类,找到运行的主类。
自动装配的原理:
SpringBoot启动会加载大量的自动配置类。
我们看我们需要的配置类有没有在SpringBoot写好的自动配置类中。
再看这个配置类中到底装配了哪些组件(如果我们需要的组件在里面,就不用手动装配了)。
给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们只需要在配置文件中指定这些属性即可。
***AutoConfiguration:自动配置类,给容器中添加组件
***Properties:封装配置文件的相关属性
yaml中配置debug:true,启动可以查看哪些配置类生效了。
在springboot中,有非常多的***.Configuretion会帮助我们进行拓展配置,只要看见了这个,需要注意。
@EnableWebMvc,这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。
yaml讲解
SpringBoot使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yaml
- 语法结构:key:空格 value
配置文件可以帮我们修改SpringBoot自动配置的默认值。
YAML是"YAML Ain't a Markup Language"(YAML不是一种)的。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名。
标记语言
以前的配置文件,大多数使用xml来配置:比如一个简单的端口配置,对比一下yaml和xml
yaml配置:
server: port: 8088
xml配置
8088
使用注解@ConfigurationProperties将配置文件中的值映射到这个组件中,将类中的属性与配置文件一一对应。
yaml也可以这样使用
person: name: 周周${random.uuid} age: ${random.int} happy: true birth: 2020/10/10 maps: {k1: v1,k2: v2} list: - code - music - girl dog: name: ${person.zr:hello}_旺财 age: 5
也可以使用@PropertySource加载指定的properties文件【不建议使用,建议使用yaml】
@ConfigurationProperties只用绑定一次,@Value需要每个字段添加。
yaml可以松散绑定,即yaml中写first-name,实体类写firstName,-后的字母默认是大写的,这就是松散绑定。
JSR303数据验证,就是我们可以在字段增加一层过滤器验证,可以保证数据的合法性。
复杂类型对象,yaml可以封装对象,@Value就不可以。
结论:
- 配置yaml和properties都可以获取到值,强烈推荐yaml。
- 如果我们在某个业务中,只获取配置文件中的某个值,可以使用一下@Value。
- 如果在javaBean和配置文件进行映射,就直接使用@ConfigurationProperties。
JSR303
验证是否为邮箱格式
@Component@ConfigurationProperties(prefix = "person")@Validated//数据校验public class Person { @Email private String name;}
其它注解
空检查@Null 验证对象是否为null@NotNull 验证对象是否不为null, 无法查检长度为0的字符串@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.@NotEmpty 检查约束元素是否为NULL或者是EMPTY.Booelan检查@AssertTrue 验证 Boolean 对象是否为 true@AssertFalse 验证 Boolean 对象是否为 false长度检查@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内@Length(min=, max=) Validates that the annotated string is between min and max included.日期检查@Past 验证 Date 和 Calendar 对象是否在当前时间之前@Future 验证 Date 和 Calendar 对象是否在当前时间之后@Pattern 验证 String 对象是否符合正则表达式的规则数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null@Min 验证 Number 和 String 对象是否大等于指定的值@Max 验证 Number 和 String 对象是否小等于指定的值@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度@Digits 验证 Number 和 String 的构成是否合法@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。@Range(min=, max=) 检查数字是否介于min和max之间.@Range(min=10000,max=50000,message="range.bean.wage")private BigDecimal wage;@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)@CreditCardNumber信用卡验证@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。@ScriptAssert(lang= ,script=, alias=)@URL(protocol=,host=, port=,regexp=, flags=) @Pattern 正则表达式
Web开发
静态资源
在springboot中,我们可以使用以下路径处理静态资源
- webjars 【WebMvcAutoConfiguration.addResourceHandlers】 webjars 需导入
- public,static,/**,resources 【WebMvcAutoConfiguration-->ResourceProperties-->Resources】
优先级:resorces>static(默认)>public
首页
WebMvcAutoConfiguration下部分源码
private OptionalgetWelcomePage() { String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();}private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html");}
即在资源配置目录下放入index.html即可自动识别。
Thymeleaf
pom中引入Thymeleaf,就可以将.html放在templates下访问。
org.springframework.boot spring-boot-starter-thymeleaf
部分源码
@ConfigurationProperties( prefix = "spring.thymeleaf")public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; ......}
可以看出来前缀是classpath:/templates/,后缀.html,即html文件放在classpath:/templates/下
测试
package com.zr.controller;//在templates下的所有页面,只能通过controller来跳转//需要模板引擎的支持@Controllerpublic class IndexController { @RequestMapping("/test") public String index(Model model){ model.addAttribute("msg","hello test"); return "test"; }}
test.html
拓展SpringMvc
package com.zr.config;//扩展springmvc@Configuration//@EnableWebMvc //这配置是导入了一个类,DelegatingWebMvcConfiguration,从容器中获取所有的WebMvcConfigurer。public class MyMvcConfig implements WebMvcConfigurer { //视图跳转 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/zr").setViewName("test"); registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); } @Bean public ViewResolver myViewResolver(){ return new MyViewResolver(); } //自定义视图解析器 public static class MyViewResolver implements ViewResolver{ @Override public View resolveViewName(String s, Locale locale) throws Exception { return null; } }}
员工管理系统
首页配置,使用了thymeleaf接管,所有的herf引用改为th:herf="@{}"(th标签支持xmlns:th="")
页面国际化
- 需要配置i18n文件
- 如果需要在项目中进行按钮自由切换,我们需要自定义一个组件LocaleResolver
- 然后将自己写的组件配置到spring容器 @Bean
{}
整合JDBC使用
对于数据访问层,无论使SQL(关系型数据库)还是NOSQL(非关系型数据库),SpringBoot底层都是采用 Spring Data 的方式进行统一处理。
application.yml
spring: datasource: username: root password: 123456 #时区配置报错 增加时区配置serverTimezone=UTC url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver
mybatis数据库 user表
测试
@SpringBootTestclass Springboot04DataApplicationTests { @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //查看默认的数据源 System.out.println(dataSource.getClass()); //获得数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //***templete: SpringBoot已经配置好的bean 拿来即用 connection.close(); }}
controller
@RestControllerpublic class JdbcController { @Autowired JdbcTemplate jdbcTemplate; //查询数据库的所有信息 //没有实体类,数据库的东西怎么取 Map @GetMapping("/userlist") public List
整合Druid数据源
Druid是阿里巴巴开源平台上的一个数据库连接池实现,结合了C3P0,DBCP,PEOXOOL等DB池的优点,同时加入了日志监控。
SpringBoot默认的是Hikari数据源。
application.yml
spring: datasource: username: root password: 123456 #时区配置报错 增加时区配置serverTimezone=UTC url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #SpringBoot默认不注入这些属性的,需要自己绑定 #durid配置 initialSize: 5 minIdel: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志 #如果允许时报错,导入log4j依赖 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
监控
DruidConfig
@Configurationpublic class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } //后台监控 //springboot内置了Servlet,所有没有web.xml,替代方法: ServletRegistrationBean @Bean public ServletRegistrationBean statViewServlet(){ // ServletRegistrationBeanbean = new ServletRegistrationBean<>(new StatusManagerServlet(), "/druid/*"); ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); //后台需要有人登录,账号密码配置 HashMap initParameters = new HashMap<>(); //增加配置 initParameters.put("loginUsername","admin"); //登录的key是固定的 initParameters.put("loginPassword","123456"); //允许谁可以访问 initParameters.put("allow",""); //禁止谁能访问 initParameters.put("zr","39.405.48.101"); bean.setInitParameters(initParameters); //设置初始化参数 return bean; } @Bean //filter public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean<>(); bean.setFilter(new WebStatFilter()); //可以过滤哪些请求 HashMap initParameters = new HashMap<>(); //这些不进行统计 initParameters.put("exclusions","*.js,*.css,/druid/*"); bean.setInitParameters(initParameters); return bean; }}
启动后访问localhost:8080/druid ,账号密码为配置中设置的值,登陆后进入监控界面。
整合MyBatis
依赖mybatis-spring-boot-starter
application.properties
spring.datasource.username=rootspring.datasource.password=123456spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&chacterEncoding=utf-8spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#整合mybatismybatis.type-aliases-package=com.zr.pojomybatis.mapper-locations=classpath:mybatis/mapper/*.xml
User
package com.zr.pojo;@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private Integer id; private String name; private String pwd;}
UserMapper
package com.zr.mapper;//这个注解表示了这是一个mybatis的mapper类@Mapper@Repositorypublic interface UserMapper { ListqueryUserList(); User queryById(int id); int addUser(User user); int updateUser(User user); int deleteUser(int id);}
resource/mapper/UserMapper.xml
insert into user(id,name,pwd) values (#{id},#{name},#{pwd}); update user set name = #{name},pwd=#{pwd} where id=#{id}; delete from user where id=#{id};
UserController
package com.zr.controller;@RestControllerpublic class UserController { @Autowired private UserMapper userMapper; @GetMapping("/queryUserList") public ListqueryUserList(){ List userList = userMapper.queryUserList(); return userList; } @GetMapping("/queryById/{id}") public User queryById(@PathVariable("id") int id){ User user = userMapper.queryById(id); System.out.println(user); return user; } @GetMapping("/addUser") public String addUser(){ userMapper.addUser(new User(7,"韩信","33333")); return "addUser OK"; } @GetMapping("/updateUser") public String updateUser(){ userMapper.updateUser(new User(7,"韩信","54321")); return "updateUser OK"; } @GetMapping("/deleteUser") public String deleteUser(){ userMapper.deleteUser(7); return "deleteUser OK"; }}
以上仅为测试mybatis,没有编写业务层。
SpringSecurity
SpringSecurity是针对spring项目的安全框架,也是 SpringBoot 底层安全模块默认的技术选型,它可以实现强大的web安全控制,对于安全控制,我们只需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理。
记住以下几个类:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
SpringSecurity的两个主要目标是“认证” 和 “授权”(访问控制)
“认证” Authentication
“授权” Authorization
这两个概念是统用的,而不只是SpringSecurity中存在。
项目结构
application.properties
spring.thymeleaf.cache=false
login.html
登录 登录
注册blog.kuangstudy.comSpring Security Study by 秦疆
index.html
首页
RouterController
package com.zr.controller;@Controllerpublic class RouterController { @RequestMapping({"/","/index"}) public String index(){ return "index"; } @RequestMapping("/toLogin") public String toLogin(){ return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id){ return "views/level1/"+id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id")int id){ return "views/level2/"+id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id")int id){ return "views/level3/"+id; }}
SecurityConfig
package com.zr.config;@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { //授权 @Override protected void configure(HttpSecurity http) throws Exception { //首页所有人可以访问,功能页只有对应有权限的人才能访问 //请求授权的规则 http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); //没有权限默认会到登录页 开启登录页面 //usernameParameter("username").passwordParameter("password") // 括号内为前端传入的用户名为username,底层源码默认为username,当前端为name,pwd时就需要更改括号内问name,pwd //loginProcessingUrl("/login");//点登录跳转的路径 http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password").loginProcessingUrl("/login"); http.csrf().disable();//防止跨站脚本请求攻击关闭 登出失败可能的原因 //开启了注销功能 http.logout().logoutSuccessUrl("/index"); //开启记住我功能,cookie 默认保存两周,自定义接收前端的参数 //开启记住我功能 默认保存两周 http.rememberMe().rememberMeParameter("remember"); } //认证 //密码编码:PasswordEncoder //在Spring Security 5.0+中,新增了很多的加密方式 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //正常应该从数据库中读 这里是从内存中读取 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("zr").password(new BCryptPasswordEncoder().encode("12345")).roles("vip2","vip3") .and() .withUser("zz").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2","vip3"); }}
Shiro
Apache Shiro 是一个Java的安全(权限)框架。
Shiro 可以非常容易地开发出足够好的应用,其不仅可以用在JavaSE环境,还可以用在JavaEE环境。
Shiro可以完成认证,授权,加密,会话管理,Web集成,缓存等。
重要对象:
Subject:应用代码直接交互的对象是subject,也就是说shiro的对外核心API就是subject,subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是subject,如网络爬虫,机器人等,与subject的所有交互都会委托给SecurityManager,subject是一个门面,SubjectManager才是具体的执行者。
SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互,并且它管理着所有的subject,可以看出它是shiro的核心,它负责与shiro的其它组件交互,相当于SprinfMvc的DispatcherServlet的角色。
Realm:shiro从realm获得安全数据(如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从realm获得相应的用户进行比较,来确定用户的身份是否合法,也需要从realm得到用户相应的角色,权限来验证用户的操作是否能够进行,可以把realm当成DataSource。
整合shiro练习
目录结构
数据库为整合JDBC中的数据库增加一个perms字段
依赖pom.xml
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-thymeleaf org.apache.shiro shiro-spring 1.4.1 mysql mysql-connector-java log4j log4j 1.2.17 com.alibaba druid 1.1.21 org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.1 org.projectlombok lombok 1.18.16 com.github.theborakompanioni thymeleaf-extras-shiro 2.0.0
application.yml
spring: datasource: username: root password: 123456 #时区配置报错 增加时区配置serverTimezone=UTC url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&chacterEncoding=utf-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #SpringBoot默认不注入这些属性的,需要自己绑定 #durid配置 initialSize: 5 minIdel: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控拦截,stat 监控统计, wall 防御sql注入,log4j 日志 #如果允许时报错,导入log4j依赖 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
application.properties
mybatis.type-aliases-package=com.zr.pojomybatis.mapper-locations=classpath:mapper/*.xml
index.html
Title 首页
login.html
登录
add.html
add
update.html
update
User实体类
package com.zr.pojo;@Data@AllArgsConstructor@NoArgsConstructorpublic class User { private int id; private String name; private String pwd; private String perms;}
UserMapper
package com.zr.mapper;@Repository@Mapperpublic interface UserMapper { public User queryUserByName(String name);}
UserMapper.xml
UserService
package com.zr.service;import com.zr.pojo.User;public interface UserService { public User queryUserByName(String name);}
UserServiceImpl
package com.zr.service;@Servicepublic class UserServiceImpl implements UserService{ @Autowired UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); }}
MyController
package com.zr.controller;@Controllerpublic class MyController { @RequestMapping({"/","/index"}) public String toIndex(Model model){ model.addAttribute("msg","hello shiro"); return "index"; } @RequestMapping("/user/add") public String add(){ return "user/add"; } @RequestMapping("/user/update") public String update(){ return "user/update"; } @RequestMapping("/toLogin") public String toLogin(){ return "login"; } @RequestMapping("/login") public String login(String username,String password,Model model){ //获取当前的用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try{ subject.login(token); //执行登录的方法,如果没有一次就ok return "index"; }catch (UnknownAccountException a){ //用户名不存在 model.addAttribute("msg","用户名错误!"); return "login"; }catch (IncorrectCredentialsException a){ //密码不存在 model.addAttribute("msg","密码错误!"); return "login"; } } @RequestMapping("/noauth") @ResponseBody public String unAuthorized(){ return "未授权不能访问此页面!"; }}
UserRealm
package com.zr.config;//自定义的UserRealmpublic class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了==>>授权:AuthorizationInfo"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //info.addStringPermission("user:add"); //拿到当前登录的对象 Subject subject = SecurityUtils.getSubject(); User currentUser = (User)subject.getPrincipal(); //拿到user对象 //设置当前用户的权限 info.addStringPermission(currentUser.getPerms()); return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了==>>认证:AuthenticationInfo"); //用户名密码 数据库中取 UsernamePasswordToken userToken = (UsernamePasswordToken) token; User user = userService.queryUserByName(userToken.getUsername()); if (user==null){ return null; } Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser",user); //密码认证 shiro默认做 return new SimpleAuthenticationInfo(user,user.getPwd(),""); }}
ShiroConfig
package com.zr.config;@Configurationpublic class ShiroConfig { //ShiroFilterFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon:无需认证就可以访问 authc:必须认证 user:必须拥有记住我功能才能用 perms:拥有某个资源的权限才能访问 role:拥有某个角色权限才能访问 */ MapfilterMap = new LinkedHashMap<>(); //授权 没有授权会跳转到未授权界面(401) filterMap.put("/user/add","perms[user:add]"); filterMap.put("/user/update","perms[user:update]"); filterMap.put("/user/*","authc"); bean.setFilterChainDefinitionMap(filterMap); //返回登录页 bean.setLoginUrl("/toLogin"); //未授权页面 bean.setUnauthorizedUrl("/noauth"); return bean; } //DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联realm securityManager.setRealm(userRealm); return securityManager; } //创建realm对象,自定义 @Bean public UserRealm userRealm(){ return new UserRealm(); } //整合shiroDialect 用来整合shiro和thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }}
Swagger
学习目标:
- 了解Swagger的概念和作用
- 巩固前后端分离
- 在SpringBoot中巩固Swagger
Swagger简介
- 号称最流行的Api框架
- RestFul Api 文档在线自动生成工具=>Api文档与Api定义同步更新
- 直接运行,可以在线测试Api接口
- 支持多种语言
在项目中使用swagger需要导入jar包
- swagger2
- ui
SpringBoot集成Swagger
新建一个SpringBoot-web项目
导入相关依赖
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 编写helloword工程
配置swagger===>config
package com.zr.config;@Configuration@EnableSwagger2 //开启swagger2public class SwaggerConfig {}
访问http://localhost:8080/swagger-ui.html
配置Swagger
Swagger的Bean实例 Docket
package com.zr.config;import java.util.ArrayList;@Configuration@EnableSwagger2 //开启swagger2public class SwaggerConfig { //配置swagger的Docket的 Bean实例 @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()); } //配置swagger文档的信息 private ApiInfo apiInfo(){ //作者信息 Contact contact = new Contact("周周", "http://www.cnblogs.com/zhou-zr", "813794474@qq.com"); return new ApiInfo("周周的SwaggerApi文档", "coding", "1.0", "http://www.cnblogs.com/zhou-zr", contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList() ); }}
Swagger配置扫描接口
Docket select()
在配置Swagger中修改
//配置swagger的Docket的 Bean实例//enable 是否启动swagger 如果为false 不能再浏览器中访问swagger@Beanpublic Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(false) .select() //配置要扫描接口的方式 //basePackage 指定扫描的包 //any() 扫描全部 //none() 都不扫描 //withClassAnnotation() 扫描类上的注解 参数是一个注解的反射对象 //withMethodAnnotation() 扫描方法上的注解 .apis(RequestHandlerSelectors.basePackage("com.zr.controller")) //过滤什么路径 .paths(PathSelectors.ant("/zr/**")) .build();}
根据是否是生产环境来开启swagger
//配置swagger的Docket的 Bean实例 //enable 是否启动swagger 如果为false 不能再浏览器中访问swagger @Bean public Docket docket(Environment environment){ //设置要显示的swagger环境 Profiles profiles = Profiles.of("dev"); //通过environment.acceptsProfiles判断是否处在自己设定的环境当中 boolean flag = environment.acceptsProfiles(profiles); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(flag) .select() .apis(RequestHandlerSelectors.basePackage("com.zr.controller")) //过滤什么路径 //.paths(PathSelectors.ant("/zr/**")) .build(); }
application.properties
spring.profiles.active=dev
application-dev.properties
server.port=8081
application-pro.properties
server.port=8082
配置Api文档分组
.groupName("zzr")
如何配置多个分组:配置多个Docket实例即可。
@Beanpublic Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("A");}@Beanpublic Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("B");}@Beanpublic Docket docket4(){ return new Docket(DocumentationType.SWAGGER_2).groupName("C");}
接口测试
实体类配置
package com.zr.pojo;// @Api(注释)@ApiModel("用户实体类")public class User { @ApiModelProperty("用户名") public String username; @ApiModelProperty("密码") public String password;}
HelloController
package com.zr.controller;@RestControllerpublic class HelloController { @GetMapping("/hello") public String hello(){ return "hello"; } //只有我们的接口中,返回值中存在实体类,就会被扫描到swagger中 @PostMapping("/user") public User user(){ return new User(); } @ApiOperation("Hello控制类") @GetMapping("/hello2") public String hello(@ApiParam("用户名") String username){ return "hello"+username; } @ApiOperation("post测试") @PostMapping("/postt") public User postt(@ApiParam("用户名")User user){ return user; }}
总结:
- 我们以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
- 接口文档实时更新
- 可以在线测试
注意点:在正式上线的时候,关闭Swagger。
任务
异步任务
AsyncService
package com.zr.service;@Servicepublic class AsyncService { //告诉spring这是一个异步的方法 @Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("数据正在处理"); }}
AsyncController
package com.zr.controller;@RestControllerpublic class AsyncController { @Autowired AsyncService asyncService; @RequestMapping("/hello") public String hello(){ asyncService.hello(); //停止3秒 return "ok"; }}
主程序开启异步注解的功能
package com.zr;//开启异步注解的功能@EnableAsync@SpringBootApplicationpublic class Springboot09TestApplication { public static void main(String[] args) { SpringApplication.run(Springboot09TestApplication.class, args); }}
定时任务
TaskScheduler 任务调度者TaskExecutor 任务执行者@EnableScheduling //开启定时功能的注解@Scheduled //什么时候执行Cron 表达式
主程序开启定时任务的支持
package com.zr;@EnableScheduling //开启定时功能的注解@SpringBootApplicationpublic class Springboot09TestApplication { public static void main(String[] args) { SpringApplication.run(Springboot09TestApplication.class, args); }}
定时任务设置
package com.zr.service;@Servicepublic class ScheduledService { //在一个特定的时间执行 /* 0 30 10 * * ? 每天的10.30 执行一次 0 0/5 10,16 * * ? 每天的10点到16点 每隔五分钟执行一次 0 20 10 ? * 1-6 每个月的周一到周六 10.20执行一次 */ //cron表达式 秒 分 时 日 月 星期几(0-7 ?每一天) @Scheduled(cron = "0 * * * * ?") public void hello(){ System.out.println("hello,你被执行了"); }}
邮件任务
package com.zr;@SpringBootTestclass Springboot09TestApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { //一个简单的邮件 SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setSubject("小周,你好!"); mailMessage.setText("好好学习!"); mailMessage.setTo("813794474@qq.com"); mailMessage.setFrom("813794474@qq.com"); mailSender.send(mailMessage); } @Test void contextLoads2() throws MessagingException { //一个复杂的邮件 // MimeMessage 也可以直接new创建 MimeMessage mimeMessage = mailSender.createMimeMessage(); //组装 MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true); helper.setSubject("周周,你好呀!"); helper.setText("好好学习!
",true); //附件 helper.addAttachment("winC.PNG",new File("C:\\Users\\zr\\Pictures\\截图\\winC.PNG")); helper.addAttachment("2.jpg",new File("C:\\Users\\zr\\Pictures\\截图\\winC.PNG")); helper.setTo("813794474@qq.com"); helper.setFrom("813794474@qq.com"); mailSender.send(mimeMessage); }}
分布式
分布式系统理论
什么是分布式系统:
在《分布式系统原理与范型》 一书中有如下定义:分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。
分布式系统是有一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统,分布式系统的出现是为了用廉价的,普通的机器完成单个计算机无法完成的计算,存储任务。其目的是利用更多的机器,处理更多的数据。
只有当单个节点无法满足日益增长的计算,存储任务需求时,且硬件的提升高昂到得不偿失时,且程序也不能进一步优化的时候,我们才需要考虑分布式系统。
单一应用架构:当网站流量很小时,只需一个应用,将所有的功能部署在一起,以减少部署节点和成本,此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有的应用部署到一个应用中,简单易用。
缺点:性能扩展比较难,协同开发问题,不利于升级维护。
垂直应用架构:当数据量逐渐增大,单一应用增加机器带来的加速度越来越小,将一个应用拆分成互不相干的几个应用,以提升效率,此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职,性能更易扩展。
缺点:公用模块无法重复利用,开发性的浪费。
分布式服务架构:当垂直应用越来越多,应用之间交互不可避免。将核心应用抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构:当服务越来越多,容量的评估,小服务资源的浪费问题等问题逐渐显现,此时需要增加一个调度中心基于访问的压力实时管理集群的容量,提高集群利用率。此时。用于提高资源利用率的资源调度和治理中心(SOA)是关键。
RPC
什么是RPC:
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间的通信方式,它是一种技术的思想,而不是规范,它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不是程序员显示编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC两个核心:通信和序列化。
序列化:数据传输需要转换。
分布式 Dubbo + Zookeeper + SpringBoot
Apache Dubbo是一款高性能,轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
服务提供者(Provider):暴漏服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的 。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
容器(Container):服务运行容器。
调用关系说明
服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
window下安装zookeeper
官网下载zookeeper,解压至指定目录,管理员模式下进入到zookeeper的bin目录下,启动zkServer.cmd。
【注意报错】在zkServer.cmd源文件中加pause,可查看报错信息。
解决:进入conf目录,将zoo_sample.cfg复制一份将名字改为zoo.cfg。
启动zkServer.cmd , 启动zkCli.cmd
window下安装Dubbo
dubbo本身并不是一个服务软件,它其实就是一个jar包,把Java程序连接到zookeeper,并利用zookeeper消费,提供服务。
为了让用户更好的监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不安装也不影响使用。
下载dubbo-admin
cmd进入下载的目录下,执行mvn clean package -Dmaven.test.skip=true
打包成功
执行dubbo-admin\target下的dubbo-admin-0.0.1-SNAPSHOT.jar
命令:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar 【注意 zookeeper的服务一定要打开】
执行完毕后访问 默认账号密码是root,root。
登录成功界面
zookeeper:注册中心
dubbo-admin是一个监控管理后台,可以查看我们注册了哪些服务,哪些服务被消费了。
Dubbo:jar包
步骤:前提开启zookeeper
- 提供者提供服务
- 导入依赖
- 配置注册中心的地址,以及服务发现名,和要扫描的包
- 在想要被注册的服务上增加一个注解@Service (Dubbo的)
- 消费者如何消费
- 导入依赖
- 配置注册中心的地址,配置自己的服务名
- 从远程注入服务@Reference
项目结构:
provider-server
TickerService
package com.zr.service;public interface TickerService { public String getTicker();}
TickerServiceImpl
package com.zr.service;//服务注册与发现@Service //可以被扫描到 项目一启动就自动注册到注册中心@Component //使用dubbo后尽量不要使用servicepublic class TickerServiceImpl implements TickerService{ @Override public String getTicker() { return "zzr"; }}
application.properties
server.port=8001#服务应用的名字dubbo.application.name=provider-server#注册中心地址dubbo.registry.address=zookeeper://127.0.0.1:2181#哪些服务要被注册dubbo.scan.base-packages=com.zr.service
pom.xml(consumer-server的pom相同)
org.apache.dubbo dubbo-spring-boot-starter 2.7.8 com.github.sgroschupf zkclient 0.1 org.apache.curator curator-framework 2.12.0 org.apache.curator curator-recipes 2.12.0 org.apache.zookeeper zookeeper 3.4.14 org.slf4j slf4j-log4j12
consumer-server
TickerService
package com.zr.service;public interface TickerService { public String getTicker();}
UserService
package com.zr.service;@Service //放到容器中public class UserService { //想拿到票provider-server提供的票 去注册中心拿 @Reference //引用 pom坐标 定义路径相同的接口名 TickerService tickerService; public void byTicket(){ String ticker = tickerService.getTicker(); System.out.println("在注册中心拿到了一张票"+ticker); }}
application.properties
server.port=8002#消费者从那里去拿需要暴漏自己的名字dubbo.application.name=consumer-server#注册中心的地址dubbo.registry.address=zookeeper://127.0.0.1:2181
测试
package com.zr;@SpringBootTestclass ConsumerServerApplicationTests { @Autowired UserService userService; @Test void contextLoads() { userService.byTicket(); }}
结果
dubbo-admin中查看服务提供者
发表评论
最新留言
关于作者
