目录
5.1 建立springboot项目的时候添加jdbc、MySQL和web依赖
5.2 编写配置文件application.yaml,配置datasource:
6.2.1 在第五章节的基础上,添加druid和log4j的依赖
6.2.2 在第五章节的基础上,在properties.yaml文件中添加如下配置:
6.2.3 编写配置类,加载配置文件,初始化druid数据源
7.2 编写实体类和mapper接口,编写mapper映射文件
7.3 编写application.properties文件
写在前面:这篇文章是在看狂神说Java系列视频中关于springboot的笔记,文章里面也会用到一些狂神文章的资源【图片、运行原理流程等】。
1. springboot概述
2. 第一个springboot项目
2.1 创建项目并运行
创建一个springboot项目后,在Application主启动类的当前目录下创建文件夹,springboot项目只会扫描当前目录下的文件。
新建一个controller包,实现HelloController类,这个控制器会在用户从页面输入"hello"请求后向页面返回一个hello字符串
@Controller
public class HelloController {
@GetMapping("hello")
@ResponseBody
public String hello(){
return "hello";
}
}
运行Application类,浏览器中输入请求,返回"hello"
2.2 修改banner
在springboot项目发布的时候,控制台会打印出springboot的banner
想要修改banner,只需要resources文件夹下新建一个banner.txt,然后从网站站拷贝一个你想要的banner就好了。Spring Boot banner在线生成工具,制作下载banner.txt,修改替换banner.txt文字实现自定义,个性化启动banner-bootschool.net
2.3 项目打包成package
将项目打包成package后,它会变成一个jar包,你只需要在目录文件下打开控制台,输入 java -jar .jar包名称, 你的springboot项目就会启动了。不过这样的话需要手动关闭项目,不然项目会一直运行,端口号也会被占用。
3. 浅谈SpringBoot原理
3.1 @SpringBootApplication注解
SpringBoot项目主启动类上有一个注解@SpringBootApplication,它的主要结构如下
@SpringBootApplication
@SpringBootConfiguration
@Configuration //标志这是一个配置类
@EnableAutoConfiguration
@Import({AutoConfigurationImportSelector.class}) //自动获取注解选择器
@SpringBootApplication下有两个注解:@SpringBootConfiguration和@EnableAutoConfiguration。@SpringBootConfiguration注解下有一个@Configuration注解,也就是说它让主启动类变成了一个配置类【如果对@Configuration注解有问题的,先了解SpringMVC中@Configuration的用法】;@EnableAutoConfiguration注解下引入了一个AutoConfigurationImportSelector的类信息,从命名可以猜测它与自动配置选择器有关。进入这个类的源码,可以看到它有一个getCandidateConfigurations的方法,用来获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> 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;
}
这个方法通过反射获取了所有的候选配置,并且如果候选配置为空的话,assert就断言出 在META-INF/spring.factories 中找不到自动配置类;如果候选配置不为空,那么返回这些候选配置。
META-INF/spring.factories存在与springboot项目的External Libraries文件夹中,也就是依赖文件中,在它的autoconfiguration包下:
这个文件中有很多的默认配置,每个配置项都对应了一个XXXAutoConfiguration类,和一个XXXProperties类。XXXProterties类里面主要是配置属性以及属性的get/set方法,XXXAutoConfiguration类中一定会用到@ConditionalOnXXX(args...)或者@ConditioanlOnMissingXXX(args...)这样的注解。在AutoConfigurationImportSelector类中,有个selectImports的方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
当存在注解元数据的时候,它才返回获得的注解;不存在注解元数据的时候,就返回NO_IMPORTS。这个方法就和@Conditional注解联系起来了。如果我们在pom.xml文件中引入依赖的话,那么ConditionalOn注解就满足条件,生效,isEnabled条件也就满足,获取配置;否则,条件不满足,相对应的类也就无法使用。这也是为什么spring.factories文件中已经配置了那么多的环境,我们还要引入依赖;只有引入了相应的依赖,@Conditional和isEnabled条件才会被满足。
3.2 SpringApplication.run
SpringApplicatio主要做了以下四件事情:
1. 推断应用的类型是普通的项目还是Web项目
2. 查找并加载所有可用的初始化器,设置到initializers属性中
3. 找出所有的应用程序监听器,设置到listeners属性中
4. 推断并设置main方法的定义类,找到运行的主类
run方法流程
4. springboot项目练手笔记
5.整合jdbc
5.1 建立springboot项目的时候添加jdbc、MySQL和web依赖
5.2 编写配置文件application.yaml,配置datasource:
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver
想要知道datasource所有可配置属性,可查看DataSourceProperties类,部分属性如下:
在springboot中,存在很多的XXXTemplate,这些Template都是springboot预先定义好的模板,拿来即用。模板的细节由用户编写配置文件来定义。在整合jdbc中,springboot使用了JdbcTemplate模板,使用datasource来创建一个JdbcTemplate。在JdbcTemplateConfiguration类中,有如下代码:
至此,我们可以分析:在springboot中使用jdbc,编写好配置文件配置datasource后,由DataSourceAutoConfiguration类生成一个DataSource,并被JdbcTemplate使用。使用JdbcTemplate直接就可以操作数据库了。
5.3 编写controller
@RestController
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping("userList")
public List<Map<String, Object>> getList(){
String sql = "select * from user";
List<Map<String, Object>> list_map = jdbcTemplate.queryForList(sql);
return list_map;
}
@RequestMapping("updateUser/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update user set name=?, pwd=? where id=" + id;
Object[] objects = new Object[2];
objects[0] = "宏彬";
objects[1] = "zzzzzz";
jdbcTemplate.update(sql, objects);
return "update-ok";
}
@RequestMapping("deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id){
String sql = "delete from user where id=" + id;
jdbcTemplate.update(sql);
return "update-ok";
}
@RequestMapping("addUser")
public String addUser(){
String sql = "insert into user(id, name, pwd) VALUES (7, '图图', '987456')";
jdbcTemplate.update(sql);
return "update-ok";
}
}
可以很明显地发现,使用springboot确实简化了开发者的很多任务,这是实实在在的。不像ssm一样,只是把Java代码变成配置文件了。
6. 整合Druid数据源
6.1 Druid简介
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
6.2 整合步骤
6.2.1 在第五章节的基础上,添加druid和log4j的依赖
6.2.2 在第五章节的基础上,在properties.yaml文件中添加如下配置:
type: com.alibaba.druid.pool.DruidDataSource#Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500P
6.2.3 编写配置类,加载配置文件,初始化druid数据源
//通过@Configuration注解类将配置文件加载进行,初始化druid。
//因为springboot没有.xml文件,所以只能使用配置类
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new // druid下的所有請求都會被解析到登錄頁面
ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 设置属性,如登录名、密码、允许登录拦截等,具体属性可以在 StatViewSerlvet的父类ResourceServlet中查看
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin");
initParams.put("loginPassword", "123456");
initParams.put("allow", "");
bean.setInitParameters(initParams);
return bean;
}
// 配置web和druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String, String> initParams = new HashMap<>();
// exclusions: 设置对哪些请求进行过滤排除掉,不统计
// 所有可设置属性在WebStatFilter类中进行查看
initParams.put("exclusions", "*.js, *.css, /druid/*, /jdbc/*");
bean.setInitParameters(initParams);
// 过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
至此,springboot整合druid已经完成了,在浏览器中输入localhost:8080/druid便会跳转到druid后台监控页面,在这个页面你能查看很多监控记录。
7. 整合mybatis
7.1 添加依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
7.2 编写实体类和mapper接口,编写mapper映射文件
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User getUserById(int id);
int addUser(User user);
int deleteUserById(int id);
int updateUser(User user);
}
<?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.example.springbootmybatis.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from user
</select>
</mapper>
7.3 编写application.properties文件
spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mybatis.type-aliases-package=com.example.springbootmybatis.pojo mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
7.4 实现controller类
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("queryUserList")
public List<User> queryUserList(){
List<User> users = userMapper.queryUserList();
return users;
}
}
7.5 浏览器输入请求,得到返回结果
7.6 一个奇怪的问题
在配置application文件的时候,可以使用.properties文件和.yaml文件。这里我使用了.properties文件,因为刚开始使用的是.yaml文件,结果出现了很奇怪的错误:找不到userMapper.xml文件。propertise.yaml文件如下
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: type-aliases-package: com.example.srpingbootmybatis.pojo mapper-locations: classpath:mybatis/mapper/*.xml
具体情况如下
- 1. 当我userMapper.xml文件位于resources/mybatis/userMapper.xml,而关于userMapper.xml文件的位置声明如上的时候,项目启动时不报错,输入查询请求后报错(userMapper.xml文件应该在resources/mybatis/mapper/userMapper.xml,但是我不小心放错路径了):页面为error页面,后台报错找不到方法;如果对应的方法不调用userMapper.queryUserList()方法,而是输出userMapper,则不报错且正常输出
- 2. 当我将userMapper.xml放于resources/mybatis/mapper/userMapper.xml处时,项目启动的时候就报错了
8. SpringSecurity
8.1 简介
springsecurity是一个功能强大且高强度可定制的身份验证和访问控制框架,它实际上是保护基于spring的应用程序的标准。
springsecurity是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有的spring项目一样,spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求。
一般来说,web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证是指验证某个用户是否为系统中的合法主体,即登录功能。用户授权指的是验证某个用户是否具有权限执行某个操作。不同用户所应具有的权限是不同的,一般来说,系统会为不同的用户分配不同的角色,每个角色对应一系列的权限。
8.2 测试项目
8.2.1 添加依赖
<dependencies>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
8.2.2 实现控制类
@Controller
public class RouterController {
// 接收 访问首页 的请求
@RequestMapping({"/", "/index"})
public String index(){
return "index";
}
// 接收 登录 的请求
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
// 接收 访问不同页面level1 的请求
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/" + id;
}
// 接收 访问不同页面level2 的请求
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
// 接收 访问不同页面level3 的请求
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
在这个控制类中,通过接收不同的请求来实现访问不同服务器资源的功能。
8.2.3 实现安全配置类
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定义授权规则:首页所有人都可以访问,功能页只有具有权限的人才能访问
http.authorizeHttpRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAnyRole("vip1")
.antMatchers("/level2/**").hasAnyRole("vip2")
.antMatchers("/level3/**").hasAnyRole("vip3");
// 没有登录的话到登录页面,需要主动开启
// springboot会自动发出一个 /login 的请求,然后自动跳转到springboot内置的登录页面
http.formLogin();
// 在springboot中,登录验证是由框架自定义的操作进行处理的,比如登录的页面、用户名和密码的接收
// 当然,用户也可以定制自己的登录页面
/****************************************************************************************************
* http.formLogin().loginPage("/toLogin") // 将需要登录时,发出的请求改为 toLogin *
* .usernameParameter("userName") // 获取name属性为userName的input *
* .passwordParameter("pwd") // 获取name属性为pwd的input *
* .loginProcessingUrl("/login"); // 当在自己定制的登录页面提交登录信息后,设置处理登录的请求 *
*****************************************************************************************************/
// 注销功能,会发出一个 /logout 请求,成功注销的话重定向到 /login?logout登录页面,并显示
// 已注销的提示
// 此时回到未登录状态
http.logout();
http.csrf().disable(); //关闭csrf功能,登陆失败的可能原因
// http.logout().logoutSuccessUrl("/"); 注销成功后,跳到首页
// 记住我 功能, cookie, 默认保存两周
http.rememberMe();
// 当使用了自己定制的登陆页面时,想要使用 记住我 功能
// http.rememberMe().rememberMeParameter("remember"); 一个name为remember的checkbox
// 如果想看更多设置,可下载源码后查看注释,注释写得很详细
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置不同的登录用户名、密码、授权,
// 也可以通过连数据
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("plane").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("红猪").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2")
.and()
.withUser("宏彬").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1" ,"vip3");
}
}
8.3 项目原理讲解
上面的几个小节我们就完成了测试项目的搭建,这一小节进行项目讲解。
通过浏览器一开始访问项目的时候,controller将页面跳转到index首页,此时还未登录;点击页面链接访问其它资源,此时securityconfig类检查权限不够,就自动发出一个/login的请求,跳转到springboot内置的一个登录页面;登录成功后,回到首页,根据权限可进行操作。注销功能会就收一个/logout请求并进行处理,跳转到一个springboot内置的确认注销页面,注销后回到登录页面并提示已注销。
springsecurity还允许用户自定义登录页面和注销页面等,具体实现看上面代码的注释部分。
9. shiro
9.1 简介
Apache Shiro是一个Java的安全(权限)框架,它可以非常容易地开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以完成认证,授权,加密,会话管理,Web集成,缓存等。
几个重要的方法:
1. Subject currentUser = SecurityUtils.getSubject(); //获得当前登录用户
2. Session session = currentUser.getSession(); //获得用户的对话
3. currentUser.isAuthenticated(); 当前用户是否验证
4. currentUser.getPrincipal(); 获得当前用户验证
5. currentUser.hasRole("schwartz"); 当前用户是否具有某个角色
6. currentUser。isPermitted(“lightsaber:wield") 当前用户是否具有某个权限
7. current.logout(); 当前用户注销
9.2 shiro执行流程简介
在shiro中,有三个重要的对象:Subject, SecurityManager和Realm。Subject代表用户;SecurityManager代表安全管理,所有具体的交互都通过它来进行控制;Realm提供认证和授权的数据【即登录和权限验证】。
实现Realm需要继承AuthorizingReaml类,并重写它的授权和验证方法:
public class UserReaml extends AuthorizingRealm {
@Autowired
UserMapper userMapper;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("进入了授权方法doGetAuthorizetionInfo()");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的对象,并进行授权
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal(); //在授权处取出认证处存储的权限信息
info.addStringPermission(currentUser.getPerms());
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("进入了认证方法doGetAuthenticationInfo");
UsernamePasswordToken token1 = (UsernamePasswordToken)token;
User user = userMapper.queryUserByName(token1.getUsername());
// 用户名认证,如果用户名不正确,返回null,则在MyController.login()方法中抛出用户名不存在异常
if(user==null){
return null;
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser", user);
// 密码验证,如果密码错误,则在MyController.login()中抛出密码错误异常
/************************************************************
* SimpleAuthenticationInfo构造方法的三个参数:
* principal: 权限信息,认证处存储的权限信息,可以在授权处取出来
* credentials: 密码
* realmName:
*************************************************************/
// 将user作为权限信息存储进去
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
}
shiro中有三大对象:ShiroFileterFactoryBean, DefaultWebSecurityManager, Realm,前面我们已经定义好了我们需要的Realm,现在使用这个定义的Realm来创建这些对象:
@Configuration
public class ShiroConfig {
// shiro三大对象:ShiroFIleterFactoryBean, DefaultWebSecurityManager, realm
// 3.创建一个ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean
(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
System.out.println("进入了shiroFilterFactoryBean(),这里进行了权限限定");
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
// 添加shiro的内置过滤器
/*
* anno: 无需认证就可以访问
* authc: 必须认证了才能访问
* user: 必须拥有 记住我 功能才能用
* perms:拥有某个资源的权限才能访问
* role:有用某个角色权限才能访问
* */
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/*", "authc");
// filterMap.put("/user/add", "authc");
// filterMap.put("/user/update", "authc");
// 授权,权限不够会跳转到未授权页面
filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/update", "perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);
// 设置登录的请求
bean.setLoginUrl("/toLogin");
// 设置未授权的请求
// bean.setUnauthorizedUrl("/noauth");
return bean;
}
// 2.创建一个DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager
(@Qualifier("userReaml") UserReaml userReaml){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userReaml);
return securityManager;
}
// 1.创建realm对象,一个自定义的类
@Bean(name = "userReaml")
public UserReaml userReaml(){
return new UserReaml();
}
@Bean
public ShiroDialect getShiroDialect(){ //用来整合shiro和thymeleaf
return new ShiroDialect();
}
}
接下来就是实现控制器,处理请求:
@Controller
public class MyController {
@RequestMapping({"/", "/index"})
public String index(Model model){
model.addAttribute("msg", "hello, Shiro");
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String userName, String password, Model model){
System.out.println("进入了login()");
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try{
currentUser.login(token);
return "index";
}catch (UnknownAccountException uae){
model.addAttribute("msg", "用户名不存在");
System.out.println("用户名不存在");
return "login";
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg", "密码错误");
System.out.println("密码错误");
return "login";
}catch (AuthenticationException ae){
model.addAttribute("msg", "登陆失败");
return "login";
}
// System.out.println(userName+"***"+password);
// return "login";
}
@RequestMapping("/user/add")
public String add(){
Subject currentUser = SecurityUtils.getSubject();
// 可能是因为版本的原因,只有调用了Subject.isPermitted()这一类的方法时,才会走Realm里授权的方法
if(currentUser.isPermitted("user:add")) System.out.println("具有add权限");
else System.out.println("不具有add权限");
return "user/add";
}
@RequestMapping("/user/update")
public String update(Model model){
Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted("user:update")) {
System.out.println("具有update权限");
return "user/update";
}
else {
System.out.println("不具有update权限");
model.addAttribute("msg", "没有权限");
return "index";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "权限不够";
}
}
下面是一个用了些thymeleaf标签的前端页面【不要忘记引入命名空间】:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p1>这是首页</p1>
<p th:text="${msg}"></p>
<hr>
<!--很奇怪,项目刚启动时第一次链接请求会出现400的错误,返回页面进行第二次以及之后的链接就不会出现错误了-->
<span th:text="${msg}"></span>
<!--未登录的时候,显示登录-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<!--<div shiro:hasPermission="user:add"><a th:href="@{/user/add}">add</a></div>-->
<a th:href="@{/user/add}">add</a>
<!--有相应的权限,才能看到相应的内容-->
<div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a></div>
<!--<a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>-->
</body>
</html>
10 swagger
10.1简介
swagger是基于前后端分离使用的,它可以实时更新、展示后台代码信息,还能够直接测试
10.2使用
10.2.1 配置swagger
导入依赖【注意,maven版本不能过高,否则会出现错误,这里使用2.1.7.RELEASE】
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
配置swagger
@Configuration
@EnableSwagger2
public class SwaggerConfig {
}
编写控制器
@Controller
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello(){
return "hello, swagger";
}
}
至此,swagger已经配置好了,现在可以启动项目,输入localhost:8080/swagger-ui.html,出现swagger页面,它主要由四个部分组成
10.2.2 设置swagger
swagger页面中可以看到文档的一些说明信息,首先来完成这部分的设置,在SwaggerConfig类中配置swagger的Bean实例:
// 配置swagger的bean实例
@Bean
public Docket docket(Environment environment){
return new Docket(DocumentationType.SWAGGER_2)
.enable(flag)
.apiInfo(apiInfo())
.build();
}
private ApiInfo apiInfo(){
// 作者信息
Contact contact = new Contact("plane", "23333", "1824271147@qq.com");
return new ApiInfo(
"Api Documentation title",
"Api Documentation description",
"1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
在实例化Docket的时候,还可以配置扫描控制器的规则,在docket()方法中做如下修改就好了
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.enable(flag)
.apiInfo(apiInfo())
.select()
/*************************************************************
* RequestHandlerSelectors, 配置要扫描接口的方式 *
* basePackage:指定要扫描的包 *
* any():扫描全部 *
* none():不扫描 *
* withClassAnnotation:扫描类上的注解 *
* withMethodAnnotation:扫描方法上的注解 *
**************************************************************/
.apis(RequestHandlerSelectors.basePackage("com.plane.controller"))
// .paths(PathSelectors.ant("/plane/**")) //这个路径下的会被过滤掉
.build();
}
接下来设置模块部分,也就是实体类的展示。在controller中,返回的实体类都会被扫描到,并且扫描的是实体类的public部分。编写一个User实体类,使控制器通过一个请求返回一个User
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String name;
@ApiModelProperty("密码")
public String password;
}
@RequestMapping("/user")
public User user(){
return new User();
}
接下来设置分组信息,每配置一个Docket的Bean实例,就相当于添加了一个分组,想添加多个分组的话那就配置多个Docket的Bean实例就好了
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("B");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("C");
}
swagger可以看到项目的后台接口,这如果被用户看到是不安全的,所有还应该进行设置,使得只有在开发环境下显示页面,发布环境下不显示。通过配置文件指定环境,然后使用Environment参数获取当前环境,判定是否使用swagger
三个配置文件:
其中,application.properties中指定当前使用的配置文件,另外两个则分别指定了不同的端口号
spring.profiles.active=dev
在docket()中判断当前环境,设置是否使用swagger
// 配置swagger的bean实例
@Bean
public Docket docket(Environment environment){
// 设置要显示的Swagger环境
Profiles profiles = Profiles.of("dev", "test");
// 通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.enable(flag)
.apiInfo(apiInfo())
.select()
/*************************************************************
* RequestHandlerSelectors, 配置要扫描接口的方式 *
* basePackage:指定要扫描的包 *
* any():扫描全部 *
* none():不扫描 *
* withClassAnnotation:扫描类上的注解 *
* withMethodAnnotation:扫描方法上的注解 *
**************************************************************/
.apis(RequestHandlerSelectors.basePackage("com.plane.controller"))
// .paths(PathSelectors.ant("/plane/**")) //这个路径下的会被过滤掉
.build();
}
通过上面的代码,我们设置了页面文档的信息,分组信息,default组中控制器的扫描和模块部分,以及在不同环境下是否使用swagger,接下来就是测试了。
10.3 测试
首先输入localhost:8081/swagger-ui.html,在dev环境下访问swagger,如下
接下来,在application.properties中改变使用的配置文件,再访问 localhost:8082/swagger-ui.html,此时不是dev环境,页面不能访问
此时可以看到,default已经不存在分组信息中了。
11. 异步任务和定时任务
11.1 异步任务
在Javaweb中,有很多任务是需要异步执行的,比如邮件发送这样比较耗时的操作,如果只用单线程的话在邮件发送完成前页面会卡住,这对于用户体验是十分差的,所以需要使用异步方法。
异步方法其实就是多线程。在JavaSE中有多种开启多线程的方法,但是这些方法都比较繁琐,springboot帮我们做了很多,只需要一个注解就可以实现异步了。
@Service
public class AsyncService {
@Async //执行这个方法的时候,就开启一个线程池
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理完成");
}
}
@Controller
public class HelloController {
@Autowired
AsyncService asyncService;
@RequestMapping("/hello")
@ResponseBody
public String hello(){
asyncService.hello();
return "ok";
}
}
在这个示例中,AsyncService.hello()方法就已经被处理为异步方法了。
11.2 定时任务
定时任务,就是在设定的时间进行操作。操作时间的设定可以通过cron表达式完成。下面的示例是在周一到周日的17:11执行方法。cron表达式可上网查询。
@Service
public class SecheduledService {
// 秒 分 时 日 月 周几
@Scheduled(cron = "0 11 17 * * 0-7")
public void hello(){
System.out.println("这个方法执行了");
}
}
12 dubbo
12.1 dubbo概述
dubbo是一款高性能、轻量级的开源JavaRPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo本身并不是一个服务软件,而是一个jar包,能够帮你的Java程序连接到zookeeper,并利用zookeeper消费、提供服务。为了让用户更好的管理监控众多的dubbo服务,官方提供了也给可视化的监控程序dubbo-admin,不够这个监控即使不装也不影响。
12.2 使用
配置好dubbo和zookeeper后,分别建provider-server项目和consumer-server项目,pom依赖如下【注意版本问题,可能会出现一些奇怪的问题】:
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 日志会冲突-->
<!-- 引入zookeeper-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!-- 排除slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
12.2.1 provider-server
设置application.properties
server.port=8082
#服用应用名称
dubbo.application.name=provider-server
#注册中心地址 注册中心地址需要和dubbo中application.properties文件中的地址相同
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些服务要被注册
dubbo.scan.base-packages=com.plane.service
dubbo.config-center.timeout=25000
在service包下建立一个TicketService接口,以及它的实现类:
package com.plane.service;
public interface TicketService {
public String getTicket();
}
package com.plane.service;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Service //将服务发布出去
@Component //放在spring容器中
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "买了一张票";
}
}
启动zookeeper和dubbo-admin,然后再启动项目,访问localhost:7001,可以看到getTiciet()方法已经被监听到了:
12.2.2 consumer-server
pom依赖和提供者相同
编写application.properties配置文件
server.port=8081
#消费者去哪里拿服务,需要暴露自己的名字
dubbo.application.name=consumer-service
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
在与提供者相同的包路径下也写一个TicketService接口
package com.plane.service;
public interface TicketService {
public String getTicket();
}
编写UserService类【注意注释用的是哪个,会有同名注释】
package com.plane.service;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 到注册中心拿到需要的服务
@Reference //引用服务,使用pom坐标,定义路径相同的接口名
TicketService ticketService;
public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心拿到票:"+ticket);
}
}
不要关闭提供者项目的运行,在consumer-server项目的test中测试运行:
@SpringBootTest
class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
userService.buyTicket();
}
}
运行结果如下:
这样,就调用到了另一个项目中的资源,实现分布式了。