spring boot 2.1.6.RELEASE配oauth2-2.0版本

  • maven ->pom.xml


    4.0.0

    com.mingjia
    demo
    0.0.1-SNAPSHOT
    dataControl
    data control content

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
        Edgware.SR1
        2.2.0.RELEASE
        2.8.5
        4.2.1
        1.3.0.Beta1
        1.18.2
        8.0.16
    

    
        
            org.springframework.security.oauth
            spring-security-oauth2
            2.3.3.RELEASE
        
        
            org.springframework.security.oauth.boot
            spring-security-oauth2-autoconfigure
            2.0.1.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-security
            2.1.6.RELEASE
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
            ${spring-boot.version}
        

        
        
            org.mapstruct
            mapstruct-jdk8
            ${org.mapstruct.version}
        
        
            org.mapstruct
            mapstruct-processor
            ${org.mapstruct.version}
            provided
        



        
            org.springframework.security
            spring-security-jwt
            1.0.9.RELEASE
        
        
            commons-io
            commons-io
            2.6
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        


        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
            ${spring-boot.version}
        
        
            com.querydsl
            querydsl-jpa
            ${querydsl.version}
        



        
        
            io.springfox
            springfox-swagger2
            2.8.0
        
        
            io.springfox
            springfox-swagger-ui
            2.8.0
        


        
        
            com.google.code.gson
            gson
            ${gson.version}
            compile
        
        
            com.jayway.jsonpath
            json-path
        

        
        
            org.projectlombok
            lombok
            ${lombok.version}
            provided
        



        
        
            com.querydsl
            querydsl-jpa
        
        
        
            mysql
            mysql-connector-java
            ${mysql-connector-java.version}
        

    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            

            
                com.mysema.maven
                apt-maven-plugin
                1.1.3
                
                    
                        
                            process
                        
                        
                            target/generated-sources/java
                            com.querydsl.apt.jpa.JPAAnnotationProcessor
                        
                    
                
                
                    
                        com.querydsl
                        querydsl-apt
                        ${querydsl.version}
                    
                
            
        
        
            
                src/main/resources
            
            
                src/main/java
            
        
    




下面是oauth2相关配置,公4个class

  1. AuthenticationBeanConfig
package com.mingjia.data_control.config.oauth;


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;

@Configuration
public class AuthenticationBeanConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    @ConditionalOnMissingBean(PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @ConditionalOnMissingBean(ClientDetailsService.class)
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
}

  1. OAuth2ServerConfig
package com.mingjia.data_control.config.oauth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AccessTokenConverter accessTokenConverter;

    @Autowired
    private ClientDetailsService clientDetails;

    // 配置令牌端点(Token Endpoint)的安全约束
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                // code授权添加
                .realm("oauth2-resources")
                // 开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                // 接口/oauth/check_token允许检查令牌
                .checkTokenAccess("isAuthenticated()")
                // 使/oauth/token支持client_id以及client_secret作登录认证
                .allowFormAuthenticationForClients()
                // 密码编码器
                .passwordEncoder(passwordEncoder);
    }

    // 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints
                // 认证管理器
                .authenticationManager(authenticationManager)
                // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                // 要使用refresh_token的话,需要额外配置userDetailsService
                .userDetailsService(userDetailsService)
                // 指定token存储位置
                .tokenStore(tokenStore)
                // 配置JwtAccessToken转换器
                .accessTokenConverter(accessTokenConverter)
                // 客户端详细信息服务的基本实现 这里使用JdbcClientDetailsService
                .setClientDetailsService(clientDetails);
    }

    // 配置客户端详情服务
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 内存模式
        /**
         * clients.inMemory().withClient("demoApp").secret(bCryptPasswordEncoder.encode("demoAppSecret"))
         .redirectUris("http://baidu.com")// code授权添加
         .authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token")
         // scopes的值就是all(全部权限),read,write等权限。就是第三方访问资源的一个权限,访问范围
         .scopes("all")
         // 这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
         .resourceIds("oauth2-resource")
         // 设置accessTokenValiditySeconds属性来设置Access Token的存活时间。
         .accessTokenValiditySeconds(1200)
         // 设置refreshTokenValiditySeconds属性来设置refresh Token的存活时间。
         .refreshTokenValiditySeconds(50000);
         */

        // 数据库模式
        clients.withClientDetails(clientDetails); // 表中存储的secret值是加密后的值,并非明文;
    }
}

  1. ResourceServerConfig
package com.mingjia.data_control.config.oauth;


import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.GET,"/api/**").hasRole("ADMINISTRATOR")
                .antMatchers("/oauth/confirm_access").permitAll()
                .antMatchers("/**/*.js").permitAll()
                .antMatchers("/favicon.ico").permitAll()
                .and()
                .requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**").authenticated();
    }
}
  1. SecurityConfig
package com.mingjia.data_control.config.oauth;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisConnectionFactory connectionFactory;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")
                .and()
                .authorizeRequests()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .cors().disable();
    }

    @Bean
    public TokenStore tokenStore() {
        // 使用redis存储token信息
        RedisTokenStore redisTokenStore = new RedisTokenStore(connectionFactory);
        return redisTokenStore;

        // 使用jwt内存存储token信息
//		JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
//		return jwtTokenStore;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("healthy");
        return converter;
    }


    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }

    /**
     * *需要配置这个支持password模式 support password grant type
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    public static void main(String[] args) {
        String encode = new BCryptPasswordEncoder().encode("dataApp");
        String password = new BCryptPasswordEncoder().encode("123456");

        System.out.println("encode:"+encode);
        System.out.println("password"+password);
    }
}

创建entity DataUser 要继承UserDetails

package com.mingjia.data_control.modules.base_module.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;


/**
 * 自定义UserDetails类 携带User实例
 */
