Spring Boot(十一):Spring Security 实现权限控制
ccwgpt 2024-10-27 08:53 34 浏览 0 评论
大家好,我是 杰哥
我们知道,Web 应用的安全一般关注用户认证(authentication)以及用户授权(authorization)两个部分,即确定你是谁以及你能干什么
Spring Security 是基于 Spring 框架,用于解决 Web 应用安全性的一种方案,它是一款优秀的权限管理框架
虽然相对于 shiro (是 Apache 的一个轻量级权限控制框架,不与任何框架捆绑)来说,它比较重(依赖的东西较多),但是 Spring Boot 提供了自动化配置方案,通过较少的配置就可以使用 Spring Security 来进行权限控制。因此,若项目使用的是 Spring Boot 框架,使用 Spring Security 将会是一个更优的选择
Spring Boot 集成 Security 实战
Spring Security 采用的是责任链的设计模式,它有一条很长的过滤器链来实现各个环节的控制逻辑。要使用它,只需要自定义类继承自 WebSecurityConfigurerAdapter 来配置。我们主要配置三个地方
- 配置可登录用户,包括用户名、密码和权限/角色
configure(AuthenticationManagerBuilder auth)
- 配置权限关系(url、authority的所有对应关系)
configure(HttpSecurity http)
- 配置放行规则
configure(WebSecurity web)
具体怎样使用呢?我们一起来看看具体步骤
一 公共配置
1 pom 依赖
首先引入 security 的 pom 依赖,直接引入 spring-boot-starter-security 依赖
<!--security 依赖 -->
<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>
<!--lombok 工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
其他两个依赖 spring-boot-starter-web 和 lombok 则分别用于构建 Web 应用、采用 lombok 的依赖包进行快捷开发
2 定义 User 实体类
@Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
// 用户所有权限
private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
由于后续的配置需要使用到用户的用户名、密码以及所拥有的权限这三个信息,因此需要为原 User 实体类,添加一个 authorities 属性,表示用户所拥有的的权限列表
3 定义测试 API
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/common")
public String common() {
return "hello~ common";
}
@GetMapping("/admin")
public String admin() {
return "hello~ admin";
}
}
分别定义 /user/common 和 /user/admin 两个 API,作为用户的权限,用于测试不同用户的权限
二 核心权限代码配置
好了,做好了准备工作之后,接下来就进入到我们的重点部分。为了让初学者有一个理解过程,我们先通过手动配置的方式,让大家对于 Security 的使用方式有个大致的概念
(一) 方式一:手动配置
1 权限配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 将用户配置在内存中(可登录用户,包括用户名、密码和权限)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式一:写到内存
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).authorities("admin")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).authorities("common");
}
/**
* 登录处理-配置对应用户所拥有的权限(url、authority对应的所有关系)
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式一:写死的方式
http.authorizeRequests().
antMatchers("/user/common").hasAnyAuthority("admin")
.antMatchers("/user/admin").hasAnyAuthority("admin")
.antMatchers("/user/common").hasAnyAuthority("common")
.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated()
.and().formLogin();
}
/**
* 放行规则(忽略拦截配置)
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/hello");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
/**
* 密码加密类
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
也就是说,我们将 Web 应用中的所有用户:admin 和 user 以及对应的权限,通过实现 configure(AuthenticationManagerBuilder auth) 方法,直接手动写入内存中,这里需要指定用户的用户名、密码以及用户所拥有的权限
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式一:写到内存
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).authorities("admin")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).authorities("common");
}
通过实现 configure(HttpSecurity http) 方法,来配置不同资源与权限的对应关系。这里表示 资源 /user/common 能够分别被具有 admin 和 common 权限的用户访问,而 /user/admin 则 只能够被具有 admin 权限的用户访问
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式一:写死的方式
http.authorizeRequests().
antMatchers("/user/common").hasAnyAuthority("admin")
.antMatchers("/user/admin").hasAnyAuthority("admin")
.antMatchers("/user/common").hasAnyAuthority("common")
.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated()
.and().formLogin();
}
通过实现 configure(WebSecurity web) 方法,来配置 Web 应用的放行规则。这里表示资源 /hello 和 /css/、/js/ 资源是不用做权限校验的,也就是说任何用户都有访问他们的权限
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/hello");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
2)验证 启动项目,进行访问验证
输入 /user/common,便自动跳转到了 login 登录页面。使用 user 进行登录
用户名密码输入正确之后,点击登录,则可以自动重定向至 /user/common 接口
我们修改浏览器的访问地址为 localhost:8082/user/admin,点击回车
由于当前登录用户 user ,并没有 admin 权限,因此被禁止访问 /user/admin 接口,返回码为 403
那么,也就是说,user 用户可以访问 /user/common 接口,但并没有权限访问 /user/adimin 接口
同样地,清掉 cookie 缓存之后,再试试采用 admin 用户重新登录,分别访问 /user/common 和 /user/admin 接口,均可以正常请求
这两个用户的访问效果,均符合我们的预期,说明我们的权限功能已经成功实现啦
(二) 数据库方式
Spring Boot Security 提供了基于用户的权限控制以及基于角色来进行权限控制这两种方式,即通过分别配置用户对应的权限和资源对应的权限(hasAuthority)、分别配置用户对应的角色和资源对应的角色(hasRole)这两种方式,这里使用的是直接指定用户的权限的方式,即 hasAuthority 方法,不过两者其实都是采用 RBAC -基于角色的权限访问控制(Role-Based Access Control) 进行权限管理的。即:
所以,若需要采用数据库的方式进行权限逻辑控制,我们需要创建 5 张表:user 表、role 表、permission 表、user_role 表以及 role_permission 表
1)建表语句
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of permission
-- ----------------------------
BEGIN;
INSERT INTO `permission` VALUES (1, '/user/common', 'common', NULL, 0);
INSERT INTO `permission` VALUES (2, '/user/admin', 'admin', NULL, 0);
COMMIT;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'USER');
INSERT INTO `role` VALUES (2, 'ADMIN');
COMMIT;
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
BEGIN;
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 2, 1);
INSERT INTO `role_permission` VALUES (3, 2, 2);
COMMIT;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'user', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
COMMIT;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user_role
-- ----------------------------
BEGIN;
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (3, 2, 2);
COMMIT;
其中,user 表中的两个用户的密码,均为 123456,是提前采用代码new BCryptPasswordEncoder().encode("123456")加密之后所得的结果
2)引入依赖
再引入数据库连接相关依赖
<!-- mysql 连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jpa 连接池-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>com.zaxxer</artifactId>
<groupId>HikariCP</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
3)数据库配置
在配置文件 application.yml 中,添加数据库配置
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
username: root
password: 123456
4)定义 UserMapper 接口
public interface UserMapper {
/**
* 根据用户名称查询
*
* @param userName
* @return
*/
@Select(" select * from user where username = #{userName}")
User findByUsername(@Param("userName") String userName);
/**
* 查询用户的权限根据用户查询权限
*
* @param userName
* @return
*/
@Select(" SELECT d.*\n" +
"from user a,user_role b,role_permission c,permission d\n" +
"WHERE \n" +
"a.id = b.user_id\n" +
"and b.role_id = c.role_id\n" +
"and c.permission_id = d.id\n" +
"and \n" +
"a.username= #{userName};")
List<Permission> findPermissionByUsername(@Param("userName") String userName);
}
5)定义 UserService 接口
该接口继承自接口 UserDetaileService 接口
public interface UserService<T extends User> extends UserDetailsService {
}
6) 接口实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.根据用户名称查询到user用户
User userDetails = userMapper.findByUsername(username);
if (userDetails == null) {
return null;
}
// 2.查询该用户对应的权限
List<Permission> permissionList = userMapper.findPermissionByUsername(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
permissionList.forEach((a) -> grantedAuthorities.add(new SimpleGrantedAuthority(a.getName())));
log.info(">>permissionList:{}<<", permissionList);
// 设置权限
userDetails.setAuthorities(grantedAuthorities);
return userDetails;
}
}
该实现类,主要实现 UserDetailsService 接口的 loadUserByUsername(String username) 方法,根据用户名,从数据库中获取到用户的信息,包括用户名、密码以及权限列表,用户后续的配置
7) 配置类
接下来,则进入具体代码逻辑
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserService<User> userService;
@Resource
private PermissionMapper permissionMapper;
/**
* 将用户配置在内存或者mysql中(可登录用户,包括用户名、密码和角色)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式二:来源于数据库
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
/**
* 登录处理-配置对应用户所拥有的权限(url、authority对应的所有关系)
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式二:配置来源于数据库
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeRequests = http.csrf().disable().authorizeRequests();
// 1.查询到所有的权限
List<Permission> allPermission = permissionMapper.findAllPermission();
// 2.分别添加权限规则
allPermission.forEach((p -> {
authorizeRequests.antMatchers(p.getUrl()).hasAnyAuthority(p.getName()) ;
}));
authorizeRequests.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated().and().formLogin();
}
/**
* 忽略拦截处理
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/getUserInfo");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
其实只需要将 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 中写死的部分改写为从数据库中获取的方式即可
完成之后,只需要重新启动项目,再次进行验证,发现效果与方式一的一样
项目中,一般也就是采用数据库配置的方式进行权限配置,当然根据不同的需要,只需要进行不同的数据库更换即可,底层逻辑并未发生变化。那么,这样我们就轻松实现了采用 Spring Boot 集成 Security 进行权限控制的效果
但是,你可能还没有发现,到了 Spring Boot 2.7 以后,WebSecurityConfigurerAdapter 被标记为了过期
这么重要的一个类,突然被标记为过期,那么,我们的权限配置逻辑要如何实现呢?
别慌,车到山前必有路,既然这个类不建议再使用了,那么Spring Security 的开发大佬们一定为我们提供了一个更为方便快捷的方式 我们一起来看看~
(三) Spring Security 2.7 以后的配置方式
根据 WebSecurityConfigurerAdapter 类的注释,我们可以清楚地看到:如果想要配置过滤器链,可以通过自定义 SecurityFilterChain Bean 来实现
而如果想要配置 WebSecurity,可以通过 WebSecurityCustomizer Bean 来实现
我们再来采用最新的方式,实现一下上述例子中的效果:
1 定义一个配置类
@Configuration
@Slf4j
public class SecurityConfig {
@Resource
private PermissionMapper permissionMapper;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeRequests = http.csrf().disable().authorizeRequests();
// 方式二:配置来源于数据库
// 1.查询到所有的权限
List<Permission> allPermission = permissionMapper.findAllPermission();
// 2.分别添加权限规则
allPermission.forEach((p -> {
authorizeRequests.antMatchers(p.getUrl()).hasAnyAuthority(p.getName()) ;
}));
authorizeRequests.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated().and().formLogin();
return http.build();
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return new WebSecurityCustomizer() {
@Override
public void customize(WebSecurity web) {
web.ignoring().antMatchers("/hello");
web.ignoring().antMatchers("/css/**", "/js/**");
}
};
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
也就是说,最新的方案:
- 只需要自定义 SecurityFilterChain(HttpSecurity http) Bean,将原先 configure(HttpSecurity http) 方法中的逻辑,转移到 SecurityFilterChain(HttpSecurity http) Bean 中实现即可
- 放行规则,则可以将原先 configure(WebSecurity web) 方法中的逻辑转移到 WebSecurityCustomizer Bean 中进行实现即可
- 而对于当前系统的用户信息,包括用户名、密码以及所拥有的权限列表这些信息,只需要实现 UserDetailsService 接口,并通过重写 loadUserByUsername() 方法进行获取即可(采用最新方案,这里不需要做任何修改)
总结
权限管理是每个 Web 应用都需要考虑的,而 SpringBoot 为 Security 提供了自动化配置方案,基于 RBAC 权限模型,通过较少的配置就可以实现较为复杂的权限管理逻辑
本文章,介绍了 Spring Security,并通过实例演示,让大家很容易理解 Security 的用法。需要注意的是,Spring Boot 2.7 以后,配置类WebSecurityConfigurerAdapter 已经被标记为过期,需要参考文章中的最新的方案,进行配置使用
文章演示代码地址:
https://github.com/helemile/Spring-Boot-Notes/tree/master/springSecurity
嗯,就这样。每天学习一点,时间会见证你的强大~
欢迎大家关注我们的公众号,一起持续性学习吧~
往期精彩回顾
总结复盘
架构设计读书笔记与感悟总结
带领新人团队的沉淀总结
复盘篇:问题解决经验总结复盘
网络篇
网络篇(四):《图解 TCP/IP》读书笔记
网络篇(一):《趣谈网络协议》读书笔记(一)
事务篇章
事务篇(四):Spring事务并发问题解决
事务篇(三):分享一个隐性事务失效场景
事务篇(一):毕业三年,你真的学会事务了吗?
Docker篇章
Docker篇(六):Docker Compose如何管理多个容器?
Docker篇(二):Docker实战,命令解析
Docker篇(一):为什么要用Docker?
..........
SpringCloud篇章
Spring Cloud(十三):Feign居然这么强大?
Spring Cloud(十):消息中心篇-Kafka经典面试题,你都会吗?
Spring Cloud(九):注册中心选型篇-四种注册中心特点超全总结
Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛
..........
Spring Boot篇章
Spring Boot(七):你不能不知道的Mybatis缓存机制!
Spring Boot(六):那些好用的数据库连接池们
Spring Boot(四):让人又爱又恨的JPA
SpringBoot(一):特性概览
..........
翻译
[译]用 Mint 这门强大的语言来创建一个 Web 应用
【译】基于 50 万个浏览器指纹的新发现
使用 CSS 提升页面渲染速度
WebTransport 会在不久的将来取代 WebRTC 吗?
.........
职业、生活感悟
你有没有想过,旅行的意义是什么?
程序员的职业规划
灵魂拷问:人生最重要的是什么?
如何高效学习一个新技术?
如何让自己更坦然地度过一天?
..........
相关推荐
- RACI矩阵:项目管理中的角色与责任分配利器
-
作者:赵小燕RACI矩阵RACI矩阵是项目管理中的一种重要工具,旨在明确团队在各个任务中的角色和职责。通过将每个角色划分为负责人、最终责任人、咨询人和知情人四种类型,RACI矩阵确保每个人都清楚自己...
- 在弱矩阵组织中,如何做好项目管理工作?「慕哲制图」
-
慕哲出品必属精品系列在弱矩阵组织中,如何做好项目管理工作?【慕哲制图】-------------------------------慕哲制图系列0:一图掌握项目、项目集、项目组合、P2、商业分析和NP...
- Scrum模式:每日站会(Daily Scrum)
-
定义每日站会(DailyScrum)是一个Scrum团队在进行Sprint期间的日常会议。这个会议的主要目的是为了应对Sprint计划中的不断变化,确保团队能够有效应对挑战并达成Sprint目标。为...
- 大家都在谈论的敏捷开发&Scrum,到底是什么?
-
敏捷开发作为一种开发模式,近年来深受研发团队欢迎,与瀑布式开发相比,敏捷开发更轻量,灵活性更高,在当下多变环境下,越来越多团队选择敏捷开发。什么是敏捷?敏捷是一种在不确定和变化的环境中,通过创造和响应...
- 敏捷与Scrum是什么?(scrum敏捷开发是什么)
-
敏捷是一种思维模式和哲学,它描述了敏捷宣言中的一系列原则。另一方面,Scrum是一个框架,规定了实现这种思维方式的角色,事件,工件和规则/指南。换句话说,敏捷是思维方式,Scrum是规定实施敏捷哲学的...
- 敏捷项目管理与敏捷:Scrum流程图一览
-
敏捷开发中的Scrum流程通常可以用一个简单的流程图来表示,以便更清晰地展示Scrum框架的各个阶段和活动。以下是一个常见的Scrum流程图示例:这个流程图涵盖了Scrum框架的主要阶段和活动,其中包...
- Mockito 的最佳实践(mock方法)
-
记得以前面试的时候,面试官问我,平常开发过程中自己会不会测试?我回答当然会呀,自己写的代码怎么不测呢。现在想想我好像误会他的意思了,他应该是想问我关于单元测试,集成测试以及背后相关的知识,然而当时说到...
- EffectiveJava-5-枚举和注解(java枚举的作用与好处)
-
用enum代替int常量1.int枚举:引入枚举前,一般是声明一组具名的int常量,每个常量代表一个类型成员,这种方法叫做int枚举模式。int枚举模式是类型不安全的,例如下面两组常量:性别和动物种...
- Maven 干货 全篇共:28232 字。预计阅读时间:110 分钟。建议收藏!
-
Maven简介Maven这个词可以翻译为“知识的积累”,也可以翻译为“专家”或“内行”。Maven是一个跨平台的项目管理工具。主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。仔...
- Java单元测试框架PowerMock学习(java单元测试是什么意思)
-
前言高德的技术大佬在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现...
- Spring框架基础知识-第六节内容(Spring高级话题)
-
Spring高级话题SpringAware基本概念Spring的依赖注入的最大亮点是你所有的Bean对Spring容器的存在是没有意识的。但是在实际的项目中,你的Bean必须要意识到Spring容器...
- Java单元测试浅析(JUnit+Mockito)
-
作者:京东物流秦彪1.什么是单元测试(1)单元测试环节:测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:1)单元测试:针对计算机程序模块进行输出正确性检验工作...
- 揭秘Java代码背后的质检双侠:JUnit与Mockito!
-
你有没有发现,现在我们用的手机App、逛的网站,甚至各种智能设备,功能越来越复杂,但用起来却越来越顺畅,很少遇到那种崩溃、卡顿的闹心事儿?这背后可不是程序员一拍脑袋写完代码就完事儿了!他们需要一套严谨...
- 单元测试框架哪家强?Junit来帮忙!
-
大家好,在前面的文章中,给大家介绍了以注解和XML的方式分别实现IOC和依赖注入。并且我们定义了一个测试类,通过测试类来获取到了容器中的Bean,具体的测试类定义如下:@Testpublicvoid...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- mfc框架 (52)
- abb框架断路器 (48)
- ui自动化框架 (47)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- ppt框架 (48)
- 内联框架 (52)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)