@Getter
@Setter
@Entity
public class DataUser extends BaseModel implements UserDetails, Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private Long id;

    private String username;

    private String password;


    /**
     * 是否激活
     */
    @Transient
    private Boolean active = true;



    /**
     * 角色
     */
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "data_user_role_map",
            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
    private Set roles = new HashSet<>();





    /**
     * implements UserDetails
     */
    @Override
    public String getUsername() {
        return username;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public Collection getAuthorities() {
        return getGrantedAuthorities(this.getPermissions(roles));
    }

    private List getGrantedAuthorities(List getPermissions) {
        List authorities = new ArrayList<>();
        for (String permission : getPermissions) {
            authorities.add(new SimpleGrantedAuthority(permission));
        }
        return authorities;
    }

    private List getPermissions(Collection roles) {
        List permissions = new ArrayList<>();
        for (DataRole role : roles) {
			permissions.add("ROLE_" + role.getType().toString().toUpperCase());
            for (DataPermission item : role.getPermissions()) {
				permissions.add("OP_" +item.getCode().toUpperCase());
            }
        }

        return permissions;
    }


    @Override
    public boolean isEnabled() {
        return active;
    }

    private static List mapToGrantedRoles(List authorities) {
        return authorities.stream()
                .map(authority -> new SimpleGrantedAuthority(authority.getName()))
                .collect(Collectors.toList());
    }
}

主要验证口:DataUserServiceImpl ,其他的IDataUserService我就不贴了

package com.mingjia.data_control.modules.base_module.service.impl;


import com.mingjia.data_control.modules.base_module.entity.DataPermission;
import com.mingjia.data_control.modules.base_module.entity.DataRole;
import com.mingjia.data_control.modules.base_module.entity.DataUser;
import com.mingjia.data_control.modules.base_module.service.IDataUserService;
import com.mingjia.data_control.modules.base_module.service.query.DataUserQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;


@Component("userDetailsService")
public class DataUserServiceImpl extends BaseServiceImpl implements IDataUserService {

    @Autowired
    private PasswordEncoder passwordEncoder;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Optional optionalDataUser = this.findOne(DataUserQuery.findByUserName(username));
        DataUser dataUser = this.checkOptional(optionalDataUser);
        /**
         * isEnabled 账户是否启用
         * isAccountNonExpired 账户没有过期
         * isCredentialsNonExpired 身份认证是否是有效的
         * isAccountNonLocked 账户没有被锁定
         * 对于 isAccountNonLocked 和 isEnabled 没有做业务处理,只是抛出了对于的异常信息;
         */
        List permissions = new ArrayList<>();
        for (DataRole role : dataUser.getRoles()) {
            permissions.add("ROLE_" + role.getType());
            for (DataPermission item : role.getPermissions()) {
                permissions.add("OP_" + item.getType() + "_" + item.getCode().toUpperCase());

            }
        }
        String authorityString = String.join(",",permissions);
        return new User(username, dataUser.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(authorityString));
    }
}

基本就完成了

application.properties配置

spring.application.name=oauth2-demo
server.port=8090

logging.level.root=info





###################################
# 数据库 MYSQL 设置
###################################
spring.datasource.url=jdbc:mysql://localhost/data_control?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Hibernate ORM 设置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

spring.main-server.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.main-server.datasource.initial-size=5
spring.main-server.datasource.max-active=20
spring.main-server.datasource.min-idle=5
spring.main-server.datasource.max-wait=60000
spring.main-server.datasource.validation-query=SELECT 1 FROM DUAL
spring.main-server.datasource.test-on-borrow=false
spring.main-server.datasource.test-on-return=false
spring.main-server.datasource.test-while-idle=true
spring.main-server.datasource.time-between-eviction-runs-millis=60000
spring.main-server.datasource.min-evictable-idle-time-millis=300000
spring.main-server.datasource.filter.stat.log-slow-sql=true



# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000


数据库创建 - data_control

需要生成一个表oauth_client_details

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('demoApp',
 'oauth2-resource', '$2a$10$z6DXhwPrzQe4wk9nGmqLvO6zQzYEAYscmNaAaDTDVhhuJGrhqZzk.',
 'all', 'authorization_code,client_credentials,password,refresh_token',
  'http://baidu.com', 'ROLE_CLIENT', 3600, 3600, NULL, NULL);

SET FOREIGN_KEY_CHECKS = 1;

完成样子
spring boot 2.1.6.RELEASE配oauth2-2.0版本_第1张图片spring boot 2.1.6.RELEASE配oauth2-2.0版本_第2张图片
spring boot 2.1.6.RELEASE配oauth2-2.0版本_第3张图片
spring boot 2.1.6.RELEASE配oauth2-2.0版本_第4张图片
spring boot 2.1.6.RELEASE配oauth2-2.0版本_第5张图片

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('dataApp',
 'oauth2-resource', '$2a$10$8H0SUv/f0jU1KM2YNjYlouYGurGIWK2phCngtApaiucDFAeihxR5e',
 'all', 'authorization_code,client_credentials,password,refresh_token',
  'http://baidu.com', 'ROLE_CLIENT', 3600, 3600, NULL, NULL);

SET FOREIGN_KEY_CHECKS = 1;

找不到可以码云搜,实测可用
spring boot 2.1.6.RELEASE配oauth2-2.0版本_第6张图片
我用的是码云gitee
项目下载地址:
https://gitee.com/mingjia_xu/data_control/repository/archive/master.zip

你可能感兴趣的:(java)