SpringBootSecurity学习17前后端分离版之-OAuth20-数据库JDBC存储客户端

自动批准授权码前面我们授权的流程中,第一步获取授权码的时候,都会经历一个授权是否同意页面: 这个流程就像第三方登录成功后,提问是否允许获取昵称和头像信息的页面一样,这个过程其实是可以自动同意的,需要在客户端配置中,增加一个自动批准: 这样我们申请授权码直接就可以得到: 在流程需要自动完成的时候,需要这样配置,如果需要用户点击同意,那么这里需要设置为false,不写默认也是false。 客户端信息整理上面的自动批准只是客户端配置中一个小的配置,下面我们来系统整理一下客户端所有可配置的内容。首先来看现在已经配置的6个字段: 上面的6个字段是我们最常用的也是不可或缺的客户端信息。不过客户端的配置还有很多其它的字段,我们来整体看一下: 上面的11个字段基本上包括了所有的第三方客户端的配置内容。下面来一个个详细讲解: withClient 方法:用来配置 client_id ,是必须配置的,用于唯一标识每一个客户端(client);注册时必须填写(也可以服务端自动生成),这个字段是必须的,实际应用也有叫app_keyresourceIds 方法:用来配置resource_ids ,表示客户端能访问的资源id集合,注册客户端时,根据实际需要可选择资源id,也可以根据不同的注册流程,赋予对应的额资源id。我们可以为每一个Resource Server(资源服务)设置一个resourceid。再给client授权的时候,可以设置这个client可以访问哪一些资源实例,如果没设置,就是对所有的resource都有访问权限。secret 方法:用来配置 client_secret ,注册填写或者服务端自动生成,实际应用也有叫app_secret,scopes 方法 :用来配置 scope ,指定client的权限范围,比如读写权限,比如移动端还是web端权限,all表示全部权限authorizedGrantTypes 方法:用来配置 authorized_grant_types ,可选值, 授权码模式:authorization_code,密码模式:password,刷新token: refresh_token, 隐式模式: implicit: 客户端模式: client_credentials。支持多个用逗号分隔redirectUris 方法: 用来配置 web_server_redirect_uri ,客户端重定向uri,authorization_code和implicit需要该值进行校验,注册时填写authorities 方法:用来配置 authorities ,指定用户的权限范围,如果授权的过程需要用户登陆,该字段不生效,implicit和client_credentials需要accessTokenValiditySeconds 方法,用来配置 access_token_validity ,设置access_token的有效时间(秒),默认(12小时)refreshTokenValiditySeconds 方法:用来配置 refresh_token_validity ,设置refresh_token有效期(秒),默认(30天)additionalInformation 方法: 用来配置 additional_information ,表示补充信息,可空,值必须是json格式autoApprove 方法:用来配置autoapprove ,默认false,适用于authorization_code模式,设置用户是否自动approval操作,设置true跳过用户确认授权操作页面,直接跳到redirect_uri下面我们在数据库中新建一张表,定义这是十一个字段,表名为 oauth_client_details: 注意,这里表名必须是 oauth_client_details 是 oauth规定的表名,也是默认的jdbc操作中的表名。我们来增加一条记录: 相比前面的例子,这里多了一个resource_ids的字段值,其它的都和前面的配置一样。注意secret存储的是加密后的密文,加密前是secret。 授权服务多了一个resource_id,资源服务也得配置自己的id: 默认的jdbc管理客户端将配置死的客户端信息改为jdbc从客户端查询的方式很简单,首先引入数据库依赖: ...

October 9, 2019 · 1 min · jiezi

spring源码分析系列3BeanFactory核心容器的研究

在讲容器之前,再明确一下知识点。 BeanDefinition是Bean在容器的描述。BeanDefinition与Bean不是一个东西。Bean是根据BeanDefinition创建出来的。也即是我们所说的对象。BeanDefinition物料需要有地方存储,Bean成品需要有地方存。今天我们讲讲仓库。 BeanFactory家族此图是默认容器DefaultListableBeanFactory的继承,实现关系图.我们从右向左来分析下. BeanFactory接口: 容器顶级接口,提供了容器最基本的能力,包括获取bean,是否包含bean,是否单例,获取bean类型,Bean的别名等方法。ListableBeanFactory接口:BeanFactory的子接口;具有批量获取Bean的能力HierarchicalBeanFactory接口:具有访问父容器的能力。有层次的BeanFactory。AutowireCapableBeanFactory接口:继承BeanFactory,扩展了自动装配能力。这个接口更多的作用是用于于与其他框架集成,把不在spring容器中的Bean加入到Spring容器生命周期管理中来。此接口很少用ConfigurableBeanFactory:定义了BeanFactory的配置。继承HierarchicalBeanFactory和SingletonBeanRegistry接口。实现了此接口的容器,具有层次,单例BeanDefinition注册功能。ConfigurableListableBeanFactory:大融合接口,除了具有上述接口的能外,还具有:类加载器,类型转化,属性编辑器,BeanPostProcessor,作用域,bean定义,处理bean依赖关系, bean销毁等功能。SingletonBeanRegistry接口: 具有Bean的操作能力.注册,查询,获取Bean数量等能力. 注意此处的Bean是实例.区别于BeanDefinition.SimpleAliasRegistry:Bean的别名操作类,实现了AliasRegistry.具有存储Bean别名,注册Bean别名,获取Bean别名的功能.aliasMap属性存储Bean别名DefaultSingletonBeanRegistry:除了继承了SimpleAliasRegistry的功能外. 最重要的是实现了SingletonBeanRegistry接口.具有存储Bean实例,注册Bean,获取Bean的能力.我们定义的被Spring管理的Class类的实例对象,以及实例的之间的相互依赖关系都是存储在此类中.默认常用的容器DefaultListableBeanFactory的Bean的相关能力.就是通过间接继承此类来实现的. /** Disposable bean instances: bean name --> disposable instance */ private final Map<String, Object> disposableBeans = new LinkedHashMap<String, Object>(); /** Map between containing bean names: bean name --> Set of bean names that the bean contains */ private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<String, Set<String>>(16); /** Map between dependent bean names: bean name --> Set of dependent bean names */ private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<String, Set<String>>(64); /** Map between depending bean names: bean name --> Set of bean names for the bean's dependencies */ private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<String, Set<String>>(64)FactoryBeanRegistrySupport: 提供多工厂Bean的支持.FactoryBean通过其名字我也可以看出是生产Bean的Bean.AbstractBeanFactory抽象类: 承上启下.从图中我们看出. AbstractBeanFactory.通过继承关系,继承FactoryBeanRegistrySupport各种能力.而且实现了右边部分接口.已然是比较完备的Bean容器了.AbstractBeanFactory还通过模板模式定义了获取Bean的算法骨架,AbstractAutowireCapableBeanFactory: .具有大部分的能力. 实现了AbstractBeanFactory定义的模板方法.其中doCreateBean方法逻辑是把一个BeanDefinition变成Bean的过程.这个方法非常重要.通常我们使用类创建对象.直接new出来. spring把BeanDefinition到Bean的过程模板化,留下了很多扩展点. 留给使用者可以在不同的时刻自定义BeanDefition创建Bean的过程.DefaultListableBeanFactory常用的默认容器实现,也是spring最常使用的容器类.看左边DefaultListableBeanFactory实现了BeanDefinitionRegistry接口. 这说明什么?说明DefaultListableBeanFactory具有存储BeanDefinition,操作BeanDefinition的能力.DefaultListableBeanFactory通过继承关系也具有了Bean的存储操作功能.小结: ...

October 9, 2019 · 1 min · jiezi

初识BFF架构设计

BFF是(Backends For Frontends)单词的缩写,主要是用于服务前端的后台应用程序,来解决多访问终端业务耦合问题。 最近在公司的微服务架构中遇到了一些多终端访问接口的问题,不同的终端拥有不同的接口服务,有不同的操作数据的能力,针对这种业务场景做出了调研,我们是否可以在不同的访问层进行业务逻辑处理,获取不同的数据内容呢? 早在微服务出现的初期就已经存在类似的业务需求出现,而且衍生出了一套成熟的解决方案,那就是BFF,可以针对不用业务场景来提供对应的服务接口,每一种业务场景之间完全独立。 演进过程在传统的应用程序中,我们一般只将接口提供给一种类型的终端使用。单端调用基础服务 传统的应用程序内提供的接口是有业务针对性的,这种类型的接口如果独立出来再提供给别的系统再次使用是一件比较麻烦的事情,设计初期的高耦合就决定了这一点。 多端直接调用基础服务 如果我们的接口同时提供给web、移动端使用,移动端仅用来采集数据以及数据的展示,而web端大多数场景是用来管理数据,因为不同端点的业务有所不同每一个端的接口复用度不会太高。 多端共用一个BFF 针对多端共用服务接口的场景,我们将基础的数据服务与BFF进行了分离,数据服务仅提供数据的增删改查,并不过多涉及业务的判断处理,所有业务判断处理都交给BFF来把控,遇到的一些业务逻辑异常也同样由BFF格式化处理后展示给访问端点。 这种设计方式同样存在一定的问题,虽然基础服务与BFF进行了分离,我们只需要在BFF层面进行业务判断处理,但是多个端共用一个BFF,也会导致代码编写复杂度增高、代码可阅读性降低、多端业务耦合。 每个端提供一个BFF 如果我们为每一个端点都提供一个BFF,每个端点的BFF处理自身的业务逻辑,需要数据时从基础服务内获取,然后在接口返回之前进行组装数据用于实例化返回对象。 这样基础服务如果有新功能添加,BFF几乎不会受到影响,而我们如果后期把App端点进行拆分成Android、IOS时我们只需要将app-bff进行拆分为android-bff、ios-bff,基础服务同样也不会受到影响 这样每当新增一个访问端点时,我们需要修改的地方也只有网关的转发以及添加一个BFF即可,基础服务内提供的服务接口我们完全可以复用,因为基础服务提供的接口都是没有业务针对性的!!! 总结在微服务架构设计中,BFF起到了一个业务聚合的关键作用,可以 通过openfeign、restTemplate调用基础服务来获取数据,将获取到的数据进行组装返回结果对象,BFF解决了业务场景问题,也同样带来了一些问题,如下所示: 响应时间延迟(服务如果是内网之间访问,延迟时间较低)编写起来较为浪费时间(因为在基础服务上添加的一层转发,所以会多写一部分代码)业务异常处理(统一格式化业务异常的返回内容)分布式事务(微服务的通病)

October 9, 2019 · 1 min · jiezi

SpringBoot详细打印启动时异常堆栈信息

SpringBoot在项目启动时如果遇到异常并不能友好的打印出具体的堆栈错误信息,我们只能查看到简单的错误消息,以致于并不能及时解决发生的问题,针对这个问题SpringBoot提供了故障分析仪的概念(failure-analyzer),内部根据不同类型的异常提供了一些实现,我们如果想自定义该怎么去做? FailureAnalyzerSpringBoot提供了启动异常分析接口FailureAnalyzer,该接口位于org.springframework.boot.diagnosticspackage内。内部仅提供一个分析的方法,源码如下所示: @FunctionalInterfacepublic interface FailureAnalyzer { /** * Returns an analysis of the given {@code failure}, or {@code null} if no analysis * was possible. * @param failure the failure * @return the analysis or {@code null} */ FailureAnalysis analyze(Throwable failure);}该接口会把遇到的异常对象实例Throwable failure交付给实现类,实现类进行自定义处理。 AbstractFailureAnalyzerAbstractFailureAnalyzer是FailureAnalyzer的基础实现抽象类,实现了FailureAnalyzer定义的analyze(Throwable failure)方法,并提供了一个指定异常类型的抽象方法analyze(Throwable rootFailure, T cause),源码如下所示: public abstract class AbstractFailureAnalyzer<T extends Throwable> implements FailureAnalyzer { @Override public FailureAnalysis analyze(Throwable failure) { T cause = findCause(failure, getCauseType()); if (cause != null) { return analyze(failure, cause); } return null; } /** * Returns an analysis of the given {@code rootFailure}, or {@code null} if no * analysis was possible. * @param rootFailure the root failure passed to the analyzer * @param cause the actual found cause * @return the analysis or {@code null} */ protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause); /** * Return the cause type being handled by the analyzer. By default the class generic * is used. * @return the cause type */ @SuppressWarnings("unchecked") protected Class<? extends T> getCauseType() { return (Class<? extends T>) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric(); } @SuppressWarnings("unchecked") protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) { while (failure != null) { if (type.isInstance(failure)) { return (E) failure; } failure = failure.getCause(); } return null; }}通过AbstractFailureAnalyzer源码我们可以看到,它在实现于FailureAnalyzer的接口方法内进行了特殊处理,根据getCauseType()方法获取当前类定义的第一个泛型类型,也就是我们需要分析的指定异常类型。 ...

October 9, 2019 · 2 min · jiezi

漫谈-GOF-设计模式在-Spring-框架中的实现

原文地址:梁桂钊的博客博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」。一群同频者,一起成长,一起精进,打破认知的局限性。 漫谈 GOF 设计模式在 Spring 框架中的实现在开始正文之前,请你先思考几个问题: 你项目中有使用哪些 GOF 设计模式说一说 GOF 23 种设计模式的设计理念说说 Spring 框架中如何实现设计模式假设我是面试官问起了你这些面试题,你该如何回答呢,请先思考一分钟。 好的,我们开始进入正题。设计模式实践里面提供了许多经久不衰的解决方案和最佳方案。这里,GOF 设计模式主要分为三大类:创建模式、结构模式和行为模式。创建模式对于创建对象实例非常有用。结构模式通过处理类或对象的组合来作用于企业级应用的设计结构,从而降低了应用的复杂性,提高了应用的可重用性和性能。行为模式的意图是一组对象之间的交互作用,以执行单个对象无法自己执行的任务。它描述了类或对象交互以及职责的分配。 那么,本文的核心话题是 Spring 如何通过使用大量设计模式和良好实践来构建应用程序。 工厂方法模式Spring 框架使用工厂模式来实现 Spring 容器的 BeanFactory 和 ApplicationContext 接口。Spring 容器基于工厂模式为 Spring 应用程序创建 bean,并管理着每一个 bean 的生命周期。BeanFactory 和 ApplicationContext 是工厂接口,并且在 Spring 中存在有很多实现类。getBean() 方法是相对应的 bean 的工厂方法。 抽象工厂模式在 Spring 框架中,FactoryBean 接口是基于抽象工厂模式设计的。Spring 提供了很多这个接口的实现,比如 ProxyFactoryBean、JndiFactoryBean、LocalSessionFactoryBean、LocalContainerEntityManagerFactoryBean 等。FactoryBean 帮助 Spring 构建它自己无法轻松构建的对象。通常这是用来构造具有许多依赖关系的复杂对象。它也可以根据配置构造高易变的逻辑。例如,在 Spring 框架中,LocalSessionFactoryBean 是 FactoryBean 的一个实现,它用于获取 Hibernate 配置的关联的 bean 的引用。这是一个数据源的特定配置,它在得到 SessionFactory 的对象之前被使用。对此,在一致的情况下可以用 LocalSessionFactoryBean 获取特定的数据源配置。读者可以将 FactoryBean 的 getObject() 方法的返回结果注入到任何其他属性中。 ...

October 9, 2019 · 1 min · jiezi

MongoDBSpring-Data-MongoDB详细的操作手册增删改查

github:https://github.com/Ccww-lx/Sp... 模块:spring-boot-base-mongodb 在NoSQL盛行的时代,App很大可能会涉及到MongoDB数据库的使用,而也必须学会在Spring boot使用Spring Data连接MongoDB进行数据增删改查操作,如下为详细的操作手册。 1. 依赖直接导入spring-data-mongodb包或者使用Spring Boot starter <dependencies> <!-- other dependency elements omitted --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-mongodb</artifactId> <version>2.2.0.RELEASE</version> </dependency></dependencies><!--spring 框架使用最新的 --><spring.framework.version>5.2.0.RELEASE</spring.framework.version><!--用一即可--><!--使用Spring Boot starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>2. 属性文件application.properties#mongodb连接地址,集群用“;”隔开spring.mongo.mongoDatabaseAddress=10.110.112.165:27092;10.110.112.166:27092#mongo数据名spring.mongo.dbname=mongodb#mongo用户spring.mongo.username=mongodbopr#mongo密码spring.mongo.password=123456#mongo最大连接数spring.mongo.connectionsPerHost=503. mongodb 配置注册Mongo实例配置: @Configurationpublic class MongodbConfig { public static final String COMMA = ";"; public static final String COLON = ":"; @Value("${spring.mongo.mongoDatabaseAddress}") private String mongoDatabaseAddress; @Value("${spring.mongo.username}") private String username; @Value("${spring.mongo.dbname}") private String dbname; @Value("${spring.mongo.password}") private String password; @Value("${spring.mongo.connectionsPerHost}") private String connectionsPerHost; /** * 获取mongodb的地址 * * @return */ private List<ServerAddress> getMongoDbAddress() { List<ServerAddress> serverAddrList = new ArrayList<ServerAddress>(); //如果有多个服务器的话 if (this.mongoDatabaseAddress.indexOf(COMMA) > 0) { String[] addressArrays = mongoDatabaseAddress.split(COMMA); String[] hostPort; for (String address : addressArrays) { hostPort = address.split(COLON); ServerAddress serverAdress = new ServerAddress(hostPort[0], Integer.valueOf(hostPort[1])); serverAddrList.add(serverAdress); } } else { String[] hostPort = mongoDatabaseAddress.split(COLON); ServerAddress serverAdress = new ServerAddress(hostPort[0], Integer.valueOf(hostPort[1])); serverAddrList.add(serverAdress); } return serverAddrList; } /** * 设置连接参数 */ private MongoClientOptions getMongoClientOptions() { MongoClientOptions.Builder builder = MongoClientOptions.builder(); // todo 添加其他参数配置 //最大连接数 builder.connectionsPerHost(Integer.valueOf(connectionsPerHost)); MongoClientOptions options = builder.readPreference(ReadPreference.nearest()).build(); return options; } /** * * @return */ @Bean public MongoClient mongoClient() { //使用数据库名、用户名密码登录 MongoCredential credential = MongoCredential.createCredential(username, dbname, password.toCharArray()); //创建Mongo客户端 return new MongoClient(getMongoDbAddress(), credential, getMongoClientOptions()); } /** * 注册mongodb操作类 * @param mongoClient * @return */ @Bean @ConditionalOnClass(MongoClient.class) public MongoTemplate mongoTemplate(MongoClient mongoClient) { MongoTemplate mongoTemplate = new MongoTemplate(new SimpleMongoDbFactory(mongoClient, dbname)); return mongoTemplate; }}4. mongodb操作使用MongoTemplate类进行增删改查 ...

October 9, 2019 · 3 min · jiezi

SpringBoot嵌入式Tomcat的自动配置原理

在读本篇文章之前如果你读过这篇文章SpringBoot自动装配原理解析应该会更加轻松准备工作我们知道SpringBoot的自动装配的秘密在org.springframework.boot.autoconfigure包下的spring.factories文件中,而嵌入Tomcat的原理就在这个文件中加载的一个配置类:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration @Configuration@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)@ConditionalOnClass(ServletRequest.class)@ConditionalOnWebApplication(type = Type.SERVLET)@EnableConfigurationProperties(ServerProperties.class)@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })public class ServletWebServerFactoryAutoConfiguration { @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new ServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } /** * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via * {@link ImportBeanDefinitionRegistrar} for early registration. */ public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (this.beanFactory == null) { return; } registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class); registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class); } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) { if (ObjectUtils.isEmpty( this.beanFactory.getBeanNamesForType(beanClass, true, false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name, beanDefinition); } } }}首先看一下上方的几个注解 ...

October 9, 2019 · 4 min · jiezi

SpringBootSecurity学习16前后端分离版之-OAuth20-加密配置

示例代码的改进前面使用spring cloud security和spring cloud oauth2写了一个第三方授权的例子,例子非常的简单,主要目的是用来熟悉OAuth2.0 申请授权的整个流程,这个简单的示例肯定是不能直接用于生产环境的,还有很多需要改进的地方,我们来总结一下: 1、只演示了授权码的形式,其它的三种(隐藏式,密码式,客户端凭证)并没有熟悉2、密码和秘钥是未加密的3、oauth的客户端配置,包括id,秘钥等信息是在内存中配置死的,无法动态增加4、token也是放在内存中存储的,无法手动废止5、jwt的生成使用的是简单的秘钥形式,最好使用非对称加密的方式,更加安全可靠6、令牌的申请权限配置不太合理7、资源服务中的jwt和令牌验证配置也不太灵活8、授权流程应该结合eureka开发和使用9、没有验证令牌中继特性,等等等等从上面列举的问题可以看出,就算示例代码能运行,流程也清楚,但是要学会使用Spring Cloud Security,尤其要在生产环境使用,还需要对每个细节都能掌握和了解才行。 加密配置首先我们来改进第一个地方,实现加密配置。大家知道用户注册之后,存储用户资料的时候,用户密码在数据库中最好是加密后再保存,同样的道理,oauth的客户端信息最终也是要存储在数据库而不是直接在代码中配置死的,它的secret字段也应该加密后再存储,下面在授权服务中来配置加密,首先修改security配置类,配置加密方式: 然后将登陆用户的密码配置为加密: 然后修改授权配置类,将客户端配置中的secret配置为加密: 这样加密配置就完成了。资源服务不用做任何修改。 测试配置上面三个加密的地方后,加密就配置好了,下面来测试一下,其实测试流程与前面的一样,客户端感知不到,得到的access_token如下: 最终访问结果如下: 代码地址:https://gitee.com/blueses/spr... 17 18

October 8, 2019 · 1 min · jiezi

ApiBoot-新官网发布-丰富使用文档

ApiBoot 简介ApiBoot为接口服务而生,基于SpringBoot完成扩展、自动化配置,通过封装一系列Starter来让调用者快速集成组件,降低学习、使用门槛,提高开发效率。 ApiBoot 官网官网地址:http://apiboot.minbox.io ApiBoot官网初版已发布,内容大致包含: 文档 更新日志 更新日志ApiBoot 迭代版本更新日志) 源码、文档地址GitHub Wiki:https://github.com/hengboy/api-boot/wiki 码云 Wiki:https://gitee.com/hengboy/api-boot/wikis ApiBoot 目前集成组件ApiBoot提供的所有封装依赖对应第三方框架关系如下所示: 依赖名称介绍api-boot-starter所有Starter的基础依赖ApiBoot 整合案例ApiBoot落地使用示例,是恒宇少年知识库小程序接口源码api-boot-starter-http-converter集成FastJson作为格式化返回JSONapi-boot-starter-security-oauth-jwt集成SpringSecurity、Oauth、Jwt安全、认证框架api-boot-starter-swagger集成Swagger2作为接口服务文档api-boot-starter-alibaba-oss集成阿里云Oss对象存储接口服务api-boot-starter-alibaba-sms集成阿里云国际短信接口服务api-boot-starter-quartz集成分布式定时任务框架Quartzapi-boot-starter-datasource-switch集成支持多数据源自动切换、动态创建数据源api-boot-starter-resource-load资源与业务完全分离、自动化读取api-boot-starter-message-push推送服务,集成极光推送api-boot-starter-rate-limiter接口QPS限流api-boot-starter-mybatis-enhance集成Myabtis Enhance 持久化框架api-boot-starter-mybatis-pageable集成Mybatis Pageable 自动分页插件api-boot-mybatis-enhance-maven-codegenMybatis Enhance专属代码插件**点击依赖名称跳转示例~~~

October 8, 2019 · 1 min · jiezi

Spring5源码解析6ConfigurationClassParser-解析配置类

ConfigurationClassParser在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了ConfigurationClassParser对象并调用其parse方法。该方法就是在负责解析配置类、扫描包、注册BeanDefinition,源码如下: //ConfigurationClassParser#parseSet<BeanDefinitionHolder>) 方法源码public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // 根据不同的 BeanDefinition 实例对象 调用不同的 parse 方法 // 底层其实都是在调用 org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } //执行DeferredImportSelector this.deferredImportSelectorHandler.process();}在该方法内部根据不同的BeanDefinition实例对象,调用了不同的parse方法,而这些parse方法底层,实际上都是调用了ConfigurationClassParser#processConfigurationClass方法。 ...

October 8, 2019 · 5 min · jiezi

springbootplus集成SpringBootShiroJWT权限管理

SpringBoot+Shiro+JWT权限管理ShiroApache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。三个核心组件:Subject, SecurityManager 和 Realms. Subject代表了当前用户的安全操作,即“当前操作用户”。SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。ShiroBasicArchitecture ShiroArchitecture JWTJSON Web Token(JWT)是目前最流行的跨域身份验证解决方案JSON Web令牌是一种开放的行业标准 RFC 7519方法,用于在双方之间安全地表示声明。JWT 数据结构eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJodHRwczovL3NwcmluZ2Jvb3QucGx1cyIsIm5hbWUiOiJzcHJpbmctYm9vdC1wbHVzIiwiaWF0IjoxNTE2MjM5MDIyfQ.1Cm7Ej8oIy1P5pkpu8-Q0B7bTU254I1og-ZukEe84II JWT有三部分组成:Header:头部,Payload:负载,Signature:签名SpringBoot+Shiro+JWTpom.xml Shiro依赖<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.4.1</version></dependency>pom.xml JWT依赖<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version></dependency>ShiroConfig.java配置@Slf4j@Configurationpublic class ShiroConfig { /** * JWT过滤器名称 */ private static final String JWT_FILTER_NAME = "jwtFilter"; /** * Shiro过滤器名称 */ private static final String SHIRO_FILTER_NAME = "shiroFilter"; @Bean public CredentialsMatcher credentialsMatcher() { return new JwtCredentialsMatcher(); } /** * JWT数据源验证 * * @return */ @Bean public JwtRealm jwtRealm(LoginRedisService loginRedisService) { JwtRealm jwtRealm = new JwtRealm(loginRedisService); jwtRealm.setCachingEnabled(false); jwtRealm.setCredentialsMatcher(credentialsMatcher()); return jwtRealm; } /** * 禁用session * * @return */ @Bean public DefaultSessionManager sessionManager() { DefaultSessionManager manager = new DefaultSessionManager(); manager.setSessionValidationSchedulerEnabled(false); return manager; } @Bean public SessionStorageEvaluator sessionStorageEvaluator() { DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator(); sessionStorageEvaluator.setSessionStorageEnabled(false); return sessionStorageEvaluator; } @Bean public DefaultSubjectDAO subjectDAO() { DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO(); defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator()); return defaultSubjectDAO; } /** * 安全管理器配置 * * @return */ @Bean public DefaultWebSecurityManager securityManager(LoginRedisService loginRedisService) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jwtRealm(loginRedisService)); securityManager.setSubjectDAO(subjectDAO()); securityManager.setSessionManager(sessionManager()); SecurityUtils.setSecurityManager(securityManager); return securityManager; } /** * ShiroFilterFactoryBean配置 * * @param securityManager * @param loginRedisService * @param shiroProperties * @param jwtProperties * @return */ @Bean(SHIRO_FILTER_NAME) public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, LoginService loginService, LoginRedisService loginRedisService, ShiroProperties shiroProperties, JwtProperties jwtProperties) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filterMap = new HashedMap(); filterMap.put(JWT_FILTER_NAME, new JwtFilter(loginService, loginRedisService, jwtProperties)); shiroFilterFactoryBean.setFilters(filterMap); Map<String, String> filterChainMap = shiroFilterChainDefinition(shiroProperties).getFilterChainMap(); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap); return shiroFilterFactoryBean; } /** * Shiro路径权限配置 * * @return */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(ShiroProperties shiroProperties) { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // 获取ini格式配置 String definitions = shiroProperties.getFilterChainDefinitions(); if (StringUtils.isNotBlank(definitions)) { Map<String, String> section = IniUtil.parseIni(definitions); log.debug("definitions:{}", JSON.toJSONString(section)); for (Map.Entry<String, String> entry : section.entrySet()) { chainDefinition.addPathDefinition(entry.getKey(), entry.getValue()); } } // 获取自定义权限路径配置集合 List<ShiroPermissionConfig> permissionConfigs = shiroProperties.getPermissionConfig(); log.debug("permissionConfigs:{}", JSON.toJSONString(permissionConfigs)); if (CollectionUtils.isNotEmpty(permissionConfigs)) { for (ShiroPermissionConfig permissionConfig : permissionConfigs) { String url = permissionConfig.getUrl(); String[] urls = permissionConfig.getUrls(); String permission = permissionConfig.getPermission(); if (StringUtils.isBlank(url) && ArrayUtils.isEmpty(urls)) { throw new ShiroConfigException("shiro permission config 路径配置不能为空"); } if (StringUtils.isBlank(permission)) { throw new ShiroConfigException("shiro permission config permission不能为空"); } if (StringUtils.isNotBlank(url)) { chainDefinition.addPathDefinition(url, permission); } if (ArrayUtils.isNotEmpty(urls)) { for (String string : urls) { chainDefinition.addPathDefinition(string, permission); } } } } // 最后一个设置为JWTFilter chainDefinition.addPathDefinition("/**", JWT_FILTER_NAME); Map<String, String> filterChainMap = chainDefinition.getFilterChainMap(); log.debug("filterChainMap:{}", JSON.toJSONString(filterChainMap)); return chainDefinition; } /** * ShiroFilter配置 * * @return */ @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName(SHIRO_FILTER_NAME); filterRegistrationBean.setFilter(proxy); filterRegistrationBean.setAsyncSupported(true); filterRegistrationBean.setEnabled(true); filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC); return filterRegistrationBean; } @Bean public Authenticator authenticator(LoginRedisService loginRedisService) { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setRealms(Arrays.asList(jwtRealm(loginRedisService))); authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy()); return authenticator; } /** * Enabling Shiro Annotations * * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * depends-on lifecycleBeanPostProcessor * * @return */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; }}JWT过滤器配置@Slf4jpublic class JwtFilter extends AuthenticatingFilter { private LoginService loginService; private LoginRedisService loginRedisService; private JwtProperties jwtProperties; public JwtFilter(LoginService loginService, LoginRedisService loginRedisService, JwtProperties jwtProperties) { this.loginService = loginService; this.loginRedisService = loginRedisService; this.jwtProperties = jwtProperties; } /** * 将JWT Token包装成AuthenticationToken * * @param servletRequest * @param servletResponse * @return * @throws Exception */ @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { String token = JwtTokenUtil.getToken(); if (StringUtils.isBlank(token)) { throw new AuthenticationException("token不能为空"); } if (JwtUtil.isExpired(token)) { throw new AuthenticationException("JWT Token已过期,token:" + token); } // 如果开启redis二次校验,或者设置为单个用户token登陆,则先在redis中判断token是否存在 if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) { boolean redisExpired = loginRedisService.exists(token); if (!redisExpired) { throw new AuthenticationException("Redis Token不存在,token:" + token); } } String username = JwtUtil.getUsername(token); String salt; if (jwtProperties.isSaltCheck()){ salt = loginRedisService.getSalt(username); }else{ salt = jwtProperties.getSecret(); } return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond()); } /** * 访问失败处理 * * @param request * @param response * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); HttpServletResponse httpServletResponse = WebUtils.toHttp(response); // 返回401 httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 设置响应码为401或者直接输出消息 String url = httpServletRequest.getRequestURI(); log.error("onAccessDenied url:{}", url); ApiResult apiResult = ApiResult.fail(ApiCode.UNAUTHORIZED); HttpServletResponseUtil.printJSON(httpServletResponse, apiResult); return false; } /** * 判断是否允许访问 * * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { String url = WebUtils.toHttp(request).getRequestURI(); log.debug("isAccessAllowed url:{}", url); if (this.isLoginRequest(request, response)) { return true; } boolean allowed = false; try { allowed = executeLogin(request, response); } catch (IllegalStateException e) { //not found any token log.error("Token不能为空", e); } catch (Exception e) { log.error("访问错误", e); } return allowed || super.isPermissive(mappedValue); } /** * 登陆成功处理 * * @param token * @param subject * @param request * @param response * @return * @throws Exception */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { String url = WebUtils.toHttp(request).getRequestURI(); log.debug("鉴权成功,token:{},url:{}", token, url); // 刷新token JwtToken jwtToken = (JwtToken) token; HttpServletResponse httpServletResponse = WebUtils.toHttp(response); loginService.refreshToken(jwtToken, httpServletResponse); return true; } /** * 登陆失败处理 * * @param token * @param e * @param request * @param response * @return */ @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { log.error("登陆失败,token:" + token + ",error:" + e.getMessage(), e); return false; }}JWT Realm配置@Slf4jpublic class JwtRealm extends AuthorizingRealm { private LoginRedisService loginRedisService; public JwtRealm(LoginRedisService loginRedisService) { this.loginRedisService = loginRedisService; } @Override public boolean supports(AuthenticationToken token) { return token != null && token instanceof JwtToken; } /** * 授权认证,设置角色/权限信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { log.debug("doGetAuthorizationInfo principalCollection..."); // 设置角色/权限信息 String token = principalCollection.toString(); // 获取username String username = JwtUtil.getUsername(token); // 获取登陆用户角色权限信息 LoginSysUserRedisVo loginSysUserRedisVo = loginRedisService.getLoginSysUserRedisVo(username); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 设置角色 authorizationInfo.setRoles(loginSysUserRedisVo.getRoles()); // 设置权限 authorizationInfo.setStringPermissions(loginSysUserRedisVo.getPermissions()); return authorizationInfo; } /** * 登陆认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { log.debug("doGetAuthenticationInfo authenticationToken..."); // 校验token JwtToken jwtToken = (JwtToken) authenticationToken; if (jwtToken == null) { throw new AuthenticationException("jwtToken不能为空"); } String salt = jwtToken.getSalt(); if (StringUtils.isBlank(salt)) { throw new AuthenticationException("salt不能为空"); } return new SimpleAuthenticationInfo( jwtToken, salt, getName() ); }}更多配置:https://github.com/geekidea/spring-boot-plusapplication.yml配置############################## spring-boot-plus start ##############################spring-boot-plus: ######################## Spring Shiro start ######################## shiro: # shiro ini 多行字符串配置 filter-chain-definitions: | /=anon /static/**=anon /templates/**=anon # 权限配置 permission-config: # 排除登陆登出相关 - urls: /login,/logout permission: anon # 排除静态资源 - urls: /static/**,/templates/** permission: anon # 排除Swagger - urls: /docs,/swagger-ui.html, /webjars/springfox-swagger-ui/**,/swagger-resources/**,/v2/api-docs permission: anon # 排除SpringBootAdmin - urls: /,/favicon.ico,/actuator/**,/instances/**,/assets/**,/sba-settings.js,/applications/** permission: anon # 测试 - url: /sysUser/getPageList permission: anon ######################## Spring Shiro end ########################## ############################ JWT start ############################# jwt: token-name: token secret: 666666 issuer: spring-boot-plus audience: web # 默认过期时间1小时,单位:秒 expire-second: 3600 # 是否刷新token refresh-token: true # 刷新token的时间间隔,默认10分钟,单位:秒 refresh-token-countdown: 600 # redis校验jwt token是否存在,可选 redis-check: true # true: 同一个账号只能是最后一次登陆token有效,false:同一个账号可多次登陆 single-login: false # 盐值校验,如果不加自定义盐值,则使用secret校验 salt-check: true ############################ JWT end ############################################################## spring-boot-plus end ###############################Redis存储信息使用Redis缓存JWTToken和盐值:方便鉴权,token后台过期控制等Redis二次校验和盐值校验是可选的127.0.0.1:6379> keys *1) "login:user:token:admin:0f2c5d670f9f5b00201c78293304b5b5"2) "login:salt:admin"3) "login:user:admin"4) "login:token:0f2c5d670f9f5b00201c78293304b5b5"Redis存储的JwtToken信息127.0.0.1:6379> get login:token:0f2c5d670f9f5b00201c78293304b5b5{ "@class": "io.geekidea.springbootplus.shiro.vo.JwtTokenRedisVo", "host": "127.0.0.1", "username": "admin", "salt": "f80b2eed0110a7ea5a94c35cbea1fe003d9bb450803473428b74862cceb697f8", "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWIiLCJpc3MiOiJzcHJpbmctYm9vdC1wbHVzIiwiZXhwIjoxNTcwMzU3ODY1LCJpYXQiOjE1NzAzNTQyNjUsImp0aSI6IjE2MWQ1MDQxZmUwZjRmYTBhOThjYmQ0ZjRlNDI1ZGQ3IiwidXNlcm5hbWUiOiJhZG1pbiJ9.0ExWSiniq7ThMXfqCOi9pCdonY8D1azeu78_vLNa2v0", "createDate": [ "java.util.Date", 1570354265000 ], "expireSecond": 3600, "expireDate": [ "java.util.Date", 1570357865000 ]}ReferenceShirohttps://shiro.apache.org/spring.htmlhttps://shiro.apache.org/spring-boot.htmlJWThttps://jwt.io/https://github.com/auth0/java-jwtspring-boot-plushttps://github.com/geekidea/spring-boot-plushttps://springboot.plus/guide/shiro-jwt.html

October 8, 2019 · 6 min · jiezi

SpringBootSecurity学习15前后端分离版之-OAuth20简单示例

OAuth2.0OAuth 引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。客户端来申请资源,资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。也就是说,OAuth 的核心就是向第三方应用颁发令牌。而且,OAuth 2.0 规定了四种获得令牌的流程。你可以选择最适合自己的那一种,向第三方应用颁发令牌。 具体的OAuth学习建议仔细研读阮一峰的教程, http://www.ruanyifeng.com/blo...下面我们来使用spring cloud security 和 spring cloud oauth2两个组件来简单实现授权流程。 授权服务下面我们来使用spring cloud security 实现一个授权服务,首先来引入依赖: 除了一个web组件,只引入了一个spring-cloud-starter-oauth2,这是因为spring cloud下的oauth2组件已经包含了security: 首先写一个正常的登录功能,application配置文件和启动类都不用增加特殊配置,主要来配置security配置类: 这里面基本没有特殊的配置,都是前面遇到过的熟悉的配置。有了这个配置类,基本的登录功能就有了,要想有授权功能,还需要一个授权配置类,授权配置类需要继承 AuthorizationServerConfigurerAdapter 类,并引入 @EnableAuthorizationServer 注解: 首先配置一个客户端: 然后配置token的存储和管理,此处使用secret作为秘钥,后面会介绍使用非对称加密的方式 : 上面的token存储在了内存中,token也可以存储在数据库或者redis中。最后配置授权端点的访问控制: 以上就是一个简答的授权服务。 资源服务下面来搭建一个资源服务,其实授权和资源服务是可以合二为一的,此处为了清晰,将它们分开。pom中引入的依赖和授权服务是一样的,同样,配置文件和启动类不需要做特殊配置。首先来写两个简单的接口,一个定义为受保护,另一个不受保护: 然后定义一个资源服务配置类,需要继承 ResourceServerConfigurerAdapter 类,并引入 @EnableResourceServer 注解: 首先来看令牌验证的配置: 然后来看接口资源的拦截规则: save开头的可以直接访问,不会被拦截,/user/save接口会被验证。 注意上面配置的clientId和secret都是单一的配置死的,如果需要对多客户端动态进行认真,需要重写,后面是通过http调用的方式解析访问令牌(主要是通过访问授权服务的/oauth/check_token解析)。 测试我们来根据前面说到的流程测试,首先向授权服务申请一个授权码: http://localhost:8015/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=http://localhost:8015/访问首先会跳转到登录页面: 输入配置中默认的用户名密码登录,然后进入下一个页面: 这个页面是真正的授权页面,选择Approve,点击按钮,同意授权,授权码会通过回调地址获取,如下图: 然后携带授权码申请访问令牌,需要访问下面的地址(需要使用post方式): http://localhost:8015/oauth/token?grant_type=authorization_code&code=授权码&redirect_uri=http://localhost:8015/&client_id=clientId&client_secret=secret将授权码替换上面地址中的授权码三个字,然后在postman中访问: 其返回结果中,包含access_token参数,就是我们需要的访问令牌,token_type 参数说明了令牌的类型,一般类型为bearer或者refresh_token可以在访问令牌过期后向授权服务申请新的令牌,expires_in参数是令牌的有效时间,单位是秒,图中显示默认是12个小时。令牌已经得到了,下面来访问资源接口,首先试一下不受保护的资源: 可以看到直接就能访问,然后访问受保护的资源接口: 得到这样的结果说明该接口需要认证,我们来使用我们前面得到的令牌来访问,首先选择正确的认真协议: ...

October 8, 2019 · 1 min · jiezi

Spring-Boot-使用-JWT-进行身份和权限验证

上周写了一个 适合初学者入门 Spring Security With JWT 的 Demo,这篇文章主要是对代码中涉及到的比较重要的知识点的说明。 适合初学者入门 Spring Security With JWT 的 Demo 这篇文章中说到了要在十一假期期间对代码进行讲解说明,但是,你们懂得,到了十一就一拖再拖,眼看着今天就是十一的尾声了,抽了一下午完成了这部分内容。 明天就要上班了,擦擦眼泪,还是一条好汉!扶我起来,学不动了。 如果 对 JWT 不了解的话,可以看前几天发的这篇原创文章:《一问带你区分清楚Authentication,Authorization以及Cookie、Session、Token》。 Demo 地址:https://github.com/Snailclimb... 。 Controller这个是 UserControler 主要用来验证权限配置是否生效。 getAllUser()方法被注解@PreAuthorize("hasAnyRole('ROLE_DEV','ROLE_PM')")修饰代表这个方法可以被 DEV,PM 这两个角色访问,而deleteUserById() 被注解@PreAuthorize("hasAnyRole('ROLE_ADMIN')")修饰代表只能被 ADMIN 访问。 /** * @author shuang.kou */@RestController@RequestMapping("/api")public class UserController { private final UserService userService; private final CurrentUser currentUser; public UserController(UserService userService, CurrentUser currentUser) { this.userService = userService; this.currentUser = currentUser; } @GetMapping("/users") @PreAuthorize("hasAnyRole('ROLE_DEV','ROLE_PM')") public ResponseEntity<Page<User>> getAllUser(@RequestParam(value = "pageNum", defaultValue = "0") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) { System.out.println("当前访问该接口的用户为:" + currentUser.getCurrentUser().toString()); Page<User> allUser = userService.getAllUser(pageNum, pageSize); return ResponseEntity.ok().body(allUser); } @DeleteMapping("/user") @PreAuthorize("hasAnyRole('ROLE_ADMIN')") public ResponseEntity<User> deleteUserById(@RequestParam("username") String username) { userService.deleteUserByUserName(username); return ResponseEntity.ok().build(); }}安全认证工具类里面主要有一些常用的方法比如 生成 token 以及解析 token 获取相关信息等等方法。 ...

October 7, 2019 · 4 min · jiezi

并发编程之多线程线程安全

一、什么是线程安全?为什么有线程安全问题?当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。 案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 代码: public class ThreadTrain implements Runnable { private int trainCount = 100; @Override public void run() { while (trainCount > 0) { try { Thread.sleep(50); } catch (Exception e) { } sale(); } } public void sale() { if (trainCount > 0) { System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票"); trainCount--; } } public static void main(String[] args) { ThreadTrain threadTrain = new ThreadTrain(); Thread t1 = new Thread(threadTrain, "①号"); Thread t2 = new Thread(threadTrain, "②号"); t1.start(); t2.start(); }}运行结果: ...

October 7, 2019 · 3 min · jiezi

Spring中Import的各种用法以及ImportAware接口

@Import 注解@Import注解提供了和XML中<import/>元素等价的功能,实现导入的一个或多个配置类。@Import即可以在类上使用,也可以作为元注解使用。 @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Import { /** * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} * or regular component classes to import. */ Class<?>[] value();}注解中只有一个value();。支持导入@Configuration标注的配置类,实现ImportSelector接口的类、实现ImportBeanDefinitionRegistrar接口的类和普通的@component类。 作为元注解使用@Import可以作为元注解使用,可以在@Import的继承上封装一层。我的理解是,这样做不会对外(使用方)暴露我内部的具体实现细节。 举个例子:例如@EnableAspectJAutoProxy注解。 @Import(AspectJAutoProxyRegistrar.class)public @interface EnableAspectJAutoProxy {@EnableAspectJAutoProxy就是被@Import这个元注解所标志了,我们(程序员)通过使用@EnableAspectJAutoProxy来开启AspectJAutoProxy,而Spring底层是通过@Import导入相应的配置类来实现的。 导入实现ImportSelector接口的类先来看一下ImportSelector接口,该接口中只有一个方法: public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata);}ImportSelector,输入选择器。该接口就是用来根据给定的条件,选择导入哪些配置类。 举个例子:例如@EnableTransactionManagement注解。 @Import(TransactionManagementConfigurationSelector.class)public @interface EnableTransactionManagement {在@EnableTransactionManagement注解中使用了@Import(TransactionManagementConfigurationSelector.class)注解,其中TransactionManagementConfigurationSelector类就是实现了ImportSelector接口。 public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] { TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME}; default: return null; } }}方法的内部实现逻辑也很简单,就是根据不同的AdviceMode导入不同的配置类,来实现事务管理。 ...

October 7, 2019 · 2 min · jiezi

Spring-Boot-整合视图层技术

这一节我们主要学习如何整合视图层技术: JspFreemarkerThymeleaf在之前的案例中,我们都是通过 @RestController 来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢? Spring Boot推荐使用模板引擎 模板引擎实现伪html 达到seo优化 使动态页面静态化 在动态html上实现Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。 Spring Boot提供了默认配置的模板引擎主要有以下几种: Thymeleaf FreeMarker Velocity Groovy Mustache Spring Boot建议使用这些模板引擎,避免使用jsp。 Jsp创建项目 创建 war 项目,编写pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.springboot</groupId> <artifactId>springboot-view</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>springboot-view Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <dependencies> <!-- Web 组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot不推荐使用 jsp,所以需要手动导入 jstl 和 jasper 依赖 --> <!-- jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- jasper --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> </dependencies></project>视图解析器 resources/application.properties ...

October 7, 2019 · 6 min · jiezi

并发编程之多线程Java

一、线程与进程区别每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行 使用线程可以把占据时间长的程序中的任务放到后台去处理,程序的运行速度可能加快,在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换,更多的线程需要更多的内存空间,线程的中止需要考虑其对程序运行的影响。通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。 总结:进程是所有线程的集合,每一个线程是进程中的一条执行路径。 二、为什么要使用多线程?多线程提高程序执行效率。如: 迅雷多线程下载、数据库连接池、分批发送短信等。 三、多线程创建方式1、第一种继承Thread类 重写run方法/** * * @classDesc: 功能描述:(创建多线程例子-Thread类 重写run方法) */class CreateThread extends Thread { // run方法中编写 多线程需要执行的代码 public void run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } }}public class ThreadDemo { public static void main(String[] args) { System.out.println("-----多线程创建开始-----"); // 1.创建一个线程 CreateThread createThread = new CreateThread(); // 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法 System.out.println("-----多线程创建启动-----"); createThread.start(); System.out.println("-----多线程创建结束-----"); }}2、第二种实现Runnable接口,重写run方法/** * * @classDesc: 功能描述:(创建多线程例子-Thread类 重写run方法) */class CreateRunnable implements Runnable { @Override publicvoid run() { for (inti = 0; i< 10; i++) { System.out.println("i:" + i); } }}/** * * @classDesc: 功能描述:(实现Runnable接口,重写run方法) */public class ThreadDemo2 { public static void main(String[] args) { System.out.println("-----多线程创建开始-----"); // 1.创建一个线程 CreateRunnable createThread = new CreateRunnable(); // 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法 System.out.println("-----多线程创建启动-----"); Thread thread = new Thread(createThread); thread.start(); System.out.println("-----多线程创建结束-----"); }}3、第三种使用匿名内部类方式public class ThreadDemo3 { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { for (int i = 0; i< 10; i++) { System.out.println("i:" + i); } } }); thread.start(); }}四、常用APi使用继承Thread类还是使用实现Runnable接口好?使用实现实现Runnable接口好,原因实现了接口还可以继续继承,继承了类不能再继承。 ...

October 6, 2019 · 2 min · jiezi

Spring-FORM-标签库

Spring FORM标签库Spring MVC提供了一个JSP标签库(Spring Form),使将表单元素绑定到Model 数据变得更加容易。Spring Framework 提供了一些标签,用于显示 错误,设置主题和输出国际化消息。 使用Spring Form标签库的语法需要在jsp文件头部加上: <%@taglib uri="http://www.springframework.org/tags/form" prefix="form">本示例中使用的表单标签呈现HTML “form” 标签,并向内部标签公开绑定路径以进行绑定。使用绑定值呈现类型为“text”的HTML“input”标记。在HTML'span'标签中呈现字段错误。使用绑定值呈现类型为“ password”的HTML “input”标签。呈现类型为“radio”的HTML“ input”标签。呈现HTML“select”元素。支持数据绑定到所选选项。呈现单个HTML“option”。根据绑定值适当设置“selected”。呈现HTML“textarea”。呈现类型为“checkbox”的HTML“input”标签。简单注册表单示例1.修改web.xml以配置Dispatcher Servlet。web.xml <?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> <session-config> <session-timeout> 30 </session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>创建一个dispatcher-servlet.xml文件,其中包含所有用于处理用户请求的配置bean,它处理用户请求并将其分派到各个控制器。dispatcher-servlet.xml <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"><map><entry key="/index.html"><ref bean="registrationController"/></entry></map></property> </bean><bean id="registrationValidator" class="registrationValidator"/><bean id="registrationController" class="RegistrationFormController"><property name="sessionForm"><value>false</value></property><property name="commandName" value="registration"></property><property name="commandClass" value="Registration"></property><property name="validator"><ref bean="registrationValidator"/></property><property name="formView"><value>index</value></property><property name="successView"><value>success</value></property></bean><bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages"/> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /></beans>3.创建一个Jsp文件以从用户index.jsp接收输入,该文件包含带有Spring Form标签的所有表单字段。 index.jsp ...

October 6, 2019 · 3 min · jiezi

Spring5源码分析5ConfigurationClassPostProcessor-上

接上回,我们讲到了refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory)方法主要在执行BeanFactoryPostProcessor和其子接口BeanDefinitionRegistryPostProcessor的方法。 在创建AnnotationConfigApplicationContext对象时Spring就添加了一个非常重要的BeanFactoryPostProcessor接口实现类:ConfigurationClassPostProcessor。注意,这里说的添加只是添加到容器的beanDefinitionMap中,还没有创建真正的实例Bean。 简单回顾一下ConfigurationClassPostProcessor是在什么时候被添加到容器中的:在AnnotationConfigApplicationContext的无参构造器中创建AnnotatedBeanDefinitionReader对象时会向传入的BeanDefinitionRegistry中注册解析注解配置类相关的processors的BeanDefinition,ConfigurationClassPostProcessor就是在此处被添加到容器中的。 ConfigurationClassPostProcessor先看一些ConfigurationClassPostProcessor的继承体系: ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,也就拥有了在Spring容器启动时,往容器中注册BeanDefinition的能力。 我们知道,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法是在refresh();方法中的invokeBeanFactoryPostProcessors(beanFactory);中被执行的,下面我们就一起来看一下该方法。 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry);}主要的逻辑在processConfigBeanDefinitions(registry);中,点开源码: public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); //获取所有的BeanDefinitionName String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); // https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts // Full @Configuration vs “lite” @Bean mode if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } // 校验是否为配置类 // 配置类分为两种 Full @Configuration vs “lite” @Bean mode // 校验之后在 BeanDefinition 中添加标志属性 // 如果满足条件则加入到configCandidates else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 如果是配置类,就放到 configCandidates 变量中 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; // 传入的 registry 是 DefaultListableBeanFactory if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { //获取自定义BeanNameGenerator,一般情况下为空 BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class // new ConfigurationClassParser,用来解析 @Configuration 类 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // 将 configCandidates 转成 set candidates , 去重 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { // 解析配置类 parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // Import类,@Bean,@ImportResource 转化为 BeanDefinition this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); // 再获取一下容器中BeanDefinition的数据,如果发现数量增加了,说明有新的BeanDefinition被注册了 if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); }}获取所有的BeanDefinitionNames,然后循环这个数组,判断其是否为配置类。 ...

October 6, 2019 · 3 min · jiezi

SpringBootSecurity学习14前后端分离版之-OAuth20介绍

登录总结前面基本介绍了security的常规用法,同时介绍了JWT和它的一个简单实现,基本上开发中遇到的登录问题都能解决了,即使在分布式开发,或者微服务开发中实现登录也基本没有问题了。security本身已经实现的比较完善的安全处理,加上JWT的验证方式,可以实现一个理想的登录功能。 我们来看登录,给用户一个账号,验证有效后登录成功,这一步是任何系统都无法避免的。无论这个账号只能登录一个系统还是像支付宝账号一样登录多个app,无论账号是用户名密码,还是手机验证码,或者邮箱等其他形式,可以说认证这一步是最基础的,无法避免。 登录成功后,通过授权可以让用户访问一些登录前无法访问的页面或者接口,而且无论session或者token,其实都是有有效期的,过了有效期就需要重新登录。从这种形式上看,授权包含了更多的场景,不仅是内部已经登录的用户,还有可能是第三方的应用,或者两个系统之间的信息交换等等。而且微服务的开发模式下,服务越来越多,可以被授权的内容也越来越多,如果没有统一的方式来管理这些接口资源的授权,会非常麻烦。因此,系统针对所有的访问需要有统一的认证和授权的机制,而 OAuth2.0 是我们实现这种统一认证授权非常好的一个选择。 OAuth2.0介绍OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。最经典的场景就是我们使用QQ来进行第三方登录的时候,选择可以访问用户的哪些信息。关于OAuth 2.0的介绍,推荐读取阮一峰的三篇介绍文章,地址是: OAuth 2.0 的一个简单解释OAuth 2.0 的四种方式GitHub OAuth 第三方登录示例教程这三篇文章是一个非常好和非常详细的OAuth2.0的入门。 关于OAuth2.0的理解,用来授权第三方应用,以前总是理解不到位的原因是,我没有站在不同的角度去分析思考。比如在使用QQ进行第三方登录时,是我们登录的软件需要获取我们qq账号的部分用户信息,因此需要腾讯的认证授权,我们需要在一键登录(授权)的时候,登录QQ,点击同意即可。 而如果我们作为开发人员,去设计一个OAuth2.0授权功能的时候,需要从开发人员角度去思考哪部分是我们要完成的功能,比如上面的QQ第三方登录,首先QQ软件是我们开发人员开发的,第三方应用有一个我们软件的QQ账号,第三方用户想在自己的软件上面展示第三方的QQ账号的部分用户信息,需要来我们的授权服务申请,同意后才能查询我们开发的软件中的用户信息,因此我们要开发的是一个基本的QQ服务(资源服务),一个授权服务,并且第三方可以在我方注册账号,或者可以给第三方分配账号。 关于第三方应用,可以是其它公司的系统,也可以本公司架构内的其它服务,大家可以根据阮一峰的文章,参考开发人员的任务属于文章中的哪些内容。这样,对开发OAuth2.0和使用OAuth2.0会有清晰的区分和理解。 SSO单点登录单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的。 当用户第一次访问应用系统A的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统B和应用系统C了。 从上面的介绍可以看得出,单点登录需要的正是一个共享的授权和验证系统,也就是说SSO是可以使用OAuth2.0机制去设计实现的。但是SSO和OAuth2.0也有一点区别,sso和oauth2.0在应用场景上的区别在于,使用sso的各个系统(子模块)之间是互相信任的,通常是一个厂家的各个软件产品,或者是一个产品的不同模块系统。使用oauth2.0的各个应用大部分之间是互相不信任的,通常是不同厂家之间的账号共享。OAuth2.0 解决的是服务提供方(微信等)给第三方应用授权的问题,而SSO解决的是大型系统中各个子系统如何共享登陆状态的问题(比如你登录了百度首页,那么你进入百度百科,百度贴吧,百度音乐等服务的时候都不需要重新登录)。 Spring Cloud SecuritySpring Cloud Security组件可以理解为,springboot security加上OAuth2.0的整合,可以实现微服务系统中sso单点登录功能,和第三方授权功能,是一个强大的权限组件。关于springboot2.1.x版本对应使用的springcloud的security组件的官方文档如下: https://cloud.spring.io/sprin...文档的目录如下: 从目录上看,Spring Cloud Security组件主要的功能也是sso和资源认证授权,后面的内容主要用来学习Spring Cloud Security。

October 6, 2019 · 1 min · jiezi

SpringBoot2xSpringBoot-Web开发中ThymeleafWebTomcat以及Favicon

github:https://github.com/Ccww-lx/Sp... 模块:spring-boot-starter-base-web Web开发是开发中至关重要的一部分, Web开发的核心内容主要包括内嵌Servlet容器和Spring MVC。更重要的是,Spring Boot`为web开发提供了快捷便利的方式进行开发,使用依赖jar:spring-boot-starter-web,提供了嵌入式服务器Tomcat以及Spring MVC的依赖,且自动配置web相关配置,可查看org.springframework.boot.autoconfigure.web`。 Web相关的核心功能: Thymeleaf模板引擎Web相关配置Tomcat配置Favicon配置1.模板配置1.1原理以及源码分析 Spring Boot提供了大量模板引擎, 包含括FreeMarker、Groovy、 Thymeleaf、 Velocity和Mustache, Spring Boot中推荐使用Thymeleaf作为模板引擎, 因为Thymeleaf提供了完美的Spring MVC的支持。 在Spring Boot的org.springframework.boot.autoconfigure.thymeleaf包下实现自动配置,如下所示: ThymeleafAutoConfiguration源码: @Configuration@EnableConfigurationProperties(ThymeleafProperties.class)@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })public class ThymeleafAutoConfiguration { //配置TemplateResolver @Configuration @ConditionalOnMissingBean(name = "defaultTemplateResolver") static class DefaultTemplateResolverConfiguration { ... } //配置TemplateEngine @Configuration protected static class ThymeleafDefaultConfiguration { ... } //配置SpringWebFluxTemplateEngine @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) static class ThymeleafWebMvcConfiguration { ... } //配置thymeleafViewResolver @Configuration @ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true) static class ThymeleafWebFluxConfiguration { ... } ...} ThymeleafAutoConfiguration自动加载Web所需的TemplateResolver、TemplateEngine、SpringWebFluxTemplateEngine以及thymeleafViewResolver,并通过ThymeleafProperties进行Thymeleaf属性配置。详细细节查看官方源码。 ...

October 6, 2019 · 3 min · jiezi

你真的懂Spring-Java-Config-吗Full-Configuration-vs-lite-Bean-mode

Full @Configuration和lite @Bean mode 是 Spring Java Config 中两个非常有意思的概念。 先来看一下官方文档关于这两者的相关内容: The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods. In contrast, invoking a method or field in a @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying. ...

October 5, 2019 · 3 min · jiezi

推荐收藏系列一文理解JVM虚拟机内存垃圾回收性能优化解决面试中遇到问题

一. JVM内存区域的划分1.1 java虚拟机运行时数据区java虚拟机运行时数据区分布图: JVM栈(Java Virtual Machine Stacks): Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈,因此栈存储的信息都是跟当前线程(或程序)相关信息的,包括局部变量、程序运行状态、方法返回值、方法出口等等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。堆(Heap): 堆是所有线程共享的,主要是存放对象实例和数组。处于物理上不连续的内存空间,只要逻辑连续即可方法区(Method Area): 属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据常量池(Runtime Constant Pool): 它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。本地方法栈(Native Method Stacks):其中,堆(Heap)和JVM栈是程序运行的关键,因为: 栈是运行时的单位(解决程序的运行问题,即程序如何执行,或者说如何处理数据),而堆是存储的单位(解决的是数据存储的问题,即数据怎么放、放在哪儿)。堆存储的是对象。栈存储的是基本数据类型和堆中对象的引用;(参数传递的值传递和引用传递)那为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗? 从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据,分工明确,处理逻辑更为清晰体现了“分而治之”以及“隔离”的思想。堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这样共享的方式有很多收益:提供了一种有效的数据交互方式(如:共享内存);堆中的共享常量和缓存可以被所有栈访问,节省了空间。栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。堆和栈的结合完美体现了面向对象的设计。当我们将对象拆开,你会发现,对象的属性即是数据,存放在堆中;而对象的行为(方法)即是运行逻辑,放在栈中。因此编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。1.2 堆(Heap)和JVM栈:1.2.1 堆(Heap) Java堆是java虚拟机所管理内存中最大的一块内存空间,处于物理上不连续的内存空间,只要逻辑连续即可,主要用于存放各种类的实例对象。该区域被所有线程共享,在虚拟机启动时创建,用来存放对象的实例,几乎所有的对象以及数组都在这里分配内存(栈上分配、标量替换优化技术的例外)。 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor(S0)、To Survivor(S1)。如图所示: 堆的内存布局: 这样划分的目的是为了使jvm能够更好的管理内存中的对象,包括内存的分配以及回收。 而新生代按eden和两个survivor的分法,是为了 有效空间增大,eden+1个survivor;有利于对象代的计算,当一个对象在S0/S1中达到设置的XX:MaxTenuringThreshold值后,会将其挪到老年代中,即只需扫描其中一个survivor。如果没有S0/S1,直接分成两个区,该如何计算对象经过了多少次GC还没被释放。两个Survivor区可解决内存碎片化1.2.2 堆栈相关的参数参数描述-Xms堆内存初始大小,单位m、g-Xmx堆内存最大允许大小,一般不要大于物理内存的80%-Xmn年轻代内存初始大小-Xss每个线程的堆栈大小,即JVM栈的大小-XX:NewRatio年轻代(包括Eden和两个Survivor区)与年老代的比值-XX:NewSzie(-Xns)年轻代内存初始大小,可以缩写-Xns-XX:MaxNewSize(-Xmx)年轻代内存最大允许大小,可以缩写-Xmx-XX:SurvivorRatio年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1-XX:MinHeapFreeRatioGC后,如果发现空闲堆内存占到整个预估堆内存的40%,则放大堆内存的预估最大值,但不超过固定最大值。-XX:MaxHeapFreeRatio预估堆内存是堆大小动态调控的重要选项之一。堆内存预估最大值一定小于或等于固定最大值(-Xmx指定的数值)。前者会根据使用情况动态调大或缩小,以提高GC回收的效率,默认70%-XX:MaxTenuringThreshold垃圾最大年龄,设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率-XX:InitialTenuringThreshold可以设定老年代阀值的初始值-XX:+PrintTenuringDistribution查看每次minor GC后新的存活周期的阈值Note: 每次GC 后会调整堆的大小,为了防止动态调整带来的性能损耗,一般设置-Xms、-Xmx 相等。 &emsp;&emsp;&emsp;&emsp;新生代的三个设置参数:-Xmn,-XX:NewSize,-XX:NewRatio的优先级: (1).最高优先级: -XX:NewSize=1024m和-XX:MaxNewSize=1024m (2).次高优先级: -Xmn1024m (默认等效效果是:-XX:NewSize==-XX:MaxNewSize==1024m) (3).最低优先级:-XX:NewRatio=2 推荐使用的是-Xmn参数,原因是这个参数很简洁,相当于一次性设定NewSize和MaxNewSIze,而且两者相等。 1.3 jvm对象1.3.1 创建对象的方式各个方式的实质操作如下: 方式实质使用new关键调用无参或有参构造器函数创建使用Class的newInstance方法调用无参或有参构造器函数创建,且需要是publi的构造函数使用Constructor类的newInstance方法调用有参和私有private构造器函数创建,实用性更广使用Clone方法不调用任何参构造器函数,且对象需要实现Cloneable接口并实现其定义的clone方法,且默认为浅复制第三方库Objenesis利用了asm字节码技术,动态生成Constructor对象1.3.2 jvm对象分配在虚拟机层面上创建对象的步骤: ...

October 5, 2019 · 2 min · jiezi

SpringBootSecurity学习13前后端分离版之JWT

JWT 使用前面简单介绍了把默认的页面登录改为前后端分离的接口异步登录的方法,可以帮我们实现基本的前后端分离登录功能。但是这种基本的登录和前面的页面登录还有一个一样的地方,就是使用session和cookie来维护登录状态,这种方法的问题在于,扩展性不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。 一种解决方案是 session 数据持久化,写入redis或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。 另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。关于JWT的理论知识,建议参考 阮一峰 大神写的教程 :JSON Web Token 入门教程,这是我认为可能是写的最清晰的一个,下面的jwt的实现也是根据此教程来实现。 具体的理论知识可以参考教程,这里简单说下流程,用户登录成功后,在header中返回用户一个token信息,这个信息里面包含了加密的用户信息和数字签名,最重要的还有过期时间,客户端接到后,每次访问接口header中都带着这个token,服务端验证成功后就表示处于登录状态,过期后再从新获取即可。 具体的token内容包含了头部(加密信息),载体(用户信息),签名(签名两个部分的前面)三大块,三大块之间用英文句号(也就是 ".")连接起来,组成一个完整的token信息 流程设计根据前面的理论知识,我们来设计一下如何使用jwt。首先我们使用jwt,就可以不再使用session和cookie,所以第一步就是: 在security配置文件中配置session为无状态。然后考虑构建jwt消息体,有三个部分,第一个部分就是头部,内容是加密类型: 上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT,最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串,作为第一部分。所以第二步就是: 在security配置文件中配置session为无状态。确定header信息格式下一步确定第二部分,消息载体(Payload),这也是一个json对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用: 当然除了这些还可以加一些其它内容,比如用户信息,这个 JSON 对象也要使用 Base64URL 算法转成字符串,所以第三步和第四步就是: 在security配置文件中配置session为无状态。确定header信息格式确定消息体使用 HMAC SHA256 算法 对header和消息体进行签名作为第三部分现在token的消息基本组合完成了,用户登录成功和客户端访问接口,都要把token放在header里面,名字是 Authorization 。所以最后一步就是,客户端正常访问非登录等接口时,验证token的合法性,所以,总体设计流程如下: 在security配置文件中配置session为无状态。确定header信息格式确定消息体使用 HMAC SHA256 算法 对header和消息体进行签名作为第三部分添加过滤器,验证token合法性修改配置类上面的流程设计完了,下面我们按照流程修改项目,首先修改security配置类: 配置完后,启动项目,访问登录,登录成功后可以看到,没有任何cookie保存下来。 定义JWT工具类首先来定义几个常量: 然后定义Base64URL 算法编码和解码方法: 然后定义HmacSHA256 加密算法和获取签名的方法: 最后来设计一个简单验证token的方法: 这样jwt工具类就设计好了,目前这几个方法足够操作token内容。 ...

October 4, 2019 · 1 min · jiezi

Spring5源码解析4refresh方法之invokeBeanFactoryPostProcessors

invokeBeanFactoryPostProcessors(beanFactory);方法源码如下: protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { // getBeanFactoryPostProcessors 获取的是 this.beanFactoryPostProcessors; //this.beanFactoryPostProcessors 只能通过 AbstractApplicationContext.addBeanFactoryPostProcessor 方法添加 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }}getBeanFactoryPostProcessors()方法获取的是AbstractApplicationContext#beanFactoryPostProcessors这个成员变量。 这个成员变量只能通过代码中手动编码调用AbstractApplicationContext#addBeanFactoryPostProcessor方法来添加新的元素。很明显,我们这里为空。 invokeBeanFactoryPostProcessors(beanFactory)方法的主要的逻辑在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法中: //PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors())源码public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set<String> processedBeans = new HashSet<>(); if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>(); List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>(); //beanFactoryPostProcessors是传进来里的对象,把传入的对象分类放入 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor //BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor ,是一个特殊的 BeanFactoryPostProcessor for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; //如果传入的beanFactoryPostProcessors是它的子类,即:BeanDefinitionRegistryPostProcessor //则执行传入的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法 registryProcessor.postProcessBeanDefinitionRegistry(registry); registryProcessors.add(registryProcessor); } else { regularPostProcessors.add(postProcessor); } } // Do noitialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // Separate between BeanDefinitionRegistryPostProcessors that implement // PriorityOrdered, Ordered, and the rest. List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>(); // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. //这里只能拿到spring内部的BeanDefinitionRegistryPostProcessor, //因为到这里spring还没有去扫描Bean,获取不到我们通过@Component标识的自定义BeanDefinitionRegistryPostProcessor //一般默认情况下,这里只有一个,BeanName:org.springframework.context.annotation.internalConfigurationAnnotationProcessor //对应的BeanClass:ConfigurationClassPostProcessor String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { //beanFactory.getBean, 这里开始创建BeanDefinitionRegistryPostProcessor bean 了 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //排序 sortPostProcessors(currentRegistryProcessors, beanFactory); // registryProcessors 中放的是 BeanDefinitionRegistryPostProcessor // 因为这里只执行eanDefinitionRegistryPostProcessor中独有的方法,而不会执行其父类即BeanFactoryProcessor的方法 // 所以这里需要把处理器放入一个集合中,后续统一执行父类的方法 registryProcessors.addAll(currentRegistryProcessors); // 执行BeanDefinitionRegistryPostProcessor,currentRegistryProcessors中放的是spring内部的BeanDefinitionRegistryPostProcessor // 默认情况下,只有 org.springframework.context.annotation.ConfigurationClassPostProcessor // ConfigurationClassPostProcessor 里面就是在执行扫描Bean,并且注册BeanDefinition invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); // 清空这个临时变量,方便后面再使用 currentRegistryProcessors.clear(); // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. // 这里已经可以获取到我们通过注册到Spring容器的 BeanDefinitionRegistryPostProcessor 了 postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { // 之前优先处理的是实现PriorityOrdered接口的,而PriorityOrdered接口也实现了Ordered接口 // 所有这里需要把之前已经处理过的给过滤掉 if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { //之前这个临时变量已经被清空了,现在又开始放东西了 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //排序 sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); // 执行BeanDefinitionRegistryPostProcessor invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //清空临时变量 currentRegistryProcessors.clear(); // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate = true; while (reiterate) { reiterate = false; postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { //执行没有实现Ordered接口的BeanDefinitionRegistryPostProcessor if (!processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate = true; } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); } // Now, invoke the postProcessBeanFactory callback of all processors handled so far. // List<BeanDefinitionRegistryPostProcessor> registryProcessors // 之前已经执行过BeanDefinitionRegistryPostProcessor独有方法,现在执行其父类方法 invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); // List<BeanFactoryPostProcessor> regularPostProcessors // 执行 BeanFactoryPostProcessor 方法 invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); } else { // Invoke factory processors registered with the context instance. invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // 获取 BeanFactoryPostProcessor 的 beanName String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>(); List<String> orderedPostProcessorNames = new ArrayList<>(); List<String> nonOrderedPostProcessorNames = new ArrayList<>(); for (String ppName : postProcessorNames) { // 如果已经被执行过了,就不在执行 // 因为一开始先获取的BeanDefinitionRegistryPostProcessor,而BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor if (processedBeans.contains(ppName)) { // skip - already processed in first phase above } else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. // 根据不同的优先级,按序执行 BeanFactoryPostProcessor sortPostProcessors(priorityOrderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // Next, invoke the BeanFactoryPostProcessors that implement Ordered. List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(); for (String postProcessorName : orderedPostProcessorNames) { orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } sortPostProcessors(orderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); // Finally, invoke all other BeanFactoryPostProcessors. List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(); for (String postProcessorName : nonOrderedPostProcessorNames) { nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); // Clear cached merged bean definitions since the post-processors might have // modified the original metadata, e.g. replacing placeholders in values... beanFactory.clearMetadataCache();}源码超级长,我们慢慢来看。 ...

October 4, 2019 · 3 min · jiezi

带你入门SpringCloud-之-通过SpringCloud-Bus-自动更新配置

前言在《带你入门SpringCloud统一配置 | SpringCloud Config》中通过 SpringCloud Config 完成了统一配置基础环境搭建,但是并没有实现配置修改自动更新的操作(GitHub 或Gitee 修改配置后,需要重启配置服务才能更新配置)。 本文是《带你入门SpringCloud统一配置 | SpringCloud Config》的续篇,通过 SpringCloud Bus 完成配置修改自动更新的操作介绍。 阅读本文前需要你先移步《带你入门SpringCloud统一配置 | SpringCloud Config》因为本文是在其基础上进行讲解的。 另外需要你熟悉 SpringBoot 项目的基本使用即可,还有一点需要注意的是在操作过程中尽量和我本地环境一致,因为环境不一致可能会带来一些问题。我本地环境如下: SpringBoot Version: 2.1.0.RELEASESpringCloud Version: Greenwich.RELEASEApache Maven Version: 3.6.0Java Version: 1.8.0_144IDEA:Spring Tools Suite (STS)接下来就开始 SpringCloud Bus 环境搭建操作介绍! SpringCloud Bus 环境搭建第一步:安装并启用 RabbitMQ,这里就不做详细介绍了。可以查看之前的总结:Windows 环境安装 RabbitMQ 如果你的 RabbitMQ和 Config Server 端不在一台机器上,或者端口、用户名、密码不是使用的默认配置,那么你需要进行如下配置在 Config Server 端 application.properties 中 spring.rabbitmq.host=rabbitmq 服务IP地址spring.rabbitmq.port=rabbitmq 服务端口号spring.rabbitmq.username=rabbitmq 服务用户名spring.rabbitmq.password=rabbitmq 服务密码 第二步:在 Config Server 端端和客户端都引入 spring-cloud-starter-bus-amqp 依赖。具体代码如下: ...

October 4, 2019 · 3 min · jiezi

玩转-SpringBoot-2-之整合-JWT-下篇

前言在《玩转 SpringBoot 2 之整合 JWT 上篇》 中介绍了关于 JWT 相关概念和JWT 基本使用的操作方式。本文为 SpringBoot 整合 JWT 的下篇,通过解决 App 用户登录 Session 问题的实战操作,带你更深入理解 JWT。通过本文你还可以了解到如下内容: SpringBoot 使用拦截器的实际应用SpringBoot 统一异常处理SpringBoot 快速搭建 RESTful Api关于生成JWT 操作请参考 《玩转 SpringBoot 2 之整合 JWT 上篇》实战操作登录操作登录操作流程图: 登录操作流程介绍: App 根据用户名和密码访问登录接口。如果用户名和密码错误则提示 App 用户密码输入错误。如果用户名和密码正确则获取用户信息(表示登录成功)并根据用户信息生成 Token 并将其存入ServletContext 中。将生成的 Token 返回给 App。登录操作具体代码: @RestControllerpublic class LoginController { Logger log = LoggerFactory.getLogger(LoginController.class); @Autowired private JWTService jwtService; @RequestMapping("/login") public ReturnMessage<Object> login(String loginName,String password,HttpServletRequest request) { if(valid(loginName,password)) { ReturnMessageUtil.error(CodeEnum.LOGINNAMEANDPWDERROR); } Map<String,String> userInfo = createUserInfoMap(loginName,password); String token = jwtService.createToken(userInfo, 1); ServletContext context = request.getServletContext(); context.setAttribute(token, token); log.info("token:"+token); return ReturnMessageUtil.sucess(token); }} private Map<String,String> createUserInfoMap(String loginName, String password) { Map<String,String> userInfo = new HashMap<String,String>(); userInfo.put("loginName", loginName); userInfo.put("password", password); return userInfo; } private boolean valid(String loginName, String password) { if(Objects.equal("ljk", loginName) && Objects.equal("123456", password) ) { return true; } return false; }拦截操作拦截操作流程图: ...

October 4, 2019 · 2 min · jiezi

玩转-SpringBoot-2-快速整合拦截器

概述首先声明一下,这里所说的拦截器是 SpringMVC 的拦截器 HandlerInterceptor。使用SpringMVC 拦截器需要做如下操作: 创建拦截器类需要实现 HandlerInterceptor在 xml 配置文件中配置该拦截器,具体配置代码如下:<mvc:interceptors> <mvc:interceptor> <!-- /test/** 这个是拦截路径以/test开头的所有的URL--> <mvc:mapping path="/**"/><!—这个是拦截说有的路径--> <!-- 配置拦截器类路径--> <bean class="cn.ljk.springmvc.controller.MyInterceptor"></bean> <!-- 配置不拦截器URL路径--> <mvc:exclude-mapping path="/fore/**"/> </mvc:interceptor></mvc:interceptors>因为在SpringBoot 中没有 xml 文件,所以SpringBoot 为我们提供 Java Config 的方式来配置拦截器。配置方式有2种: 继承 WebMvcConfigurerAdapter (官方已经不建议使用)实现 WebMvcConfigurer接下来开始 SpringBoot 整合拦截器操作详细介绍! 整合拦截器实战操作第一步:声明拦截器类 通过实现 HandlerInterceptor 来完成。具体代码如下: public class LoginInterceptor implements HandlerInterceptor{}第二步:实现 HandlerInterceptor 3 个拦截方法 preHandle:Controller逻辑执行之前进行拦截postHandle:Controller逻辑执行完毕但是视图解析器还为进行解析之前进行拦截afterCompletion:Controller逻辑和视图解析器执行完毕进行拦截实际开发中 preHandle使用频率比较高,postHandle 和 afterCompletion操作相对比较少。 在下面的代码中 preHandle 方法中定义拦截所有访问项目 URL并进行日志信息记录。postHandle 中在视图解析前进行拦截,通过 Model 在次添加数据Request域中。 afterCompletion 暂时没有想到使用场景,如果有使用过的场景可以在下面评论区中进行评论。拦截器详细代码如下: public class LoginInterceptor implements HandlerInterceptor{ private Logger log = LoggerFactory.getLogger(LoginInterceptor.class); //ControllerController逻辑执行之前 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("preHandle...."); String uri = request.getRequestURI(); log.info("uri:"+ uri); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; log.info("拦截 Controller:"+ handlerMethod.getBean().getClass().getName()); log.info("拦截方法:"+handlerMethod.getMethod().getName()); } return true; } //Controller逻辑执行完毕但是视图解析器还为进行解析之前 @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { log.info("postHandle...."); Map<String,Object>map=modelAndView.getModel(); map.put("msg","postHandle add msg"); } //Controller逻辑和视图解析器执行完毕 @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { log.info("afterCompletion...."); }}第三步:Java Config 的方式来配置拦截器 ...

October 4, 2019 · 2 min · jiezi

玩转-SpringBoot-2-之整合-JWT-上篇

前言该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT。介绍前在这里我们来探讨一下如何学习一门新的技术,我个人总结为 RSA。 R:read 去读官方文档 。S:search 谷歌或百度先关技术文章或 github 去搜索先关信息。A:ask 可以向技术大牛请教或和自己的同事同学进行探讨。关于 RSA 仅仅代码个人的学习观点,只是给读者一个不成熟的小建议哈JWT 介绍官网介绍如下: What is JSON Web Token?JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.中文翻译:JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间作为JSON对象安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。JWTS可以使用秘密(使用HMAC算法)或公钥/私钥对使用RSA或ECDSA来签名。虽然JWTS可以加密,但也提供保密各方之间,我们将重点放在签名令牌。签名的令牌可以验证包含在其中的声明的完整性,而加密的令牌隐藏这些声明以防其他各方。当令牌使用公钥/私钥对签名时,签名也证明只有持有私钥的方才是签名的方。 ...

October 4, 2019 · 4 min · jiezi

秋招打怪升级之路十面阿里终获offer

本文转载自:https://gongfukangee.github.i... 作者:G.Fukang开源项目推荐: JavaGuide: Java学习+面试指南!Github 56k+ 的 Java项目。一份涵盖大部分Java程序员所需要掌握的核心知识。springboot-guide:SpringBoot 学习指南!重要知识点以及常见面试题总结。programmer-advancement:技术人员应该具有的一些好习惯。秋招阿里本地生活 - Java 开发百度个人云 - 移动端开发华为成研所 - 分布式数据库开发作业帮 - 数据平台开发顺丰 - 后端开发拼多多 - 基础架构平台开发快手 - Java 开发面完阿里 HR 面后,其他就不想面了,推掉了美团、虾皮和字节跳动效率工程。 作业帮(数据平台研发)作业帮是我面的最早的公司,也是一波三折,约的第一次视频面,面试官迟到,赛码网又出了问题,没声音没画面,再约第二次视频面试跟其他面试冲突了,直接拒掉了,后面又约的第三次视频面试才面上,一面主要是简历和基础,70+min,面的还可以,面试过程中面试官技术也很厉害,也直接就约了二面,二面也是约的视频面试,不过面试官网出了问题,改成了电话面试,30min,问了很多场景问题,二面结束的当天晚上就接到了 HR 面的电话,HR 面结束第三天就收到了意向书。 整个感觉作业帮技术水平还是很厉害的,效率也很快,感觉是真招人,有 HR 一直在推动流程。 百度(移动软件开发)百度今年的内推的比较早,七月多就开始了,自己投递了有基础平台、智能云、大搜、度秘、个人云等部门,不过只有个人云给了面试机会,说没有 Java 岗问我转不转移动端,我说可以,然后很快就收到了面试,一面电话面,因为我没有移动端的经验,问的都是基础,还有一道多线程的编程和一道数据库 SQL 题目,数据库的题目没写上来,面试官也没说啥,一面就直接约了二面的时间。二面微信视频面试,主要是问的项目,穿插几个基础题目,还问了一道大数相加的算法题目,没啥大问题,因为自己不是做移动端的,所以了解也少,和面试官讨论了很久移动端开发的事情,面试管给推荐了书籍还给了很多学习上的建议,顺带说了还有三面。三面就是第二天,电话面,感觉一半技术一半非技术,问了很多项目中的分工,难点,难点如何解决的,新技术,平时看的博客,对新技术的看法,对移动端的看法。也没什么太大的问题,面完面试官说挺好的,说后续有 HR 沟通,然后就开始了漫长的等待,直到八月中旬才有 HR 加我要了个人信息,后续也收到了短信信息,在 9 月中旬有性格测评,下旬发放 offer。 整体感觉百度流程也很快,面试流程很快,就是等的时间有点长,不过整体的面试感觉还是挺好的,面试官会引导面试者并且不会刻意刁难,毕竟还是 BAT,技术积累和实力还是很强的。 插曲百度和作业帮都是在八月放暑假时在深圳面的,本来是去放松的,但是没想到一直被排满了面试,搞得很累,不过也有所收获,拿到了百度和作业帮的 offer,在后续的面试中就不再心慌了。 华为(分布式数据库开发)华为是从深圳回来后,在微信群里看到有成研所的 HR 说参加华为软件精英挑战赛的学生可以提前参与优招,也就说顶尖学生计划,本来不报希望,不过 HR 说不影响后续的优招,因此自己就报着试一试的心态参加了。一共两面,第一面在成研所,CloudBU 首席架构师面试,40min 基本是围绕简历来的,穿插着一些分布式和 GC 调优的知识,没有太大的难度,也不是很简单,个人感觉仅次于阿里面试,华为也不是以前那个聊天就能进的了。成研所面完后,中午 HR 还带我们在餐厅吃了饭,味道挺好的,就是有点贵。第二天 HR 通知我一面过了,要我准备二面部长面,视频面试,部长面就是综合面,技术问了一些,还问了简历上发表的论文,不过不是计算机方向,我讲了下也没讲太明白。面试完不到十分钟,HR 就通知我说面试通过了,定级 14 级,成都第一档,让我安心等消息。本来以为华为稳了,其他也不是想面了,不过后面出了个插曲,就是性格测评挂了,本来华为保底,现在不行了,心里也有点慌,就又开始好好准备面试,不过后面补测一次过了,就没啥了,HR 也说安心等后续的消息。 ...

October 2, 2019 · 2 min · jiezi

Spring5源码解析2register方法注册配置类

接上回已经讲完了this()方法,现在来看register(annotatedClasses);方法。 // new AnnotationConfigApplicationContext(AppConfig.class); 源码public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { //调用默认无参构造器,里面有一大堆初始化逻辑 this(); //把传入的Class进行注册,Class既可以有@Configuration注解,也可以没有@Configuration注解 //怎么注册? 委托给了 org.springframework.context.annotation.AnnotatedBeanDefinitionReader.register 方法进行注册 // 传入Class 生成 BeanDefinition , 然后通过 注册到 BeanDefinitionRegistry register(annotatedClasses); //刷新容器上下文 refresh();}register(annotatedClasses) 方法register(annotatedClasses);方法最后其实是调用了reader的doRegisterBean(annotatedClass, null, null, null);方法。 <T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) { //根据传入的Class对象生成 AnnotatedGenericBeanDefinition , // AnnotatedGenericBeanDefinition 是 BeanDefinition 的 一个实现类 AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); //根据 @Conditional 注解,判断是否需要跳过解析 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } abd.setInstanceSupplier(instanceSupplier); ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); //解析 Class<T> annotatedClass 是否有通用注解: @Lazy,@Primary,@DependsOn,@Role,@Description // 并把解决结果放入 AnnotatedBeanDefinition 中 AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); //Class<? extends Annotation>[] qualifiers 是通过方法调用传入的 // 上一步是解析Class中是否有注解,这一步是调用方作为参数传入的 if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { abd.setPrimary(true); } else if (Lazy.class == qualifier) { abd.setLazyInit(true); } else { //org.springframework.beans.factory.support.AbstractBeanDefinition.qualifiers //Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>(); //直接放入map中 abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } for (BeanDefinitionCustomizer customizer : definitionCustomizers) { customizer.customize(abd); } BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); //将该Class<T> annotatedClass 转为 BeanDefinition 后, 通过再封装为 BeanDefinitionHolder 对象,进行 registerBeanDefinition //AnnotatedBeanDefinitionReader 中有一个 BeanDefinitionRegistry registry 是通过构造方法传入的 //new AnnotationConfigApplicationContext(AppConfig.class); AnnotationConfigApplicationContext extends GenericApplicationContext // GenericApplicationContext 类 实现了 BeanDefinitionRegistry ,registry 即为 AnnotationConfigApplicationContext // GenericApplicationContext 类 内部 是 通过 DefaultListableBeanFactory 来实现 BeanDefinitionRegistry 接口的 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);}根据传入的class对象创建AnnotatedGenericBeanDefinition,AnnotatedGenericBeanDefinition 是 BeanDefinition 的一个实现类。根据@Conditional 注解,判断是否需要跳过解析,很明显这里不需要,返回false,代码继续向下执行。根据传入的参数,设置BeanDefinition 的属性解析传入的class对象是否有通用注解(@Lazy、@Primary 、@DependsOn、@Role、@Description),并把解析结果放入 AnnotatedBeanDefinition中。判断是否有传入Class<? extends Annotation>[] qualifiers参数,如果不为null,则将传入的qualifiers参数设置到BeanDefinition中。注意,第4步解析的是class中是否带有通用注解。而这步判断的注解是调用方手动传入的。将传入的class对象转化为 BeanDefinition后,再将BeanDefinition封装到BeanDefinitionHolder中(为了方便传参),然后调用BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);注册该 BeanDefinition。调用registerBeanDefinition方法时传入的this.registry对象是AnnotatedBeanDefinitionReader的一个属性,它是在构造方法中被初始化的。这个this.registry对象其实就是AnnotationConfigApplicationContext对象。AnnotationConfigApplicationContext 继承了GenericApplicationContext,GenericApplicationContext类实现了BeanDefinitionRegistry接口。而在GenericApplicationContext类中其实是委托给成员变量beanFactory来实现BeanDefinitionRegistry接口的。public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory;再来看registerBeanDefinition方法。主要是通过registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());方法将BeanDefinition注册到DefaultListableBeanFactory中,也就是spring容器中。public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } }}而所谓的注册BeanDefinition,简单理解就是将BeanDefinition放到DefaultListableBeanFactory对象的beanDefinitionMap中。 ...

October 2, 2019 · 3 min · jiezi

SpringBootSecurity学习12前后端分离版之简单登录

前后端分离前面讨论了springboot下security很多常用的功能,其它的功能建议参考官方文档学习。网页版登录的形式现在已经不是最流行的了,最流行的是前后端分离的登录方式,前端单独成为一个项目,与后台的交互,包括登录认证和授权都是由异步接口来实现。在前后端不分离的应用模式中,前端页面看到的效果都是由后端控制,由后端渲染页面或重定向,也就是后端需要控制前端的展示,前端与后端的耦合度很高。这种应用模式比较适合纯网页应用, 但是当后端对接App时,App可能并不需要后端返回一个HTML网页,而仅仅是数据本身,所以后端原本返回网页的接口不再适用于前端App应用,为了对接App后端还需再开发一套接口。 在前后端分离的应用模式中,后端仅返回前端所需的数据,不再渲染HTML页面,不再控制前端的效果。至于前端用户看到什么效果,从后端请求的数据如何加载到前端中,都由前端自己决定,网页有网页的处理方式,App有App的处理方式,但无论哪种前端,所需的数据基本相同,后端仅需开发一套逻辑对外提供数据即可。在前后端分离的应用模式中 ,前端与后端的耦合度相对较低。 在前后端分离的应用模式中,我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。 前后端分离出现的跨域问题前后端分离后,出现的经典问题就是跨域问题。跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源(域名,协议,端口)策略造成的,是浏览器对JavaScript施加的安全限制。具体的跨域理论跨域自行查询学习。在security中,解决跨域问题是非常简单的。只需要增加几行配置即可。 简单示例来写一个简单的例子来实现前后端分离的异步登录。首先引入依赖: 修改springboot默认配置文件,添加默认用户: 启动类不用修改,添加一个接口: 下面来配置security配置类,首先配置登录,异步登录不再需要后台配置登录页面地址,只需要配置登录参数和api地址即可: 然后加上授权配置和登录成功的处理: 最后加上csrf配置: 最简单的配置已经完成了,最后来看一下登录成功的处理: 返回了一个json形式的登录成功消息。 简单测试来进行一个简单的测试,启动项目,使用postman直接访问hello接口: 访问不成功,返回了登录的html页面,关于没有权限的处理,后面会做的更加友好。下面用接口进行登录: 可以看到正常登录成功,postman中也多了一个cookie信息: 这和浏览器的cookie是一样的,删掉以后就成了未登录状态。现在访问hello接口,可以看到正常的效果: 添加跨域添加跨域配置非常简单,首先在security配置中调用cors方法: 然后我们打开springboot的官方文档,查看跨域配置: 可以看到,在springboot中,全局的跨域配置非常简单,我们来模仿写一个bean: 这样跨域就配置好了。 其它处理器来看一下登录失败(比如账号错误)时的处理器: 配置登录失败处理器: 测试: 来看一下登录超时或者未登录的异常处理器: 配置登录超时或者未登录处理器: 测试,在未登录的情况下,直接访问hello接口: 现在提示友好了很多。最后看一下权限不足处理器: 配置权限不足处理器: 然后开启方法级别的权限注解,在hello方法上面配置HELLO权限: 在配置文件中,给默认用户admin配置一个其他角色: 然后重启项目,首先在postman中,进行登录,然后再访问hello接口: 此时就会根据处理器的结果提示权限不足。 说明上面是一个简单的前后端分离的登录的例子。这里只应用了几个简单的功能,不过前面讨论过的从数据库中查询用户,动态权限,共享session,记住我等等的功能,也都可以加入到前后端分离的登录功能中,这些内容的使用和前面网页版的登录没有什么区别。使用这些完全可以满足前后端分离的登录和授权功能。 代码地址 : https://gitee.com/blueses/spr... 12 ...

October 2, 2019 · 1 min · jiezi

SpringBootSecurity学习11网页版登录之URL动态权限

动态权限前面讨论用户登录认证的时候,根据用户名查询用户会将用户拥有的角色一起查询出来,自动实现判断当前登录用户拥有哪些角色。可以说用户与角色之间的动态配置和判断security做的非常不错。不过在配置方法级别的权限的时候,使用注解虽然是一种比较优雅的方式,但是要求在开发的时候就知道当前url对应哪些角色,无法实现动态的配置,而实际的项目中,每个链接允许哪些角色访问也不是一成不变的,因此下面我们来实现自己的路由判断。 创建表前面的讨论中,我们创建了用户表,角色表和用户角色中间表,下面来创建菜单功能表,并把现在有的url链接添加进去: 然后创建角色菜单中间表,加入角色与url之间的对应关系: 创建基础类首先去掉前面的方法级别权限的注解,然后创建菜单实体类: 创建查询方法,根据url查询次链接对应的所有角色名称: 对应的sql语句如下: 路由动态获取角色首先增加一个处理类,在收到访问的时候,动态获取当前url的角色: 新建一个ObjectPostProcessor类,将这个处理类配置到其中: 最后将新建的ObjectPostProcessor类配置到权限配置方法中: 路由决策管理用户与角色是多对多的关系,url与角色也是多对多的关系,这里的设定是,只要用户与url对应的角色中有相同的存在,就表示用户有访问的权限。首先看一下对应判断的处理类: 使用双重for循环进行判断,并进行结果投票。在注释中可以看到,使用不同的方式会产生不同的策略。下一步在security配置类中配置路由策略方法: 除了UrlRoleAuthHandler类,其它决策类使用的都是security存在的类,最后在权限配置中配置决策管理: 测试这样动态权限url就配置好了,根据上面方法中的数据,可以登录查看是否具有对应的url权限,没有配置的是否不具备权限。 代码地址:https://gitee.com/blueses/spr... 11

October 2, 2019 · 1 min · jiezi

Spring-Boot-整合-Web-开发

这一节我们主要学习如何整合 Web 相关技术: ServletFilterListener访问静态资源文件上传文件下载 Web三大基本组件分别是:Servlet,Listener,Filter。正常来说一旦我们用了框架,这三个基本就用不上了,Servlet 被 Controller 代替,Filter 被拦截器代替。但是可能在一些特殊的场景下不得不使用这三个基本组件时,Spring Boot 中要如何去引用呢?下面我们来一起学习一下。 Spring Boot 集成了 Servlet 容器,当我们在 pom.xml 中增加 spring-boot-starter-web 组件依赖时,不做任何 web 相关的配置便能提供 web 服务,这还得归功于 Spring Boot 自动配置的功能,帮我们创建了一堆默认的配置,以前在 web.xml 中的配置,现在都可以通过 Spring Bean 的方式或者注解方式进行配置,由 Spring 来进行生命周期的管理,大多数情况下,我们需要自定义这些配置,如:修改服务的启动端口,ContextPath,Filter,Listener,Servlet,Session超时时间等等。 Spring Boot 提供了 ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean和 @WebServlet,@WebFilter,@WebListener 三种类型分别配置应用的 Servlet,Filter,Listener。 创建 jar 项目,编写 pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.springboot</groupId> <artifactId>springboot-hello</artifactId> <version>1.0-SNAPSHOT</version> <name>springboot-hello</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <!-- 引入springboot父类依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent> <dependencies> <!-- springboot-web 组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>整合Servlet Servlet 是 Java Servlet 的简称,称为小服务程序或服务连接器,用 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。 Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet 可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。 ...

October 2, 2019 · 5 min · jiezi

使用-BeanDefinition-描述-Spring-Bean

什么是BeanDefinition在Java中,一切皆对象。在JDK中使用java.lang.Class来描述类这个对象。 在Spring中,存在bean这样一个概念,那Spring又是怎么抽象bean这个概念,用什么类来描述bean这个对象呢?Spring使用BeanDefinition来描述bean。 BeanDefinition BeanDefinition继承了AttributeAccessor和BeanMetadataElement接口。在Spring中充斥着大量的各种接口,每种接口都拥有不同的能力,某个类实现了某个接口,也就相应的拥有了某种能力。 AttributeAccessor顾名思义,这是一个属性访问者,它提供了访问属性的能力。 BeanMetadataElementBeanMetadataElement中只有一个方法,用来获取元数据元素的配置源对象: public interface BeanMetadataElement { @Nullable Object getSource();}BeanDefinitionBeanDefinition接口是Spring对bean的抽象。 我们可以从源码中可以看出,Spring是怎么描述一个bean: public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { /** * Scope identifier for the standard singleton scope: "singleton". * <p>Note that extended bean factories might support further scopes. * * @see #setScope */ String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; /** * Scope identifier for the standard prototype scope: "prototype". * <p>Note that extended bean factories might support further scopes. * * @see #setScope */ String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; /** * Role hint indicating that a {@code BeanDefinition} is a major part * of the application. Typically corresponds to a user-defined bean. */ int ROLE_APPLICATION = 0; /** * Role hint indicating that a {@code BeanDefinition} is a supporting * part of some larger configuration, typically an outer * {@link org.springframework.beans.factory.parsing.ComponentDefinition}. * {@code SUPPORT} beans are considered important enough to be aware * of when looking more closely at a particular * {@link org.springframework.beans.factory.parsing.ComponentDefinition}, * but not when looking at the overall configuration of an application. */ int ROLE_SUPPORT = 1; /** * Role hint indicating that a {@code BeanDefinition} is providing an * entirely background role and has no relevance to the end-user. This hint is * used when registering beans that are completely part of the internal workings * of a {@link org.springframework.beans.factory.parsing.ComponentDefinition}. */ int ROLE_INFRASTRUCTURE = 2; // Modifiable attributes /** * Set the name of the parent definition of this bean definition, if any. */ void setParentName(@Nullable String parentName); /** * Return the name of the parent definition of this bean definition, if any. */ @Nullable String getParentName(); /** * Specify the bean class name of this bean definition. * <p>The class name can be modified during bean factory post-processing, * typically replacing the original class name with a parsed variant of it. * * @see #setParentName * @see #setFactoryBeanName * @see #setFactoryMethodName */ void setBeanClassName(@Nullable String beanClassName); /** * Return the current bean class name of this bean definition. * <p>Note that this does not have to be the actual class name used at runtime, in * case of a child definition overriding/inheriting the class name from its parent. * Also, this may just be the class that a factory method is called on, or it may * even be empty in case of a factory bean reference that a method is called on. * Hence, do <i>not</i> consider this to be the definitive bean type at runtime but * rather only use it for parsing purposes at the individual bean definition level. * * @see #getParentName() * @see #getFactoryBeanName() * @see #getFactoryMethodName() */ @Nullable String getBeanClassName(); /** * Override the target scope of this bean, specifying a new scope name. * * @see #SCOPE_SINGLETON * @see #SCOPE_PROTOTYPE */ void setScope(@Nullable String scope); /** * Return the name of the current target scope for this bean, * or {@code null} if not known yet. */ @Nullable String getScope(); /** * Set whether this bean should be lazily initialized. * <p>If {@code false}, the bean will get instantiated on startup by bean * factories that perform eager initialization of singletons. */ void setLazyInit(boolean lazyInit); /** * Return whether this bean should be lazily initialized, i.e. not * eagerly instantiated on startup. Only applicable to a singleton bean. */ boolean isLazyInit(); /** * Set the names of the beans that this bean depends on being initialized. * The bean factory will guarantee that these beans get initialized first. */ void setDependsOn(@Nullable String... dependsOn); /** * Return the bean names that this bean depends on. */ @Nullable String[] getDependsOn(); /** * Set whether this bean is a candidate for getting autowired into some other bean. * <p>Note that this flag is designed to only affect type-based autowiring. * It does not affect explicit references by name, which will get resolved even * if the specified bean is not marked as an autowire candidate. As a consequence, * autowiring by name will nevertheless inject a bean if the name matches. */ void setAutowireCandidate(boolean autowireCandidate); /** * Return whether this bean is a candidate for getting autowired into some other bean. */ boolean isAutowireCandidate(); /** * Set whether this bean is a primary autowire candidate. * <p>If this value is {@code true} for exactly one bean among multiple * matching candidates, it will serve as a tie-breaker. */ void setPrimary(boolean primary); /** * Return whether this bean is a primary autowire candidate. */ boolean isPrimary(); /** * Specify the factory bean to use, if any. * This the name of the bean to call the specified factory method on. * * @see #setFactoryMethodName */ void setFactoryBeanName(@Nullable String factoryBeanName); /** * Return the factory bean name, if any. */ @Nullable String getFactoryBeanName(); /** * Specify a factory method, if any. This method will be invoked with * constructor arguments, or with no arguments if none are specified. * The method will be invoked on the specified factory bean, if any, * or otherwise as a static method on the local bean class. * * @see #setFactoryBeanName * @see #setBeanClassName */ void setFactoryMethodName(@Nullable String factoryMethodName); /** * Return a factory method, if any. */ @Nullable String getFactoryMethodName(); /** * Return the constructor argument values for this bean. * <p>The returned instance can be modified during bean factory post-processing. * * @return the ConstructorArgumentValues object (never {@code null}) */ ConstructorArgumentValues getConstructorArgumentValues(); /** * Return if there are constructor argument values defined for this bean. * * @since 5.0.2 */ default boolean hasConstructorArgumentValues() { return !getConstructorArgumentValues().isEmpty(); } /** * Return the property values to be applied to a new instance of the bean. * <p>The returned instance can be modified during bean factory post-processing. * * @return the MutablePropertyValues object (never {@code null}) */ MutablePropertyValues getPropertyValues(); /** * Return if there are property values values defined for this bean. * * @since 5.0.2 */ default boolean hasPropertyValues() { return !getPropertyValues().isEmpty(); } /** * Set the name of the initializer method. * * @since 5.1 */ void setInitMethodName(@Nullable String initMethodName); /** * Return the name of the initializer method. * * @since 5.1 */ @Nullable String getInitMethodName(); /** * Set the name of the destroy method. * * @since 5.1 */ void setDestroyMethodName(@Nullable String destroyMethodName); /** * Return the name of the destroy method. * * @since 5.1 */ @Nullable String getDestroyMethodName(); /** * Set the role hint for this {@code BeanDefinition}. The role hint * provides the frameworks as well as tools with an indication of * the role and importance of a particular {@code BeanDefinition}. * * @see #ROLE_APPLICATION * @see #ROLE_SUPPORT * @see #ROLE_INFRASTRUCTURE * @since 5.1 */ void setRole(int role); /** * Get the role hint for this {@code BeanDefinition}. The role hint * provides the frameworks as well as tools with an indication of * the role and importance of a particular {@code BeanDefinition}. * * @see #ROLE_APPLICATION * @see #ROLE_SUPPORT * @see #ROLE_INFRASTRUCTURE */ int getRole(); /** * Set a human-readable description of this bean definition. * * @since 5.1 */ void setDescription(@Nullable String description); /** * Return a human-readable description of this bean definition. */ @Nullable String getDescription(); // Read-only attributes /** * Return whether this a <b>Singleton</b>, with a single, shared instance * returned on all calls. * * @see #SCOPE_SINGLETON */ boolean isSingleton(); /** * Return whether this a <b>Prototype</b>, with an independent instance * returned for each call. * * @see #SCOPE_PROTOTYPE * @since 3.0 */ boolean isPrototype(); /** * Return whether this bean is "abstract", that is, not meant to be instantiated. */ boolean isAbstract(); /** * Return a description of the resource that this bean definition * came from (for the purpose of showing context in case of errors). */ @Nullable String getResourceDescription(); /** * Return the originating BeanDefinition, or {@code null} if none. * Allows for retrieving the decorated bean definition, if any. * <p>Note that this method returns the immediate originator. Iterate through the * originator chain to find the original BeanDefinition as defined by the user. */ @Nullable BeanDefinition getOriginatingBeanDefinition();}AnnotatedBeanDefinitionAnnotatedBeanDefinition 继承了BeanDefinition,拓展了BeanDefinition接口的能力: ...

October 2, 2019 · 7 min · jiezi

SpringBootSecurity学习10网页版登录之记住我功能

场景很多登录都有记住我这个功能,在用户登陆一次以后,系统会记住用户一段时间,在这段时间,用户不用反复登陆就可以使用我们的系统。记住用户功能的基本原理如下图: 用户登录的时候,请求发送给过滤器UsernamePasswordAuthenticationFilter,当该过滤器认证成功后,会调用RememberMeService,会生成一个token,将token写入到浏览器cookie,同时RememberMeService里边还有个TokenRepository,将token和用户信息写入到数据库中。这样当用户再次访问系统,访问某一个接口时,会经过一个RememberMeAuthenticationFilter的过滤器,他会读取cookie中的token,交给RememberService,RememberService会用TokenRepository根据token从数据库中查是否有记录,如果有记录会把用户名取出来,再调用UserDetailService根据用户名获取用户信息,然后放在SecurityContext里。 实现类首先来实现操作token的类,实现增删改查功能,我们来使用redis保存,新建类RememberMeHandler,这个类需要实现接口 PersistentTokenRepository,首先来全局看一下类结构: 为了方便查询,我们在记住一个用户的时候,向redis中保存三条数据,其中两个是根据series查用户名和根据用户名查series。token定义的保存时长为15天,这两个定为30天。最下面的三个方法就是保存这两个key的方法和生成所有key的方法。四个重写的方法就是增删改查方法。首先来看新增: 需要记住用户的时候,把用户的信息组合在一起,添加到redis中,并定义15天的过期时间。然后看修改和删除: 都是对保存内容的正常操作,最后看查询: 记住用户之后,用户登录,查询出用户信息,实现自动认证。 网上有很多使用jdbc实现的方式,也是一种不错的选择。 配置记住我在security配置类中,需要配置记住我的参数名字和处理类: 注意这里的授权配置要使用 authenticated() 。登录页面中增加记住我: 注意这里的参数名字 remember-me 是security记住我的默认名字。 测试不勾选记住我,点击登录,redis中并没有记录token信息,勾选记住我,点击登录,可以看到记住我的信息记录在redis中: 我们启动项目,登录成功并勾选记住用户,然后重新启动项目,在同一个浏览器中访问页面,可以看到不用登录直接可以成功! 代码地址:https://gitee.com/blueses/spr... 10

October 1, 2019 · 1 min · jiezi

Spring5源码解析1从启动容器开始

从启动容器开始最简单的启动spring的代码如下: @Configuration@ComponentScanpublic class AppConfig {}public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.close(); }}先来看一下AnnotationConfigApplicationContext类的UML图,留个印象。 点开AnnotationConfigApplicationContext(AppConfig.class);方法查看源码: public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) { //调用默认无参构造器,里面有一大堆初始化逻辑 this(); //把传入的Class进行注册,Class既可以有@Configuration注解,也可以没有@Configuration注解 //怎么注册? 委托给了 org.springframework.context.annotation.AnnotatedBeanDefinitionReader.register 方法进行注册 // 传入Class 生成 BeanDefinition , 然后通过 注册到 BeanDefinitionRegistry register(annotatedClasses); //刷新容器上下文 refresh();}该构造器允许我们传入一个或者多个class对象。class对象可以是被@Configuration标注的,也可以是一个普通的Java 类。 有参构造器调用了无参构造器,点开源码: public AnnotationConfigApplicationContext() { //隐式调用父类构造器,初始化beanFactory,具体实现类为DefaultListableBeanFactory super(); // 这个代码是笔者添加的,方便定位到super方法 //创建 AnnotatedBeanDefinitionReader, //创建时会向传入的 BeanDefinitionRegistry 中 注册 注解配置相关的 processors 的 BeanDefinition this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this);}初始化子类时会先初始化父类,会默认调用父类无参构造器。AnnotationConfigApplicationContext继承了GenericApplicationContext,在GenericApplicationContext的无参构造器中,创建了BeanFactory的具体实现类DefaultListableBeanFactory。spring中的BeanFactory就是在这里被实例化的,并且使用DefaultListableBeanFactory做的BeanFactory的默认实现。 ...

October 1, 2019 · 2 min · jiezi

SpringBootSecurity学习09网页版登录配置Session共享

场景当后台项目由部署在一台改为部署在多台以后,解决session共享问题最常用的办法就是把session存储在redis等缓存中。关于session和cookie概念这里就不再赘述了,在springboot-security环境下,把session存储到redis中共享是非常非常简单的,除了多了一些配置,几乎不用改任何代码。共享session达到的效果就是,用户在一台服务器上面登录成功后,访问另外一台,用户也是处于登录状态。下面创建两个一样的项目,来配置session共享。 增加依赖把session存储在redis中配置共享,需要添加两个依赖,一是redis,二是spring session: 配置文件修改在配置文件中,需要配置redis的数据源和session的一些属性: 关于session的配置,可以根据ide的提示看一下: 可以看到session的存储不仅可以在redis中,还可以在数据库或者MongoDB中,不过目前redis是选择最多的一种方式。 添加注解最后在启动类中添加一个注解 @EnableRedisHttpSession : 测试先打开一个项目的登录页面,登录成功, 然后再打开一个浏览器,直接访问第二个项目的主页,此时不用登录直接可以访问: 来看一下session在redis中的存储: 这样就实现了session共享!此时把其中一个退出,另一个也会自动退出,redis 中的session数据也会自动删除。 代码地址 : https://gitee.com/blueses/spr... 091 092

September 30, 2019 · 1 min · jiezi

SpringBootSecurity学习08网页版登录整合MyBatis

创建数据库前面介绍了springboot-security整合jdbc从数据库中查询用户的方式,适用性有限,下面介绍最常用的整合MyBatis,这种在开发和生产环境中是最常用,也是最实用的。首先需要创建数据库表,我们来创建三张表,分别是用户表,角色表,还有用户角色表,首先看用户表: 只有三个字段,具体业务中需要几个字段完全由我们自己设计。密码是admin,是加密的,后面的配置中会看到加密方式,与前面介绍的在内存中配置默认用户的方式类似。下面看角色表: 注意每个角色名字的前面都加了一个ROLE_前缀,最后来看用户角色表: 我们给用户只分配了一个角色。 创建实体类我们从基本做起,来创建实体类,首先创建角色实体类 SysRole, 除了基本的id和role字段,SysRole还实现了GrantedAuthority接口,实现了getAuthority方法,这个方法返回的就是角色的名字,后面会看到专门实现这个接口的好处。下面来看用户实体类 SysUser: 用户名最好是username,密码最好是password,这样命名是security默认支持的命名。这个是原生的用户实体类,除了id和用户名密码,加入了一个角色列表属性,用以返回当前用户拥有哪些角色,不过为了配合security的使用,最好给用户实体类实现一个接口UserDetails: 然后实现接口的下面几个方法: 其中比较重要的是最后一个获取角色列表的方法,这是security默认的方法。由于我们角色实体类实现了GrantedAuthority接口,所以这里可以直接返回上面定义的角色列表。还有一些其它的属性都是security默认设计的用户属性,从字面意思可以看出是判断超时,锁定,能否登陆之类的,如果想用可以定义对应的字段,如果不想用直接返回true即可。 创建mapper在springboot中整合mybatis就不再介绍了。下一步创建mapper接口,内容很简单,就是根据用户名查询用户: 来看sql语句,我们要一步到位,把用户和用户拥有的角色都查出来,这里使用一个一对多查询: security配置类配置类中把前面的jdbc配置可以删除了,我们来重写两个方法,配置用户名密码的验证和密码的加密: 这里我们查询到用户的时候直接返回的用户,是因为用户类实现了对应的接口,所以操作很方便,而且返回的内容中包含了角色信息。下面在auth中配置了登录验证方式和密码加密方式。 测试上面的内容以及配置完了,我们重启项目就可以使用admin/admin登录: 访问/two和/three页面: 可以看到以非常个性化的自定义方式完成了用户的认证和授权。来查看当前用户信息: 在实际的开发中,用户表肯定会有更加丰富的字段,使用用户类实现UserDetails接口的形式能更好的获取用户信息。 注意,新增用户的时候,密码要使用 new BCryptPasswordEncoder().encode("admin") 的方式加密,新增角色的时候,前面要加上ROLE_前缀,这是默认的规则。 代码地址:https://gitee.com/blueses/spr... 08

September 30, 2019 · 1 min · jiezi

二springBoot-整合-mybatis-项目实战

前言上一篇文章开始了我们的springboot序篇,我们配置了mysql数据库,但是我们sql语句直接写在controller中并且使用的是jdbcTemplate。项目中肯定不会这样使用,上篇文章也说了,会结合mybatis 或者JPA 使用。我们这篇文章就来结合 mybatis 来使用吧,至于为什么选mybatis 而不是JPA ,这个看个人洗好吧。然后这篇文章会附带一讲一下今天为项目新增的配置。主要是为了保持项目和文章的一致性。 先贴出我们今天项目的结构吧,和昨天贴出来的差不多,在那基础上添加了一些东西,整个框架的模型算是成型了。 引入mybatis依赖一般改动都是从pom.xml 开始的,我们在昨天基础上的pom.xml 文件中加上mybatis 依赖,版本自己选吧。 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>Entry层昨天我们创建了一个user表 并插入了一条数据,我们就先用这张表吧,所以我们在entry 包中创建一个UserEntry 的实体类.代码如下: @Getter@Setterpublic class UserEntry { private int id; private String userName; private String password; private String email; private String roleCode; private String roleName; private String gmtCreate; private String gmtUpdate; private String nickname; private String userCreate;}可以看到这里用的注解和@Getter 和@Setter。这里就是避免代码中写入过多的get和 set 方法,使得代码变得更加简洁。 Dao 层Dao是用来处理数据的,这里我们引入了mybatis ,可以使用注解,也可以创建xml 文件,将sql 语句写在xml 文件中。我们这里就采用注解的方式吧,毕竟我们springboot项目,不想再出现xml配置文件了(后期也说不定哈哈)。 在dao包下创建一个userMapper 接口。代码如下: @Mapperpublic interface UserMapper { @Select("select id,username as userName,password,email,role_code as roleCode,gmt_create as gmtCreate,gmt_update as gmtUpdate,nickname as nickName,user_create as userCreate from sys_user") List<UserEntry> findUserList(); @Insert({"insert into sys_user(username,password,email) values('${user.userName}','${user.password}','${user.email}')"}) int add(@Param("user") UserEntry user); @Delete("delete from sys_user where id = #{id}") int delete(int id);}我们这里就先写一个查询、删除和插入的方法吧。可以看到,在接口上引入@Mapper 注解,然后就可以直接使用@Select 和@Insert 等注解啦,直接把注解sql 语句写在这里面就可以了。 ...

September 20, 2019 · 2 min · jiezi

Spring-框架基础02Bean的生命周期作用域装配总结

本文源码:GitHub·点这里 || GitEE·点这里 一、装配方式Bean的概念:Spring框架管理的应用程序中,由Spring容器负责创建,装配,设置属性,进而管理整个生命周期的对象,称为Bean对象。1、XML格式装配Spring最传统的Bean的管理方式。 配置方式<bean id="userInfo" class="com.spring.mvc.entity.UserInfo"> <property name="name" value="cicada" /></bean>测试代码ApplicationContext context01 = new ClassPathXmlApplicationContext("/bean-scan-02.xml");UserInfo userInfo = (UserInfo)context01.getBean("userInfo") ;System.out.println(userInfo.getName());2、注解扫描在实际开发中:通常使用注解 取代 xml配置文件。 常见注解@Component <==> <bean class="Class">@Component("id") <==> <bean id="id" class="Class">@Repository :Mvc架构中Dao层Bean的注解@Service:Mvc架构中Service层Bean的注解@Controller:Mvc架构中Controller层Bean的注解使用案例// 1、注解代码块@Component("infoService")public class InfoServiceImpl implements InfoService { @Override public void printName(String name) { System.out.println("Name:"+name); }}// 2、配置代码块@ComponentScan // 组件扫描注解public class BeanConfig {}测试代码@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = BeanConfig.class)public class Test01 { @Autowired private InfoService infoService ; @Test public void test1 (){ infoService.printName("cicada"); System.out.println(infoService==infoService); }}3、XML配置扫描上面使用 ComponentScan 注解,也可在配置文件进行统一的配置,效果相同,还简化代码。 <context:component-scan base-package="com.spring.mvc" />4、Java代码装配这种基于Configuration注解,管理Bean的创建,在SpringBoot和SpringCloud的框架中,十分常见。 ...

September 20, 2019 · 3 min · jiezi

一springboot起航

前言之前零零散散的学习了一些springboot的知识,以及搭建一些springboot的项目,甚至还有一些项目应用到实际项目中了,但是突然有一天想要建一个自己的项目网站。发现自己不知道从何开始。发现自己虽然用了很久,但是让自己 从头开始搭建一个却处处碰壁。所以静下心来好好的整理一下springboot的知识点。以及给自己搭建一个springboot 项目的脚手架。以后方便自己套用。 创建spring boot项目springboot的之所以火热便是因为开箱即用的特效,低配置甚至无配置使用,方便我们快速上手,我们这里先就什么都不配置吧。在idea 上直接可以创建springboot 类型项目。项目名就随便起吧,整个系列就都以这个项目为例啦,整个项目会分享到github 上,大家需要的可以跟着下载学习。建好的项目目录如下:其中选中的文件夹是我自己加的,因为我想整个项目的目录大概就是这个样子了。文件名起了zlflovemm 没有什么项目含义,起名太难了,就起了一个自己纪念的名字,大家勿怪。我们pom.xml 内容,因为后期不管是加其他组件,还是引用 jar 包什么的都是改这里。所以把最初版本拿出来。 <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.quellan</groupId> <artifactId>zlflovemm</artifactId> <version>1.0.0</version> <name>zlflovemm</name> <description>zlflovemm project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>可以看到pom.xml 文件里面东西很少了,<parent> 中是 springboot 版本信息。<properties> 是 jdk 版本信息。<dependencies>中的依赖只有一个starter-web 和starter-test 前面是这个项目支持web 项目,后面一个是支持单元测试,这两个都是创建项目的时候自带的。<build> 中就是项目构建打包的方式啦。这里暂时先用这种方式。 ...

September 20, 2019 · 2 min · jiezi

SpringBoot自动装配原理解析

本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序: @SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}和我们之前使用普通Spring时繁琐的配置相比简直不要太方便,那么你知道SpringBoot实现这些的原理么 首先我们看到类上方包含了一个@SpringBootApplication注解 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})})public @interface SpringBootApplication { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {};}这个注解上边包含的东西还是比较多的,咱们先看一下两个简单的热热身 ...

September 20, 2019 · 2 min · jiezi

有的线程它死了于是它变成一道面试题

有些线程它活着,但它躺在池中碌碌无为;有的线程它死了,于是它变成一道面试题。 这次的文章,要从一次阿里的面试说起。 我记得那天是周一,刚刚经历过周末过的放松,干劲十足的我正在键盘上疯狂的输出。这时,我的手机响了起来,拿起一看,是来自杭州的电话,心想这次是要给我推荐股票呢还是要让我贷款呢。我接起了电话,准备“调戏一番”。那边响起一个声音:"你好,请问是xxx吗?这边是杭州阿里巴巴,现在有时间进行电话面试吗?"。说实在的,听完这句话后,我感觉我已经身在杭州,干劲十足的在杭州的阿里的工位上"修福报"。但是我现在正在疯狂输出,没有时间,于是我说:"不好意思,现在没有时间,可以约在今天晚上8点钟吗?". 晚上如约接到了电话。我们直奔主题,在你来我往中进行了友好的技术交流。具体的面试过程就不详述了,后面有机会整理一份面试分享。整个面试过程中,有这么一道题给我留下了深刻的印象: 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?需要说明一下,文中讨论的线程池都是Executors线程池。 对于Executors线程池我可以说是烂熟于心,因为工作中用的比较的多,阅读过其源码。也是我作为面试官时必问的几个范围之一,比如以下问题: 了解JDK Executors线程池吗?知道JDK提供了哪些默认的实现吗?看过阿里巴巴java开发手册吗?知道为啥不允许使用默认的实现吗?你们没有用默认的吧?那来介绍一下你们自定义线程池的几个常用参数呗?你这个几个参数的值是怎么得来的呀?算出来的?怎么算出来的?线程池里面的任务是IO密集型的还是计算密集型的呢?好,现在我们有一个自定义线程池了,来说一下你这个线程池的工作流程呗?那你这个线程池满了怎么办呀?拒绝?咋拒绝?有哪些拒绝策略呢?别紧张,随便说两个就行。......回到开始说的阿里巴巴java开发手册不允许使用默认实现,你回答说可能会引起OOM,那我们聊聊JVM吧...... 阿里巴巴java开发手册关于线程池创建的建议 这一系列关于线程池的连环炮,就是我作为面试官时必问的几个问题。别问为什么,因为我们的招聘JD上明确写了:熟悉多线程编程。而这些问题,我觉得是熟悉多线程编程的基础。这里我也不解答了,这种文章网上还是挺多的,可以去了解一下。 这块真的很重要,我也多次给我的小伙伴强调: 来吧,一起分析一波好了现在回到阿里的面试官问我的这道面试题: 一个线程池中的线程异常了,那么线程池会怎么处理这个线程?先说说我当时的回答,因为心里没底,我的回答很犹豫也很烂!如下: 我的回答总结起来三句话: 1.抛出堆栈异常 ---这句话对了一半!2.不影响其他线程任务 ---这句话全对!3.这个线程会被放回线程池 ---这句话全错!测试用例写起来抛出堆栈异常为啥对了一半?先让程序跑起来,我们用事实说话:从执行结果我们看出 当执行方式是execute时,可以看到堆栈异常的输出。当执行方式是submit时,堆栈异常没有输出。那么我们怎么拿到submit执行方式的堆栈异常呢,看图说话:所以,现在知道为什么回答:抛出堆栈异常只对了一半吧。execute方法执行时,会抛出(打印)堆栈异常。submit方法执行时,返回结果封装在future中,如果调用future.get()方法则必须进行异常捕获,从而可以抛出(打印)堆栈异常。你以为这一部分写到这里就完事了?那不行啊,你心里没有一个疑问吗?为啥execute直接抛出异常,submit没有直接抛出异常呢? 源码之下无秘密:当执行方式是executes时:在java.util.concurrent.ThreadPoolExecutor#runWorker中抛出了异常:在_java.lang.ThreadGroup#uncaughtException_进行了异常处理:这个uncaughtException是何许人也,看java doc上咋说的:这个方法是JVM调用的,我们只需要指定我们想要的处理方式即可。那我们怎么指定呢: //直接new Thread()的时候Thread t=newThread();t.setUncaughtExceptionHandler(newThread.UncaughtExceptionHandler(){publicvoiduncaughtException(Thread t, Throwable e){//根据业务场景,做你想做的 }});//线程池的时候:ExecutorService threadPool = Executors.newFixedThreadPool(1, thread -> {Thread t =newThread(thread);t.setUncaughtExceptionHandler((t1, e) ->System.out.println("根据业务场景,做你想做的:"+ e.getMessage()));returnt;}); 当执行方式是submit时: 其本质也是调用了execute方法,所以它还是回到_java.util.concurrent.ThreadPoolExecutor#runWorker_方法:向前,继续跟进去看看:_java.util.concurrent.FutureTask#setException_干啥了啊,瞅一眼:深呼吸,整理好思路,我们马上走向最终的真相:好了,第一个议题【抛出堆栈异常为啥对了一半?】讨论完毕。在源码里面走了一趟,现在我们可以给出这一部分的满分答案了。 不影响其他线程任务,回答正确这一部分我们直接上代码,运行起来看结果吧:代码和运行结果是不会骗人的:线程池中一个线程异常了后,不影响其他线程任务大家注意线程名称这个细节:1,2,3,4,6。魔鬼都在细节里啊,这个点我下面会讲,先在这里把问题抛出来:我就纳闷了,怎么没有5啊?! 这个线程会被放回线程池为啥全错了?我们去源码里面寻找答案:让源码给出答案:5号线程去哪里了?new Worker()方法会告诉你:5去哪里了。再配上这张由我这个灵魂画师亲自操刀画的图,一起食用,味道更佳: 现在知道为啥:我回答这个线程会被放回线程池为啥全错了吧。还附带送你一个线程名称变化的细节,不客气。 总结一下当一个线程池里面的线程异常后:当执行方式是execute时,可以看到堆栈异常的输出。当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常。不会影响线程池里面其他线程的正常执行。线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。不要背答案,要理解,要深入,上面说完后记得在问问面试官,需要我从源码的角度讲一讲吗?这逼装的,礼貌而不失风度。以上,我关于《一个线程池中的线程异常了,那么线程池会怎么处理这个线程?》这个问题的见解就表达完毕,仅代表个人观点,欢迎有不同意见的小伙伴,一起讨论,一起进步。 最后说一点这篇文章是我上周五推完上一篇文章之后就在构思并且着手准备了。大部分内容都是思考于晚上睡觉前的半小时,写于周末和工作日的早上早起的一小时。其实想到写什么内容并不难,难的是你对内容的把控。关于技术性的语言,我是反复推敲,查阅大量文章来进行证伪,总之慎言慎言再慎言,毕竟做技术,我认为是一件非常严谨的事情,我常常想象自己就是在故宫修文物的工匠,在工匠精神的认知上,目前我可能和他们还差的有点远,但是我时常以工匠精神要求自己。就像我在群里表达的:对于技术文章(因为我偶尔也会荒腔走板的聊一聊生活,写一写书评,影评),我尽量保证周推,全力保证质量。最后,再感叹一次: 有些线程它活着,但它躺在池中碌碌无为;有些线程也活着,但它一刻不停忙到飞起;有的线程它死了,被抛弃,被回收,但是它无怨无悔, 因为它是死在执行任务的路上,它凭借自己最后的一声呐喊“为了新兄弟,移除我吧!”最后,变成一道面试题。 我还没答上来。 欢迎关注公众号【why技术】。在这里我会分享一些技术相关的东西,主攻java方向,用匠心敲代码,对每一行代码负责。偶尔也会荒腔走板的聊一聊生活,写一写书评,影评。愿你我共同进步。

September 19, 2019 · 1 min · jiezi

Spring-框架基础01核心组件总结基础环境搭建

本文源码:GitHub·点这里 || GitEE·点这里 一、Spring框架1、框架简介Spring是一个开源框架,框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。简单来说,Spring是一个分层的轻量级开源框架。2、优点分析1)、分层架构 一站式,每一个层都提供的解决方案web层:struts,spring-MVCservice层:springdao层:hibernate,mybatis,jdbcTemplate,JPA 2)、轻量级 依赖资源少,销毁的资源少。 3)、高内聚低耦合 Spring就是一个大容器,可以将所有对象创建和依赖关系统一维护,交给Spring管理。 4)、AOP编程的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。 5)、事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程 6)、集成测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序。 7)、降低API的使用难度 Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低 8)、集成各种框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的集成,如:Struts、Hibernate、MyBatis等。 二、核心组件分析 1、核心容器 容器是Spring框架的核心模式,该模块包含Bean的创建、配置、管理等功能。2、AOP编程 AOP 编程可以帮助应用程序解耦,使用AOP编程模式,可以把系统中的核心点从对象方法中解耦,统一管理。3、数据访问 该模块集成了JDBC,解决JDBC开发模式导致的大量代码冗余,集成常用的Dao层框架,hibernate,mybatis等,使开发环境的搭建更加便捷。4、Web编程 Spring不仅集成各种流程的MVC框架,还自带springmvc强大的框架,有助实现界面逻辑和应用程序分离,在Web层面实现应用的解耦。三、环境搭建项目结构图: 1、Spring环境配置spring-contextSpring框架上下文环境容器配置。 <!--读取外部配置文件--><bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- 允许JVM参数覆盖 --> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/> <!--忽略没有找到的配置参数--> <property name="ignoreResourceNotFound" value="true"/> <!--资源文件位置--> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property></bean><!-- 启动组件扫描,排除@Controller组件,该组件由SpringMVC配置文件扫描 --><context:component-scan base-package="com.spring.mvc"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /></context:component-scan><!-- 配置DRUID的连接池 --><bean id="druidDataSource" abstract="true"> <!-- 配置初始化,最小,最大 --> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}" /> <property name="maxActive" value="${jdbc.maxActive}" /> <!-- 配置连接等待超时时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中的最小生存时间,单位毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置监控统计拦截的filters,去掉后监控界面SQL无法统计 --> <property name="filters" value="stat" /></bean><!-- 设置数据源信息 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" parent="druidDataSource"> <!-- 配置连接的基本信息 --> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /></bean><!--Spring整合mybatis--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--读取mybatis配置文件--> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <!-- 自动扫描mapper.xml映射文件 --> <property name="mapperLocations" value="classpath:mybatis/mapper/**.xml"/> <!--分页助手插件--> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> dialect=mysql </value> </property> </bean> </array> </property></bean><!-- Mapper接口文件扫描 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.spring.mvc.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /></bean><!--设置JDBC操作数据库--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" lazy-init="false"> <property name="dataSource" ref="dataSource"/></bean><!--设置mybatis操作数据库--><bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/></bean><!--方式一:spring事物管理器--><bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 关联数据源 --> <property name="dataSource" ref="dataSource"/></bean><!--开启事务控制的注解支持--><tx:annotation-driven transaction-manager="dataSourceTransactionManager"/><!--配置手动事物管理--><bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="dataSourceTransactionManager"/></bean>spring-mvcMvc开发环境容器配置。 ...

September 11, 2019 · 2 min · jiezi

Springboot源码分析之代理对象内嵌调用

摘要:关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法。首先在这儿我要声明事务直接的嵌套调用除外,至于为什么,是它已经将信息保存在线程级别了,是不是又点儿抽象,感觉吃力,可以看看我前面关于事务的介绍。 @Async和@Transactional共存 @Component public class AsyncWithTransactional { @Async @Transactional public void test() { } }这样一段代码会发生什么?熟悉的人都会感觉疑惑,都有效果么?谁先被代理增强? 自动代理创建器AbstractAutoProxyCreator它实际也是个BeanPostProcessor,所以它们的执行顺序很重要~~~ 两者都继承自ProxyProcessorSupport所以都能创建代理,且实现了Ordered接口- - -- - ---AsyncAnnotationBeanPostProcessor默认的order值为Ordered.LOWEST_PRECEDENCE。但可以通过@EnableAsync指定order属性来改变此值。 AsyncAnnotationBeanPostProcessor在创建代理时有这样一个逻辑:若已经是Advised对象了,那就只需要把@Async的增强器添加进去即可。若不是代理对象才会自己去创建 public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof Advised) { advised.addAdvisor(this.advisor); return bean; } // 上面没有return,这里会继续判断自己去创建代理~ } }AbstractAutoProxyCreator默认值也同上。但是在把自动代理创建器添加进容器的时候有这么一句代码:beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); 自动代理创建器这个处理器是最高优先级由上可知因为标注有@Transactional,所以自动代理会生效,因此它会先交给AbstractAutoProxyCreator把代理对象生成好了,再交给后面的处理器执行由于AbstractAutoProxyCreator先执行,所以AsyncAnnotationBeanPostProcessor执行的时候此时Bean已经是代理对象了,此时它会沿用这个代理,只需要把切面添加进去即可~ 方法调用顺序影响想必大家都知道一点就是同类的方法调用只有入口方法被代理才会被增强,这是由于源码级别只处理入口方法调用,是你的话你也这样设计,不然方法栈那么深,你管得了那么多吗?既然知道了这个原因,那么我们接下来在看一下后面的列子。 沿用代理对象 java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available. at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69) at com.fsx.dependency.B.funTemp(B.java:14) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:206) at com.sun.proxy.$Proxy44.funTemp(Unknown Source) ...这个异常在上述情况最容易出现,然而解决的方法都是@EnableAspectJAutoProxy(exposeProxy = true) ...

September 10, 2019 · 2 min · jiezi

跟我学SpringCloud-第六篇Spring-Cloud-Config-Github配置中心

SpringCloud系列教程 | 第六篇:Spring Cloud Config Github配置中心Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 随着分布式项目越来越大,勤劳的程序猿们会开始面临一个挑战,配置文件会越来越繁杂,虽然spring提供了一个鸡肋版的解决方案,spring.profiles.active,在大型的分布式项目体系中,聊胜于无吧,手动维护配置文件的痛苦,生产,UAT,测试,开发环境的隔离,额外的配置文件,如:logback.xml日志的配置文件,bootstrap.properties配置文件,当系统中有几十个服务,相应的会有上百个配置文件,简直就是史诗级的灾难大片,每次发布上线,都要手动去检查配置文件,相应的服务都需要重启,那么,有没有一种方案,可以自动更新配置,并且对版本做出相应的控制,恰好,springcloud为我们提供了这样一种工具,虽然很多方面都还不完善,配置能力比较弱,但是也给我们提供了一种思路。 市面上有很多配置中心,BAT每家都出过,360的QConf、淘宝的diamond、百度的disconf都是解决这类问题。国外也有很多开源的配置中心Apache Commons Configuration、owner、cfg4j等等。这些开源的软件以及解决方案都很优秀,也存在这样或者那样的缺陷。今天我们要了解的Spring Cloud Config,可以无缝的和spring体系相结合,够方便够简单颜值高。 1. Spring Cloud Config在介绍Spring Cloud Config之前,我们可以先凭空想一下一个配置中心需要提供的核心功能有哪些: 提供客户端和服务端的支持提供各个环境的配置配置文件修改后可以快速生效可以提供不同版本的管理可以支持不同的语言(java、.Net、Delphi、node等)支持一定数量的并发高可用(防止意外宕机导致配置不可用)Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。Spring cloud使用git或svn存放配置文件,默认情况下使用git,我们先以git为例做一套示例。 首先在github上面创建了一个文件夹springcloud-config用来存放配置文件,为了模拟生产环境,我们创建以下三个配置文件: // 开发环境springcloud-config-dev.properties// 测试环境springcloud-config-test.properties// 生产环境springcloud-config-pro.properties每个配置文件中都写一个属性springcloud.hello,属性值分别是 hello dev/test/pro。下面我们开始配置server端 2. Server端1. pom.xml<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springcloud</groupId> <artifactId>config-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>config-server</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2. 配置文件server: port: 8080spring: application: name: spring-cloud-config-server cloud: config: server: git: uri: https://github.com/meteor1993/SpringCloudLearning # git仓库的地址 search-paths: chapter6/springcloud-config # git仓库地址下的相对地址,可以配置多个,用,分割。 username: #Git仓库用户名 password: #Git仓库密码Spring Cloud Config也提供本地存储配置的方式。我们只需要设置属性spring.profiles.active=native,Config Server会默认从应用的src/main/resource目录下检索配置文件。也可以通过spring.cloud.config.server.native.searchLocations=file:E:/properties/属性来指定配置文件的位置。虽然Spring Cloud Config提供了这样的功能,但是为了支持更好的管理内容和版本控制的功能,还是推荐使用git的方式。 ...

September 10, 2019 · 2 min · jiezi

跟我学SpringCloud-第五篇熔断监控Hystrix-Dashboard和Turbine

SpringCloud系列教程 | 第五篇:熔断监控Hystrix Dashboard和TurbineSpringboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 Hystrix-dashboard是一款针对Hystrix进行实时监控的工具,通过Hystrix Dashboard我们可以在直观地看到各Hystrix Command的请求响应时间, 请求成功率等数据。但是只使用Hystrix Dashboard的话, 你只能看到单个应用内的服务信息, 这明显不够。我们需要一个工具能让我们汇总系统内多个服务的数据并显示到Hystrix Dashboard上, 这个工具就是Turbine。 1. Hystrix Dashboard创建一个新的项目hystrix-dashboard,延用上一篇文章提到的eureka和producer两个项目。 1. hystrix-dashboard pom.xml 依赖包管理<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springcloud</groupId> <artifactId>hystrix-dashboard</artifactId> <version>0.0.1-SNAPSHOT</version> <name>hystrix-dashboard</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>前面介绍过的包我这里不再多说,讲几个前面没有见过的包: ...

September 10, 2019 · 2 min · jiezi

Springboot和vue整合

最近要把一个spirngboot项目和vue整合一下。于是百度了一下,大部分的教程都是build前端vue项目,然后把生成的dist目录下的文件拷贝到resources下的static下即可,但是按照以上教程实际上项目启动后无法正常访问。在摸索了一段时间之后,总结如下,首先看下整合后的目录结构之后我们看下具体的教程步骤 编译前端工程使用下面命令编译vue工程 npm run build编译后文件目录如下 拷贝文件到springboot目录将static下是css、fonts、img、js拷贝到springboot项目的/resources/static目录下将index.html文件拷贝到springboot项目的/resources/templates目录下 新增IndexController@Controller@RequestMapping("")public class IndexController { @RequestMapping("/index") public String index() { return "index"; }}此controller的作用是在访问项目的/index路劲时,能够跳转到index.html 新增静态资源映射@Configurationpublic class MvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); }}这样就能解决静态资源无法访问的问题 启动springboot项目在上述步骤都完成之后,启动springboot项目。以本地为例,浏览器访问http://localhost:8080/index即可

September 10, 2019 · 1 min · jiezi

Spring-IOC过程源码解析

废话不多说,我们先做一个傻瓜版的IOC demo作为例子自定义的Bean定义 class MyBeanDefinition{ public String id; public String className; public String value; public MyBeanDefinition(String id, String className, String value) { this.id = id; this.className = className; this.value = value; }}自定义的Bean工厂 class MyBeanFactory { Map<String, Object> beanMap = new HashMap<>(); public MyBeanFactory(MyBeanDefinition beanDefinition) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> beanClass = Class.forName(beanDefinition.className); Object bean = beanClass.newInstance(); ((UserService) bean).setName(beanDefinition.value); beanMap.put(beanDefinition.id, bean); } public Object getBean(String id) { return beanMap.get(id); }}测试傻瓜版IOC容器 ...

September 10, 2019 · 9 min · jiezi

跟我学SpringCloud-第四篇熔断器Hystrix

Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 1. 熔断器服务雪崩在正常的微服务架构体系下,一个业务很少有只需要调用一个服务就可以返回数据的情况,这种比较常见的是出现在demo中,一般都是存在调用链的,比如A->B->C->D,如果D在某一个瞬间出现问题,比如网络波动,io偏高,导致卡顿,随着时间的流逝,后续的流量继续请求,会造成D的压力上升,有可能引起宕机。 你以为这就是结束么,图样图森破,这才是噩梦的开始,在同一个调用链上的ABC三个服务都会随着D的宕机而引发宕机,这还不是结束,一个服务不可能只有一个接口,当它开始卡顿宕机时,会影响到其他调用链的正常调用,最终导致所有的服务瘫痪。 如下图所示: 熔断器相信大家都知道家用电闸,原来老式的电闸是使用保险丝的(现在很多都是空气开关了),当家里用电量过大的时候,保险丝经常烧断,这么做是保护家里的用电器,防止过载。 熔断器的作用和这个很像,它可以实现快速失败,如果在一段时间内服务调用失败或者异常,会强制要求当前调用失败,不在走远程调用,走服务降级操作(返回固定数据或者其他一些降级操作)。从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器也可以自动诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。 熔断器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。 Hystrix会有一个熔断时间窗口,具体转换逻辑如下: 熔断器就是保护服务高可用的最后一道防线。 2. Hystrix1. 断路器机制断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open)。这时所有请求会直接失败而不会发送到后端服务。断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN)。这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN)。Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。 2. FallbackFallback相当于是降级操作。对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值。fallback方法的返回值一般是设置的默认值或者来自缓存。 3. 资源隔离在Hystrix中, 主要通过线程池来实现资源隔离。通常在使用的时候我们会根据调用的远程服务划分出多个线程池。例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池。这样做的主要优点是运行环境被隔离开了。这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响。但是带来的代价就是维护多个线程池会对系统带来额外的性能开销。如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源。 3. Feign Hystrix上一篇我们使用了producer和consumers,熔断器是只作用在服务调用端,因此上一篇使用到的consumers我们可以直接拿来使用。因为,Feign中已经依赖了Hystrix所以在maven配置上不用做任何改动。 1. 配置文件application.yml新增server: port: 8081spring: application: name: spring-cloud-consumerseureka: client: service-url: defaultZone: http://localhost:8761/eureka/feign: hystrix: enabled: true其中新增了feign.hystrix.enabled = true 2. 创建fallback类,继承与HelloRemote实现回调的方法package com.springcloud.consumers.fallback;import com.springcloud.consumers.remote.HelloRemote;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.RequestParam;/** * Created with IntelliJ IDEA. * * @User: weishiyao * @Date: 2019/7/2 * @Time: 23:14 * @email: inwsy@hotmail.com * Description: */@Componentpublic class HelloRemoteFallBack implements HelloRemote { @Override public String hello(@RequestParam(value = "name") String name) { return "hello " + name + ", i am fallback massage"; }}3. 添加fallback属性在HelloRemote类添加指定fallback类,在服务熔断的时候返回fallback类中的内容。 ...

September 10, 2019 · 1 min · jiezi

跟我学SpringCloud-第三篇服务的提供与Feign调用

Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 上一篇,我们介绍了注册中心的搭建,包括集群环境吓注册中心的搭建,这篇文章介绍一下如何使用注册中心,创建一个服务的提供者,使用一个简单的客户端去调用服务端提供的服务。 本篇文章中需要三个角色,分别是服务的提供者,服务的消费者,还有一个是上一篇文章的主角——注册中心Eureka(使用单机版本即可,本篇的示例也会使用单机版本的Eureka)。 整体流程为: 先启动注册中心Eureka启动服务的提供者将提供服务,并将服务注册到注册中心Eureka上启动服务的消费者,在注册中心中找到服务并完成消费1. 服务提供者1. pom.xml<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springcloud</groupId> <artifactId>producer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>producer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2. 配置文件application.ymlserver: port: 8080spring: application: name: spring-cloud-producereureka: client: service-url: defaultZone: http://localhost:8761/eureka/3. 启动类ProducerApplication.java增加@EnableEurekaClient,如果是其他注册中心可以使用注解@EnableDiscoveryClient来进行服务的注册 ...

September 10, 2019 · 2 min · jiezi

Spring-中的-BeanFactory-与-FactoryBean

1.前提概要很多java开发者在使用Spring框架中都见过后缀为FactoryBean的类,比如Mybatis-Spring中的SqlSessionFactoryBean。说到这里就不得不提BeanFactory。FactoryBean和BeanFactory特别容易让人混淆,面试还经常问到这两种概念。其实它们的作用和使用场景是不一样的 2.BeanFactory先来说说BeanFactory。 用于访问Spring bean容器的根接口。这是Spring bean容器的基本客户端视图。原来是获取Spring Bean的接口,也就是IoC容器。然后我们看类图 原来我们更常用的ApplicationContext就是一个BeanFactory。我们通过bean的名称或者类型都可以从BeanFactory来获取bean。对于BeanFactory这么介绍相信都不陌生了。让我们把关注点转向FactoryBean上。 3.FactoryBeanFactoryBean 是个什么玩意儿呢?来看看源码 public interface FactoryBean<T> { @Nullable T getObject() throws Exception; @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }T getObject() 获取泛型T的实例。用来创建Bean。当IoC容器通过getBean方法来FactoryBean创建的实例时实际获取的不是FactoryBean 本身而是具体创建的T泛型实例。等下我们会来验证这个事情。Class<?> getObjectType() 获取 T getObject()中的返回值 T 的具体类型。这里强烈建议如果T是一个接口,返回其具体实现类的类型。default boolean isSingleton() 用来规定 Factory创建的的bean是否是单例。这里通过默认方法定义为单例。3.1 FactoryBean使用场景FactoryBean 用来创建一类bean。比如你有一些同属鸟类的bean需要被创建,但是它们自己有各自的特点,你只需要把他们的特点注入FactoryBean中就可以生产出各种鸟类的实例。举一个更加贴近实际生产的例子。甚至这个例子你可以应用到实际java开发中去。我们需要自己造一个定时任务的轮子。用FactoryBean 再合适不过了。我们来用代码说话一步步来演示FactoryBean的使用场景。 3.2 构建一个FactoryBean我们声明定时任务一般具有下列要素: 时间周期,肯定会使用到cron表达式。一个任务的执行抽象接口。定时任务具体行为的执行者。Task任务执行抽象接口的实现。实现包含两个方面: SomeService 是具体任务的执行逻辑。cron时间表达式public class CustomTask implements Task { private SomeService someService; private String cronExpression; public CustomTask(SomeService someService) { this.someService = someService; } @Override public void execute() { //do something someService.doTask(); } @Override public void setCron(String cronExpression) { this.cronExpression = cronExpression; } @Override public String getCron() { return cronExpression; }}通过以上的定义。任务的时间和任务的逻辑可以根据不同的业务做到差异化配置。然后我们实现一个关于Task的FactoryBean。 ...

September 9, 2019 · 2 min · jiezi

SpringBoot-任务

一、异步任务在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完美解决这个问题。 1.启动器上添加 @EnableAsync 注解 @EnableScheduling //开启注解的定时任务@SpringBootApplicationpublic class Springboot04TaskApplication { public static void main(String[] args) { SpringApplication.run(Springboot04TaskApplication.class, args); }}2.service 添加注解 @Servicepublic class AsyncService { //告诉Spring这是一个异步任务 @Async public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("处理数据中..."); }}3.controller @RestControllerpublic class AsyncController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello(){ asyncService.hello(); return "Success"; }}访问效果: 若果没有开启异步,访问 /hello 3秒后,控制台打印 “处理数据中...”以及返回 “Success”。开启后:访问 /hello 直接返回 “Success”,3秒后控制台打印返回 “Success”。二、定时任务 1.启动器上添加 @EnableScheduling 注解 2.需要执行的方法添加 @Scheduled(cron="") ...

September 9, 2019 · 2 min · jiezi

阿里巴巴资深技术专家雷卷值得开发者关注的-Java-8-后时代的语言特性

首先我们必须承认,Java 8 是一个里程碑式的版本,这个相信大多数Java程序员都认同,其中最知名的是 Streams & Lambda ,这让 Functional Programming 成为可能,让 Java 换发新的活力。这也是即便 Oracle 不在支持 Java 8 的更新,各个云厂商还是积极支持,站点为https://adoptopenjdk.net/,可以让 Java 8 能继续保留非常长的时间。 目前非常多的同学日常开发并没有切换到 Java 8 后续的版本,所以这篇文章,我们打算写一个后 Java 8 时代的特性,主要是偏向于开发的,不涉及 GC , Compiler , Java Module , Platform 等,如果一一解释,估计非常长的文章,当然后续可以写另外文章介绍。下面的这些特性会影响到我们日常的代码编写。 考虑到 Java 13 马上发布,所以版本覆盖从 9 到 13 ,与此同时 Java Release 的方式调整,一些特性是在某一版本引入(preview),后续收到反馈后做了非常多的增强和完善,这里就不一一说明特性是哪个版本的,你可以理解为后Java 8版本后的特性大杂烩。参考资料来源于官方 features 和 pluralsight 上每一个版本的 Java 特性介绍。 var 关键字(局部变量类型推导) Local-Variable Type InferenceJava 支持泛型,但是如果类型非常长,你又不是特别关注,你用 var 关键字就可以啦,可以让你代码非常简洁。Java IDE 都非常好地支持 var,不用担心代码提示等问题。 Map<String, List<Map<String,Object>>> store = new ConcurrentHashMap<String, List<Map<String,Object>>>(); Map<String, List<Map<String,Object>>> store = new ConcurrentHashMap<>(); Map<String, List<Map<String,Object>>> store = new ConcurrentHashMap<String, List<Map<String,Object>>>(); //lambda BiFunction<String, String, String> function1 = (var s1, var s2) -> s1 + s2; System.out.println(function1.apply(text1, text2));复制 confd 文件到 bin 目录下,启动 confd ...

September 9, 2019 · 2 min · jiezi

在Spring-Boot中使用OAuth2保护REST服务

解释OAuth2技术正如我所说,我们将使用OAuth2协议,因此首先要解释这个协议是如何工作的。OAuth2有一些变体,但我将解释我将在程序中使用的内容,为此,我将给你一个例子,以便你了解我们打算做什么。举个例子,在商店里用信用卡付款。在这种场景下,有三个角色:商店、银行和我们。OAuth2协议中也发生了类似的事情,就像这样:1.客户或买方需要银行提供信用卡。然后,银行将收集我们的信息,核实我们是谁,并根据我们帐户中的资金向我们提供信用卡或者直接拒绝我们。在授予卡的OAuth2协议中,它称为身份验证服务器。2.如果银行给了我们卡,我们可以去商店,即网络服务器,我们提供信用卡。商店不欠我们任何东西,但他们可以通过读卡器向银行询问他们是否可以信任我们以及信用余额(信用余额)。商店是资源服务器。3.商店,根据银行说我们拥有的钱,将允许我们购买。在OAuth2类比中,Web服务器将允许我们访问页面,具体取决于我们的财务状况。如果您没有注意到通常使用身份验证服务器,当您转到网页并被要求注册时,它允许您通过Facebook或Google进行。Facebook或Google成为发行“卡”的“银行”,并会验证您的“信用”是否足够支付这个商品。您可以看到“El Pais”的网站并创建一个帐户。如果我们使用Google或Facebook,这个商店将依赖这些身份验证提供商提供的客户身份信息。在这种情况下,网站唯一需要的是拥有信用卡 - 无论余额如何创建授权服务器现在,让我们看看如何创建银行、商店以及您需要的所有其他内容。首先,在我们的项目中,我们需要具有适当的依赖关系。我们需要启动者:Cloud OAuth2,Security和Web。那么,让我们从定义银行开始; 这就是我们之前说的:  AuthorizationServerConfiguration:我们从 @ Configuration 标签开始,然后使用  @EnableAuthorizationServer 标记告诉Spring激活授权服务器。要定义服务器属性,我们指定我们的类扩展  AuthorizationServerConfigurerAdapter,实现了  AuthorizationServerConfigurerAdapter接口,所以Spring将使用这个类来参数化服务器。我们定义了一个Spring自动提供的AuthenticationManager ,我们将使用它来收集@Autowired标签。我们还定义了一个  TokenStore对象,作为public的功能 。 虽然 AuthenticationManager由Spring提供的,但我们必须自己配置它。我等等解释要如何完成这个配置。TokenStore或者IdentifierStore是身份验证服务器提供的标识符将存储的位置,因此当资源服务器(商店)要求信用卡上的信息时,身份验证服务器就要响应它。在这种情况下,我们使用  InMemoryTokenStore将标识符存储在内存中的类。在实际应用中,我们可以使用JdbcTokenStore将它们保存在数据库中,以便在应用程序发生故障时,客户端不必更新其信用卡。在功能配置中 (ClientDetailsServiceConfigurer clients),我们指定银行的凭证,包括身份验证的管理员,以及提供的服务。因为要访问银行,我们必须为每个提供的服务提供用户名和密码。这是一个非常重要的概念:用户名和密码来自银行,而不是客户。对于银行提供的每项服务,将进行单一认证,但对于不同的服务可能相同。我将详细说明这些内容: clients.inMemory ()指定我们将服务存储在内存中。在“真正的”应用程序中,我们将其保存在数据库,LDAP服务器等中。 withClient ("client")是我们将在银行中识别的用户。在这种情况下,它将被称为“客户端”。将他称为“用户”会不会更好? 要  uthorizedGrantTypes ("password", "authorization_code", "refresh_token", "implicit") ,我们指定配置定义的用户,对服务“ 客户端 ”。在我们的示例中,我们将仅使用密码服务。 authorities ("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT", "USER") 指定所提供服务包含的角色或组。我们也不会在我们的例子中使用它,所以让我们让它暂时运行。 scopes ("read", "write")  是服务的范围 - 我们也不会在我们的应用程序中使用它。 autoApprove (true)-如果您必须自动批准客户的请求,我们会说是,以使应用程序更简单。 secret (passwordEncoder (). encode ("password")) 是客户端的密码。请注意,调用我们稍后定义的编码函数来指定密码将保存在何种类型的加密中。的编码功能进行注释与@Bean标签因为Spring,当我们在HTTP请求中提供密码时,会查找一个  PasswordEncoder对象检查交付密码的有效性。最后,我们有一个函数   configure (AuthorizationServerEndpointsConfigurer endpoints) ,我们定义哪个身份验证控制器和标识符存储应该使用端点。澄清终点是我们将与我们的“银行”联系以请求卡的URL。现在,我们已经创建了我们的身份验证服务器,但是根据引入的凭据,我们仍然需要他知道我们是谁并将我们放在不同的组中的方式。好吧,为此,我们将使用与保护网页相同的类。现在,我们可以检查我们的授权服务器是否有效。让我们看看如何使用优秀的PostMan程序。我们将使用HTTP请求类型POST,表明我们要使用基本验证。在我们的示例中,我们将分别使用“client”和“password”来设置用户和密码,即“银行”的密码。在请求正文和form-url编码格式中,我们将介绍要请求的服务,用户名和密码。'access_token'“ 8279b6f2-013d-464a-b7da-33fe37ca9afb ”是我们的信用卡,是我们必须提供给我们的资源服务器(商店)以查看非公开的页面(资源)的信用卡。创建资源服务器(ResourceServer)现在我们有了信用卡,我们将创建接受该卡的商店。在我们的示例中,我们将使用Spring Boot在相同的程序中创建资源和身份验证服务器,它无需配置任何内容。如果像现实生活中一样,资源服务器在一个地方,而身份验证服务器在另一个地方,我们应该向资源服务器指出哪个是我们的“银行”以及如何与之交谈。但是,我们将把它留给另一个条目。资源服务器的唯一类是  ResourceServerConfiguration:由于身份验证和资源服务器在同一个程序中,我们只需要配置资源服务器的安全性。这是在函数中完成的:一旦我们创建了资源服务器,我们必须只创建服务,这些服务是通过这些行完成的:现在让我们看看验证的工作原理。首先,我们检查我们是否可以在没有任何验证的情况下访问“/ publica”:如果我尝试访问“/ private”页面,则会收到错误“401 unauthorized”,表示我们无权查看该页面,因此我们将使用我们的授权服务器给用户授权。如果我们可以看到我们的私人页面,那么让我们尝试管理员的页面:我们当然没办法看到管理员的界面。因此,我们将要求凭据管理员提供新令牌,但要向用户“管理员”表明身份。返回的令牌是:“ab205ca7-bb54-4d84-a24d-cad4b7aeab57。” 这样就没问题了,我们可以安全地去购物!现在,我们只需要设置商店并拥有产品。本人创业团队产品MadPecker,主要做BUG管理、测试管理、应用分发,有需要的朋友欢迎试用、体验!本文为MadPecker团队产品经理译制,转载请标明出处

September 9, 2019 · 1 min · jiezi

springbootplusV123发布CentOS快速安装环境构建部署启动项目

spring-boot-plusV1.2.3发布,CentOS快速安装环境/构建/部署/启动项目[V1.2.3-RELEASE] 2019.09.09 spring-boot-plusV1.2.3发布,CentOS快速安装环境/构建/部署/启动项目⭐️ New Features项目运行环境安装脚本CentOS快速构建/部署/启动项目脚本⚡️ Optimization优化 maven-assembly-plugin 项目打包插件???? Added/ModifiedAdd install-jdk.sh yum安装jdk8脚本Add install-git.sh yum安装git脚本Add install-maven.sh yum安装maven脚本Add install-redis.sh yum安装redis脚本Add install-mysql.sh yum安装mysql脚本Add install-all.sh 安装所有环境脚本Add download-install-all.sh 下载并安装所有环境脚本Add deploy.sh 下载项目/构建/部署/启动项目脚本Add maven-javadoc-plugin java api docs???? DocumentationCentOS Quick Installation Environment / Build / Deploy / Launch Spring-boot-plus Project spring-boot-plus java docs???? Dependency UpgradesUpgrade to springboot 2.1.8.RELEASEUpgrade to Mybatis 3.5.2Upgrade to Mybatis Plus 3.2.0Upgrade to Alibaba Druid 1.1.20Upgrade to Fastjson 1.2.60Upgrade to commons-codec 1.13Upgrade to commons-collections 4.4Upgrade to hutool-all 4.6.4CentOS快速安装环境/构建/部署/启动spring-boot-plus项目1. 下载安装脚本安装 jdk, git, maven, redis, mysqlwget -O download-install-all.sh https://raw.githubusercontent.com/geekidea/spring-boot-plus/master/docs/bin/install/download-install-all.sh2. 运行安装脚本sh download-install-all.sh3. 修改MySQL密码ALTER USER 'root'@'localhost' IDENTIFIED BY 'Springbootplus666!';exitmysql -uroot -pSpringbootplus666!4. 导入MySQL脚本create database if not exists spring_boot_plus character set utf8mb4;use spring_boot_plus;source /root/mysql_spring_boot_plus.sql;show tables;exit5. 下载部署脚本 deploy.shwget -O deploy.sh https://raw.githubusercontent.com/geekidea/spring-boot-plus/master/deploy/deploy.sh6. 执行脚本sh deploy.sh7.访问项目SpringBootAdmin管理页面http://47.105.159.10:8888 ...

September 9, 2019 · 1 min · jiezi

跟我学SpringCloud-第十七篇服务网关Zuul基于Apollo动态路由

SpringCloud系列教程 | 第十七篇:服务网关Zuul基于Apollo动态路由Springboot: 2.1.7.RELEASESpringCloud: Greenwich.SR2 上一篇文章我们介绍了Gateway基于Nacos动态网关路由的解决方案《Spring Cloud Alibaba | Gateway基于Nacos动态网关路由》,同为Spring Cloud服务网关组件的Spring Cloud Zuul在生产环境中使用更为广泛,那么它有没有方便的动态路由解决方案呢?答案当然是肯定的,Zuul作为一个老牌的开源服务网关组件,动态路由对它来讲是一个十分必要的功能,毕竟我们不能随便重启服务网关,服务网关是一个微服务系统的大门,今天我们介绍的Zuul动态路由的解决方案来自于携程开源的配置中心Apollo。 Apollo概述Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。 Apollo支持4个维度管理Key-Value格式的配置: application (应用)environment (环境)cluster (集群)namespace (命名空间)Apollo相比于Spring Cloud Config优势前面的文章我们也介绍了Spring Cloud Config《跟我学SpringCloud | 第七篇:Spring Cloud Config 配置中心高可用和refresh》,但是它和我们今天要使用的相比,又有什么劣势呢? Spring Cloud Config的精妙之处在于它的配置存储于Git,这就天然的把配置的修改、权限、版本等问题隔离在外。通过这个设计使得Spring Cloud Config整体很简单,不过也带来了一些不便之处。 功能点ApolloSpring Cloud Config备注配置界面一个界面管理不同环境、不同集群配置无,需要通过git操作 配置生效时间实时重启生效,或手动refresh生效Spring Cloud Config需要通过Git webhook,加上额外的消息队列才能支持实时生效版本管理界面上直接提供发布历史和回滚按钮无,需要通过git操作 灰度发布支持不支持 授权、审核、审计界面上直接支持,而且支持修改、发布权限分离需要通过git仓库设置,且不支持修改、发布权限分离 实例配置监控可以方便的看到当前哪些客户端在使用哪些配置不支持 配置获取性能快,通过数据库访问,还有缓存支持较慢,需要从git clone repository,然后从文件系统读取 客户端支持原生支持所有Java和.Net应用,提供API支持其它语言应用,同时也支持Spring annotation获取配置支持Spring应用,提供annotation获取配置Apollo的适用范围更广一些工程实战这里需要准备一个Apollo配置中心,具体如何构建Apollo配置中心我这里不多做介绍,大家可以参考Apollo的官方文档:https://github.com/ctripcorp/... 工程依赖pom.xml如下:代码清单:chapter16/pom.xml <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>${apollo-client.version}</version></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>配置文件app.properties如下: 代码清单:chapter16/src/main/resources/META-INF/app.properties app.id=123456789这里配置的app.id是在Apollo中创建项目时配置的。 application.yml如下: 代码清单:chapter16/src/main/resources/application.yml apollo: bootstrap: enabled: true namespaces: zuul-config-apollo Meta: http://localhost:8080在Apollo上新建一个命名空间zuul-config-apollo。 ...

September 9, 2019 · 1 min · jiezi

有哪些你不知道的阅读源码的技巧

1. 先看官方文档和架构图优秀的开源组件官方都会维护文档和架构图,这份架构图上或许有一些最重要的组件之间的关联关系、或许哪些功能的调用流程、或许有一些别的东西,但是相信我,这些东西一定都是从总体来描述这个项目的,这个一定是你要阅读源码时第一个要看的 2. 再看项目的组织结构下载下来代码之后,不要急着开始。先看一下各个包名和包里的类名,对照着文档和类名先简单猜一下各个类的大致作用 3. 找到启动demo,把项目跑起来阅读源码不仅仅是阅读,要让项目跑起来,去调试它,去观察和改变它的运行路线 4. 找到阅读的起点很多人都想阅读源码,但是面对庞大的代码库不知道如何下手。这个时候你就要明确你的目标。可以从启动方法开始、也可以从具体的哪个功能开始。总之要找到你的起点 5. 理清主干一个优秀的开源软件总是经过了很多工程师很多年的努力孵化出来的,你去阅读它的时候很难把整个软件全部都整明白。所以一定要认准自己的目标,朝着自己的目标去读,当过程中出现一些不太重要的分枝时可以适当的忽略来节约时间 6. 把你的结论记下来好记性不如烂笔头,我们从小就知道的一句话。包括却不限于笔记、流程图、截图等任何你擅长的工具,把它记下来。另外,最好总结一下重点部分方便面试的时候快速复习 7. 阅读时使用的小技巧查看类的继承体系快捷键:Ctrl+H 查看方法的调用层级优秀的源码往往调用层级很深,当你debug到某个点却忘掉了怎么进来的或者说不知道哪个地方调用了这个方法,只需要在方法名上使用Ctrl +Alt+H即可查看这个方法的调用层级 查看类UML图当使用Ctrl +Alt+Shift+u会在新的标签页中展示当前类的UML继承图这个继承图相比较于第一个查看类的继承体系外还有以下优点: 使用UML图形展示看起来更舒服更全面支持手动排除不相关的类和接口支持展示类的属性和方法等相关信息当你仅仅只关注UML图时还可以使用Ctrl +Alt+u在当前标签页浮动显示一个图层Debug时修改变量当你在Debug的时候可以使用Alt+F8唤起这个界面在这个输入框中,你可以直接修改当前能够看到的变量,当存在以下场景时这个功能真的是绝配 当存在很多分支的时候修改某个变量来改变代码运行的逻辑不确定某句代码结果时可以直接在文本框输入,而不需要再次重启程序记住上方这7点,相信你会变得更加优秀,而我则使用这7点在一个月的时间阅读了Spring的源码Spring源码解析系列汇总

September 9, 2019 · 1 min · jiezi

面向微服务的体系结构评审中需要问的三个问题咖啡杂谈Java新闻故事和观点

面向微服务的体系结构如今风靡全球。这是因为更快的部署节奏和更低的成本是面向微服务的体系结构的基本承诺。 然而,对于大多数试水的公司来说,开发活动更多的是将现有的单块应用程序转换为面向微服务的体系结构,这可能是许多层面上阻碍和冲突的根源。 虽然Greenfield (未开发的)面向微服务的体系结构实现可以坚持对当前微服务的严格解释-设计原则。但在面向微服务的体系结构中,分解的遗留应用程序存在灰色阴影,如果没有其他原因,只能满足预算和时间限制。 在企业管理链的某个地方,有一位业务主管在一个面向微服务的体系结构中查看与这些遗留应用程序相关的分解成本,并将其与遗留代码已经提供的价值进行比较。一旦开发成本超过了预期的收益,业务主管很可能会退出并取消该项目。 这种事经常会发生。 因此,开发经理面临着巨大的压力,要求他们尽快将代码输出。“足够好”地成为转型的理想目标。 现在,这不一定是一件坏事。与等待梦想到来相比,输出工作代码的能力总是更好。但是,“灰色的阴影”是很难管理的,问题就在于如何界定“足够好”的界限。 因此,冲突开始了。一方想要输出他们想要的东西,而另一方则希望做更多的改进。 对你来说,挑战是不要让这些不同学派在本质上是信仰支持的观点上制造一场没完没了的争吵。如果您这样做了,它将造成一种情况,即根本不提供任何代码。现在,冲突可以从许多相互竞争的想法中综合出最好的想法。但是,当话语退化为永无止境的冲突时,它可能是致命的。 我通过集中讨论以下三个问题来处理这类情况,以避免这种冲突: 设计的理由是什么?风险有多大?减少风险的计划是什么?请允许我详细说明。 1. 设计的理由是什么?当您评估面向微服务的体系结构的设计时,所面临的挑战是将过去的观点转移到理论基础分析上。它的创建主要来自于单个应用程序的分解。任何设计都可能“足够好”,只要你能证明它的好处和价值。 例如,面向微服务的体系结构设计的首选样式之一是采用事件驱动的方法进行服务间通信。具体来说,这意味着您使用消息节点以异步方式在微服务之间传递消息。然而,从长远来看,虽然异步通信更加灵活和可扩展,但消息系统实现比在“面向”微服务的API之间使用同步HTTP调用的设计要复杂得多。因此,当市场时间被关注时,完全有理由将单块应用程序中的特性重构为以HTTP API方式表示的独立的微服务。 与异步服务相比,同步微服务的实现通常不那么复杂。 从长远来看,同步通信不一定是最佳选择,但考虑到从单块应用程序中提取独立的微服务所需的所有其他工作,同步对于第一个版本来说是“足够好”的。因此,这是一个合理的理由。 然而,这并不是说同步方法没有风险。事实上,风险有很多。当涉及到审查面向微服务的体系结构设计时,仅仅说明理由并不是唯一的因素。风险也必须加以阐述。 2. 风险有多大?所有的设计都有内在的风险。在上面描述的同步设计示例中,这种服务间通信方法可能会导致服务之间类型耦合的风险,由于同步HTTP通信和其他通信的性质而增加延迟增加延迟。 重要的是要让人们知道这些风险,这样就可以根据预期设计的合理性来权衡它们。如果风险是巨大的,再多的理由也是不够的。另一方面,考虑到目前的需求,某些风险可能是可以接受的。诀窍是确保风险在审查过程中得到明确的传达。讨论中已知的风险总是比隐藏的风险更可取,而这种风险可能会在路上造成冲击。此外,如果您以前知道风险,那么随着面向微服务的体系结构的成熟,您可以计划如何在未来的版本中更好地向前迈进。这就是减少风险的原因。 3. 减少风险的计划是什么?一个明智的应用程序设计人员的一个标志是能够识别他们的设计风险,一旦确定下来他会有远见地阐明一种方法,以减轻这些风险。没有适当的缓解技术的风险识别是思维不完整的标志。 如果面向微服务的体系结构设计有很大的风险和解决这些问题的边际计划,那么设计团队需要认真考虑其可行性。此外,如果缓解计划不切实际-超出项目的专门知识和预算-设计的可行性也需要质疑。这都是平衡的问题。 一个平衡良好的面向微服务的体系结构设计是合理的,因为它想要满足的条件与其固有的设计风险和旨在解决这些风险的缓解计划相权衡。 4. 把它们放在一起冲突是创造性进程的重要组成部分。有创造力的人往往对自己的想法坚韧不拔。所以,当你把它们放在一个房间里,让他们为面向微服务的建筑设计一个单一的设计时,紧张关系肯定会加剧。事情就是这样的。但要振作起来!冲突是好事。 幸运的是,有了一种理性的方法,用我前面描述的三个问题来审查面向微服务的体系结构设计,您就可以促进客观的讨论,从而产生软件以及时满足您的需求。没有任何设计是完美的,特别是那些分解单个应用程序的设计。但是,交付面向微服务的体系结构有一个很大的好处,这个体系结构足够好有效运作在短期和灵活性足够持续不断改善长期。 原文:https://www.theserverside.com...作者:Bob Reselman 译者:遗失的拂晓

September 9, 2019 · 1 min · jiezi

springboot-源码解析springboot-依赖管理梳理图

在文章 【spring-boot 源码解析】spring-boot 依赖管理 中,我梳理了 spring-boot-build、spring-boot-parent、spring-boot-dependencies、spring-boot-starter-parent 依赖之间的关系,以及我们平常应该怎么用,这次奉上一张梳理图。 公众号:逸飞兮(专注于 Java 领域知识的深入学习,从源码到原理,系统有序的学习)

September 8, 2019 · 1 min · jiezi

Springboot源码分析之Spring循环依赖揭秘

摘要:若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘Spring循环依赖的最本质原因。 Spring循环依赖流程图 Spring循环依赖发生原因使用了具有代理特性的BeanPostProcessor典型的有 事务注解@Transactional,异步注解@Async等 源码分析揭秘 protected Object doCreateBean( ... ){ ... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ... // populateBean这一句特别的关键,它需要给A的属性赋值,所以此处会去实例化B~~ // 而B我们从上可以看到它就是个普通的Bean(并不需要创建代理对象),实例化完成之后,继续给他的属性A赋值,而此时它会去拿到A的早期引用 // 也就在此处在给B的属性a赋值的时候,会执行到上面放进去的Bean A流程中的getEarlyBeanReference()方法 从而拿到A的早期引用~~ // 执行A的getEarlyBeanReference()方法的时候,会执行自动代理创建器,但是由于A没有标注事务,所以最终不会创建代理,so B合格属性引用会是A的**原始对象** // 需要注意的是:@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization创建的代理 // 从这我们也可以看出@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~) // 结论:此处给A的依赖属性字段B赋值为了B的实例(因为B不需要创建代理,所以就是原始对象) // 而此处实例B里面依赖的A注入的仍旧为Bean A的普通实例对象(注意 是原始对象非代理对象) 注:此时exposedObject也依旧为原始对象 populateBean(beanName, mbd, instanceWrapper); // 标注有@Async的Bean的代理对象在此处会被生成~~~ 参照类:AsyncAnnotationBeanPostProcessor // 所以此句执行完成后 exposedObject就会是个代理对象而非原始对象了 exposedObject = initializeBean(beanName, exposedObject, mbd); ... // 这里是报错的重点~~~ if (earlySingletonExposure) { // 上面说了A被B循环依赖进去了,所以此时A是被放进了二级缓存的,所以此处earlySingletonReference 是A的原始对象的引用 // (这也就解释了为何我说:如果A没有被循环依赖,是不会报错不会有问题的 因为若没有循环依赖earlySingletonReference =null后面就直接return了) Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 上面分析了exposedObject 是被@Aysnc代理过的对象, 而bean是原始对象 所以此处不相等 走else逻辑 if (exposedObject == bean) { exposedObject = earlySingletonReference; } // allowRawInjectionDespiteWrapping 标注是否允许此Bean的原始类型被注入到其它Bean里面,即使自己最终会被包装(代理) // 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~ // 另外dependentBeanMap记录着每个Bean它所依赖的Bean的Map~~~~ else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // 我们的Bean A依赖于B,so此处值为["b"] String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); // 对所有的依赖进行一一检查~ 比如此处B就会有问题 // “b”它经过removeSingletonIfCreatedForTypeCheckOnly最终返返回false 因为alreadyCreated里面已经有它了表示B已经完全创建完成了~~~ // 而b都完成了,所以属性a也赋值完成儿聊 但是B里面引用的a和主流程我这个A竟然不相等,那肯定就有问题(说明不是最终的)~~~ // so最终会被加入到actualDependentBeans里面去,表示A真正的依赖~~~ for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } // 若存在这种真正的依赖,那就报错了~~~ 则个异常就是上面看到的异常信息 if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } } ... }问题简化发生循环依赖时候Object earlySingletonReference = getSingleton(beanName, false);肯定有值缓存工厂addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));将给实例对象添加SmartInstantiationAwareBeanPostProcessorAbstractAutoProxyCreator是SmartInstantiationAwareBeanPostProcessor的子类,一定记住了,一定记住,SmartInstantiationAwareBeanPostProcessor的子类很关键!!!!!exposedObject = initializeBean(beanName, exposedObject, mbd);进行BeanPostProcessor后置处理,注意是BeanPostProcessor!!!!!Spring的循环依赖被它的三级缓存给轻易解决了,但是这2个地方的后置处理带来了 循环依赖的问题。 ...

September 8, 2019 · 3 min · jiezi

消息队列RabbitMQ一

世界将我包围 誓死都一齐壮观得有如 悬崖的婚礼概述消息队列,简称MQ(Message Queue), 目的是为了提高系统的异步通信、扩展解耦能力 如注册为列子,一般注册成功后会发送一条消息给用户,如果当我们将数据保存成功后,继续执行发送消息的逻辑,这时候系统就是在等待发送逻辑运行,会降低系统的速度;其实注册成功后发送消息不是要立即响应成功的,我们可以注册成功后,将要发送的消息放入消息队列,然后发送消息的逻辑代码根据消息队列信息去执行,便提高了系统的运行速度还应用在订单系统的应用解耦,秒杀系统的流量削峰两种形式队列(queue):点对点消息通信主题(topic):发布(publish)/订阅(subscribe)消息通信点对点式:消息发送者发送消息,消息代码将其放入一个队列中,消息接受者从队列获取消息,消息读取后移出队列消息只有唯一的发送者和接受者,但不是只有一个接收者,即A、B、C都接收到了这个消息,可是消息被A所接受了,B、C接收到了但是使用不了订阅式:发送者发送消息到主题,多个接受者监听订阅这个主题,那么消息在到达的同时收到消息JMS和AMQPJMS(Java Message Service),JAVA消息服务,是一个API;基于JVM消息代理的规范,ActiveMQ是JMS实现的AMQP(Advanced Message Queuing Protocol),是一个高级消息队列协议,兼容JMS,RabbitMQ是AMQP的实现 RabbitMQ简介RabbitMQ从整体上来看是一个典型的生产者消费者模型,主要负责接收、存储和转发消息。架构图如下: Product,生产者生产者连接到RabbitMQ Broker,建立一个连接( Connection)开启一个信道(Channel)生产者声明一个交换器,并设置相关属性,比如交换机类型、是否持久化等生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等生产者通过路由键将交换器和队列绑定起来生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息。相应的交换器根据接收到的路由键查找相匹配的队列。如果找到,则将从生产者发送过来的消息存入相应的队列中。如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者关闭信道,关闭连接。Exchange,交换器在RabbitMQ中,生产者并非直接将消息投递到队列中。真实情况是,生产者将消息发送到Exchange(交换器),由交换器根据路由规则将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。 RoutingKey,路由KEY指定消息的路由规则,需要与交换器类型和绑定键(Binding)联合使用才能最终生效,在绑定的条件下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪个消息队列。 Binding将交换器和队列关联起来,决定交换器的消息发送到哪个队列上 Queue,队列RabbitMQ的内部对象,用于存储消息生产者投递消息到队列,消费者从队列中获取消息并消费多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费RabbitMQ四种类型的交换器direct交换器,fanout交换器,topic交换器,headers交换器 direct交换器把消息路由到RoutingKey与BindingKey完全匹配的队列中,是完全匹配、单播的形式 eg:如果路由键为“java”,则只能转发RoutingKey为“java”的消息到绑定的队列中去,不会转发如“java.spring”fanout交换器会把发送到该交换器的消息路由到所有与该交换器绑定的队列中,不会匹配路由Key,转发消息是最快的 topic交换器通过模式匹配分配消息,将路由键和某个模式进行匹配,识别两个通配符:“#”和“”。#匹配0个或多个单词,匹配一个单词 eg:发送一条消息,RoutingKey为“java.spring”,这时候会匹配到RoutingKey为“java.#”和“#.spring”的队列headers交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers 属性进行匹配。该交换器类型性能较差且不实用,因此一般不会用到

September 8, 2019 · 1 min · jiezi

16springcloud整合Swagger2构建Restful服务的APIs

公众号: java乐园 Spring Cloud将服务注册到了Eureka上,可以从Eureka的UI界面中,看到有哪些服务已经注册到了Eureka Server上;但是如果想查看当前服务提供了哪些RESTful接口方法的话,就无法从Eureka Server获取了,而传统的方法是梳理一个接口文档来供开发人员之间来进行交流。这种情况下经常会造成文档和代码的不一致性,比如说代码改了,但是接口文档还没来得及修改等问题,而Swagger2则给我们提供了一套完美的解决方案,下面来看看Swagger2是如何来解决这个问题的。 1、新建项目sc-swagger2,对应的pom.xml文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>spring-cloud</groupId> <artifactId>sc-swagger2</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-swagger2</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>2、新建springboot启动类 package sc.swagger2;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class Swagger2Application { public static void main(String[] args) { SpringApplication.run(Swagger2Application.class, args); }}3、新建Swagger2配置类 ...

September 7, 2019 · 3 min · jiezi

15Feign整合断路器监控Hystrix-Dashboard

公众号: java乐园 Ribbon可以整合整合断路器监控Hystrix Dashboard,Feign也不能少, 本篇讲解一下Feign如何整合断路器监控Hystrix Dashboard。本篇主要整合sc-eureka-client-consumer-feign-hystrix项目和sc-hystrix-dashboard项目。 1、新建项目sc-feign-hystrix-dashboard,对应的pom.xml文件如下 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>spring-cloud</groupId> <artifactId>sc-feign-hystrix-dashboard</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>sc-feign-hystrix-dashboard</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!-- 说明是一个 eureka client --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.4.5.RELEASE</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies></project>说明:可以看出这个pom.xml文件是sc-eureka-client-consumer-feign-hystrix项目和sc-hystrix-dashboard项目的并集。 ...

September 7, 2019 · 1 min · jiezi

SpringBoot-2x-整合redis

看了好多篇文章,再根据自己的理解进行的配置。 一、Mevan相关配置1.我的SpringBoot版本 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --></parent>2.引入spring-redis的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>二、application.yml中加入redis依赖spring: datasource: url: jdbc:mysql://localhost:3306/spring_cache?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 redis: host: 39.107.250.174 port: 6379 database: 0 timeout: 1000s # 数据库连接超时时间,2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位 # 连接池配置,2.0中直接使用jedis或者lettuce配置连接池 jedis: pool: # 最大空闲连接数 max-idle: 500 # 最小空闲连接数 min-idle: 50 # 等待可用连接的最大时间,负数为不限制 max-wait: -1 # 最大活跃连接数,负数为不限制 max-active: -1 cache: redis: time-to-live: -1 #毫秒 #以下可忽略mybatis: configuration: #开启驼峰命名 map-underscore-to-camel-case: truelogging: level: com.scitc.cache.mapper : debug三、配置编写redis配置类package com.scitc.cache.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cache.CacheManager;import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@Slf4j@Configuration@EnableCaching//启用缓存,这个注解很重要;//继承CachingConfigurerSupport,为了自定义生成KEY的策略。可以不继承。public class RedisConfig extends CachingConfigurerSupport { @Value("${spring.cache.redis.time-to-live}") private Duration timeToLive = Duration.ZERO; @Bean(name = "redisTemplate") public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); redisSerializer.setObjectMapper(mapper); template.setValueSerializer(redisSerializer); //使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(timeToLive) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; }}然后我发现有好几篇文章配置 CacheManager 采用的是如下配置: ...

September 6, 2019 · 8 min · jiezi

玩转-SpringBoot-2-快速搭建-Spring-Tool-Suite篇

Spring Tool Suite (STS) 工具介绍我个人比较推荐使用 Spring Tool Suite(STS),之所以推荐使用 Spring Tool Suite(STS) ,是因为它是 Spring官方 基于 Eclipse 开发的一款 IDEA。其目的是更好使用 Spring 。 如果你习惯使用 Eclipse,不用担心操作问题。因为它操作方式和 Eclipse 是一模一样的,比 Eclipse 的好处是预定了很多关于创建 Spring 的插件。没有安装的同学可以通过访问 https://spring.io/guides/gs/sts/ 去下载安装 我本地环境如下:Maven版本:3.2.5 、JDK版本是1.8.0_144、SpringBoot版本 2.0.6.RELEASE需要说明的是SpringBoot 2.0的版本JDK必须是1.8 已上的版本。 闲话少说,开始我们的搭建之旅! 创建 SpringBoot 项目图文教程打开 Spring Tool Suite 点击 File - New-Spring Starter Project 去打开创建 SpringBoot 的操作界面。 在创建 SpringBoot 操作界面 填写 项目相关信息 。选择 JDK 版本 输入 GAV(Mavne) 信息、项目的描述等。  如果生成项目失败 建议将 https://start.spring.io 改为 http://start.spring.io 输入完成后点击下一步(Next)勾选 SpringBoot 的版本和需要添加的模块依赖,我们这里选择 web模块 然后点击 finish  目录介绍main - java :该目录是核心项目业务处理和配置类目录mian - resources - static:静态文件目录mian - resources - templates:模板文件目录application.properties : springBoot 的配置文件 (这里我们要说明一下 sprignBoot 没有 web.xml )test - java:该目录是编写测试类的目录 ...

August 28, 2019 · 1 min · jiezi

玩转-SpringBoot-2-快速搭建-IntellJ-IDEA篇

 IntellJ IDEA 介绍 IntelliJ IDEA 简称 IDEA,目前被认为是最好用的开发Java 语言开发工具之一,不过是收费的。和其同类型的工具有 Eclipse 和 MyEclipse,需要注意的是在 IntellJ IDEA中 project 相当于工作目录,模块相当于创建一个项目。 创建 SpringBoot 项目图文教程首先我们先创建一个空的项目,然后通过 File - New - Module 创建我们的项目 选择 Spring Initializr 然后在 Module SDK 右侧选择JDK 然后点击Next  这里我建议Initializr Service URL 选择Custom的方式 通过 http://start.spring.io 进行创建项目 设置 Mavne 项目的 Group、Artifact、Version 和其他项目信息,然后点击 Next。 选择 SpringBoot 项目的 Start依赖 ,这里将 Web 勾选上。然后点击Next。 输入我们项目保存的磁盘路径,然后点击Finish。 将下图中的红色框文件进行删除,这样可以走自己的在IntellJ IDEA的 Mavne 配置。目录介绍 main - java :该目录是项目业务处理类目录。mian - resources - static:静态文件目录。mian - resources - templates:模板文件目录。application.properties : springBoot 的配置文件 (这里我们要说明一下 sprignBoot 没有 web.xml )test - java:该目录是编写测试类的目录 ...

August 28, 2019 · 1 min · jiezi

玩转SpringBoot-2-快速搭建-Spring-Initializr-篇

SpringBoot 为我们提供了外网 Spring Initializr 网页版来帮助我们快速搭建 SpringBoot 项目,如果你不想用 IDEA 中的插件,这种方式也是不错的选择。闲话少说,直接开始我们的搭建操作! 记得第一次学习的时候访问 Spring Initializr地址 https://start.spring.io/ 是如下图所示的样子: 当时最新的版本是 2.1.0.RELEASE,仅仅几个月的时间 https://start.spring.io/ 就是如下图的模样了 个人觉得新的样式比以前更好看更简单啦,有木有? 首先选择用 Maven 方式创建, 语言选择 Java 然后输入我们的 Maven 坐标 Group、Artifact 。 选择 Maven 项目为jar包的方式,根据你本地的 Java 版本选择。我本地的 Java 版本是 JDK 1.8 选择完成后点击如下图中的 See all 选择 web 具体操作如下图所示: 点击如下图所示 Generate Project 按钮 就会生成一个空的 SpringBoot 项目的代码。 生成后如下图所示: 将红色框的文件夹和文件删除 在 /src/main/resources/ 目录下的application.properties配置文件中添加如下信息: server.port=8080server.servlet.context-path=/sbeserver.port 配置我们项目访问的端口号,server.servlet.context-path 配置访问的项目名称在 srcmainresourcesstatic 目录下添加 hello.html 内容如下: ...

August 28, 2019 · 1 min · jiezi

大龄开发人员如何破局

本人性格已经很外向了,也是一个相对乐观派,可是依然陷入深深的焦虑中。 为什么有这个想法说实话,在此次公司业务变动裁员(传送门)之前,从来没有想过情况会发展到这一步,一直以来都知道开发界有隐形的年龄歧视,从来没有想过这种问题会发生在自己身上,也许是工作的前10年一直都太顺利了,安逸的生活让自己产生了错觉。虽然在找工作的第一天就拿到了offer,可是与此而来的担忧反而更加严重了,躺在床上就在想,如果在35岁再经历一次这种情况,自身的竞争力在哪里?又凭什么认为自己不会遇到这种情况?一旦无法确定,那么就会深陷焦虑。当然,所有人都会说,解决这个问题要努力提升自己。我也知道。可是如何做?如何实现?如果再次碰到了,该怎么办? 上述的这个,是和朋友讨论这个问题的时候,他发给我的一份截图。他告诉我的是,大多数企业把年龄限制在了33岁,我已经31了,如何突破这个关卡? 如何突破低迷关于如何破局,最近有几个感悟,说出来和大家一起讨论一下,无关对错。 创造副业技术圈子的门槛说高也高,也低也低,其实个人感觉创造副业是相对容易的,比如承接私活,再比如线上授课/视频/文字教学,这些都是我们可以在业余时间做的。本人不玩游戏,不刷抖音,不看快手,只是偶尔看个电影,在尽量多的培养自己的业余爱好,以追求更广的人脉。可是这个毕竟时间较短,只有短短半年的时间,以前从来没有认真认真认真的思考过这个问题。 当然,上述所有的实现都是基于自身的高标准要求,严格的自律能力,自认为在最近1年已经做的很好了,可是现实还是给与了我迎头一棒。所以在考虑了N久之后,认为可提升的点还是有太多太多了。 最终还是决定从自己最熟悉的技术方面来入手,因为近1年接触了太多的技术大佬,让我深深的意识到了和他们的差距,以及对于危机意识的严重缺失,所以决定给自己下一个死命令,真的害怕在下一次大浪中,让自己和家人处于深深的忧虑以及不安和灾难之中。 承接私活 从各大私活网站,我好多年没在这接了,被小工作室压榨的太狠,行情就被自己毁了来自亲朋好友的介绍,这个是我最多的来源各种公众号,微商,快手微商(这个如果在微信朋友圈,容易被拉黑,慎用自己的主号。)线上授课 需要积累前期人脉利用大的平台并提供相对优质的课程内容经营副业店铺 这个是实体店了,我记得在13年的时候,网上当时流传一个很火的新闻 新浪IT男辞职卖水果大叔变帅小伙其实路有千万种,适合自己的才是最好的,真的无需将自己框定在自己的枷锁中。 自从来了北京之后,阅读和学习我就从来没有放下过,即便自己特别不愿意或者多么想偷懒,都在逼着自己学习,人生如逆水行舟,不进则退。 提升进大厂但是这个真的需要下很大功夫,尤其是特别心仪的部门,需要特别极致的要求,那么你要怎么努力做到?来自2014年IDC数据,开发人员大概是200W左右,因为各种培训+这几年最新毕业的从业人员,算上300W开发人员, 那么要从300W中脱颖而出0.00x% ,自己需要下多大功夫?说实话,想要进自己心仪的部门真的真的要下死苦。 人总会有一个蛰伏期的阶段,这个阶段是打定坚实基础的一个过程,没有捷径。 春秋战国,在即位之初,权臣专断。楚庄王在位日浅,力量孤单,弄不好就有被废掉的危险。于是,他只好收敛锋芒。装着弱不禁风,沉湎于酒色,三年不理国政。伍举实在看不下去了,用寓言讽刺楚王说:“有鸟停于高阜,三年不鸣,这种鸟是什么鸟?!”楚庄王回答说:“此鸟三年不飞,一飞冲天;三年不鸣,一鸣惊人!”听到这话后,伍举才知道楚庄王不过蛰伏罢了。 任何事物,在成长的时期都需要蛰伏一段时间。蛰伏一段时间之后,一旦到了惊蛰阶段,也就到了可以大干一场了。这一阶段的长短早晚,完全取决于个人。 “伏久者,飞必高”。 提升沟通其实这个是最简单的,也是最难的。 我见过太多的人技术能力很不错,但是不太会说话,或者不太敢说话,造成自己的困局:干的多拿的少?替别人背锅?我在给团队培训的时候一直share给大家的是,我们不甩锅,我们也不背锅。可是很多时候,我们有理的事,就是因为不会说话,最后反而变成了没理的。网上还有一个段子:我们总是在吵架完了之后就后悔自己没有发挥好,如果再重新吵一次会怎么怎么样。 但是在职场中,吵架真的是最LOW的方式了,因为吵架代表着我们无法解决问题,只剩下了歇斯底里。当然,每一个人都有自己的底线,不是尽量,而是要求自己坚决不要去触碰别人的底线(测试或者产品经理特别是你的直属上级),这个是红线。 我说说我的沟通方式(一定是解决问题为出发点,我在职场很少针对人如何,但是有例外): 占理 和对方一起分析问题,找到根本原因认知有问题,一定告知为什么不得理不饶人工作这么些年,特别是最近几年,我从来没有让领导做过问答题,从来都是选择题。因为从心底里认知,就是让你来是解决问题的,不是提/制造问题的。 不占理 首先认错,然后虚心求教同样的问题不会让它出现第二次该认怂认怂,这个不丢人,强词夺理才丢人。千万不要和人胡说八道,做不到的一定不要承诺,承诺的一定不要放鸽子。 我来衣二三之后,第一件事给团队培训的就是沟通,给大家讲2个小笑话: 在之前一家单位有一个同事,本人到现在都认为很奇葩,怎么一个人呢,他是从别的team transfer到我们团队的,过来之后,他领到的所有任务,都来找别人帮他,自己从来不学习,整个团队的人都被搞烦了,然后因为问过我几次,我之前确实没想到他不学,然后前面都帮了,最后一次是什么事呢?他接到一个需求找我,我帮他把业务理顺之后,又来找我,然后帮着找到了相关代码,只需要copy改改就行了,然后这哥们坐在我旁边1个多小时一行代码没写,说你帮我写一下,让我看看,我回了一个我还在忙,我的工作还没完,然后这哥们直啪的一摔纸和本,说cao,这啥破需求。我擦,然后我说滚。就再没理,几天之后就消失了。这个是上家单位,也是一个同事,从进公司之后,她的产品需求文档,学习能力,沟通能力,我很喜欢,然后即便我的团队任务再重,她随时穿插的任务,我能帮着搞定绝对不会托辞。想要告诉大家什么呢,团队中别人愿不愿意帮你,其实都是个人的沟通,说白了就是你的为人处世造成的。意识到了并且强加约束,很好,你提升会特别明显特别快。意识不到或者不愿意改。。。呵呵。 找到自己的路如果做不到怎么办? 我其实在团队和生活中都是特别反感这样的人,还没有开始做,就说自己做不到! 为什么要留给自己做不到的这个后路,如果做不到,就意味着整个家庭会陷入一个巨大的痛苦之中,你还会给自己找完不成的借口吗? 其实大多数的事情,当你开始之后,就会发现没有想法中的那么困难,大部分困难都是自己给自己造成的心理暗示和负担! 在我写完这篇文章之后,目标明确了,心情舒畅了,外面的天更蓝了。 Ps. 无论这篇文章因为何种原因让您不舒服不高兴了,很抱歉给您带来的困扰,请直接关闭即可,并请相信我不是有意的。 奔跑的人生 | 博客园 | segmentfault | spring4all | csdn | 掘金 | OSChina | 简书 | 头条 | 知乎 | 51CTO

August 28, 2019 · 1 min · jiezi

spring基本组件及注解

spring基本组件如上图: spring配置文件最初使用xml配置文件,然后转向注解。 //配置类====配置文件@Configurationpublic class MainConfig { //给容器中注册一个bean, 类型为返回值的类型, //注意以@Bean注册bean,Id默认为方法名,也可通过@Bean注解的值来修改 @Bean public Person person01(){ return new Person("username",20); }} public class MainTest2 { public static void main(String args[]){ //加载注解的配置文件 ApplicationContext app = new AnnotationConfigApplicationContext(MainConfig.class); //从容器中获取bean //Person person = (Person) app.getBean("person01"); //System.out.println(person); String[] namesForBean = app.getBeanNamesForType(Person.class); for(String name:namesForBean){ System.out.println(name); } }}@ComponentScan扫描规则@Configuration@ComponentScan(value="com.enjoy.cap2", includeFilters={ @Filter(type=FilterType.ANNOTATION, classes={Controller.class}) }, useDefaultFilters=false) public class Cap2MainConfig { //给容器中注册一个bean, 类型为返回值的类型, @Bean public Person person01(){ return new Person("james",20); }}value的值说明是扫描此包下面的所有类includeFilters表示使用自定义的过滤器,此时useDefaultFilters应该为false屏蔽默认过滤器,自定义的才生效(demo中表示只扫描Controller类型的类)另外的excludeFilters就是在默认过滤器下再过滤指定的类,自定义过滤器即可,implements TypeFilter即可自定义@ComponentScan value:指定要扫描的包excludeFilters = Filter[] 指定扫描的时候按照什么规则排除那些组件includeFilters = Filter[] 指定扫描的时候只需要包含哪些组件useDefaultFilters = false 默认是true,扫描所有组件,要改成false----扫描规则如下FilterType.ANNOTATION:按照注解FilterType.ASSIGNABLE_TYPE:按照给定的类型;比如按BookService类型FilterType.ASPECTJ:使用ASPECTJ表达式FilterType.REGEX:使用正则指定FilterType.CUSTOM:使用自定义规则,自已写类,实现TypeFilter接口 ...

August 28, 2019 · 2 min · jiezi

Springboot源码分析之Transactional

摘要:对SpringBoot有多了解,其实就是看你对Spring Framework有多熟悉~ 比如SpringBoot大量的模块装配的设计模式,其实它属于Spring Framework提供的能力。SpringBoot大行其道的今天,基于XML配置的Spring Framework的使用方式注定已成为过去式。注解驱动应用,面向元数据编程已然成受到越来越多开发者的偏好了,毕竟它的便捷程度、优势都是XML方式不可比拟的。 @Configuration @ConditionalOnClass({PlatformTransactionManager.class}) @AutoConfigureAfter({JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class}) @EnableConfigurationProperties({TransactionProperties.class}) public class TransactionAutoConfiguration { public TransactionAutoConfiguration() { } @Bean @ConditionalOnMissingBean public TransactionManagerCustomizers platformTransactionManagerCustomizers(ObjectProvider<PlatformTransactionManagerCustomizer<?>> customizers) { return new TransactionManagerCustomizers((Collection)customizers.orderedStream().collect(Collectors.toList())); } @Configuration @ConditionalOnBean({PlatformTransactionManager.class}) @ConditionalOnMissingBean({AbstractTransactionManagementConfiguration.class}) public static class EnableTransactionManagementConfiguration { public EnableTransactionManagementConfiguration() { } @Configuration @EnableTransactionManagement( proxyTargetClass = true ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true ) //默认采用cglib代理 public static class CglibAutoProxyConfiguration { public CglibAutoProxyConfiguration() { } } @Configuration @EnableTransactionManagement( proxyTargetClass = false ) @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "false", matchIfMissing = false ) public static class JdkDynamicAutoProxyConfiguration { public JdkDynamicAutoProxyConfiguration() { } } } @Configuration //当PlatformTransactionManager类型的bean存在并且当存在多个bean时指定为Primary的 PlatformTransactionManager存在时,该配置类才进行解析 @ConditionalOnSingleCandidate(PlatformTransactionManager.class) public static class TransactionTemplateConfiguration { private final PlatformTransactionManager transactionManager; public TransactionTemplateConfiguration(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } // 由于TransactionAutoConfiguration是在DataSourceTransactionManagerAutoConfiguration之后才被解析处理的,而在DataSourceTransactionManagerAutoConfiguration中配置了transactionManager,因此, TransactionTemplateConfiguration 会被处理. @Bean @ConditionalOnMissingBean public TransactionTemplate transactionTemplate() { return new TransactionTemplate(this.transactionManager); } } }PlatformTransactionManager后续章节会分析提示:使用@EnableTransactionManagement注解前,请务必保证你已经配置了至少一个PlatformTransactionManager的Bean,否则会报错。(当然你也可以实现TransactionManagementConfigurer来提供一个专属的,只是我们一般都不这么去做~~~) ...

August 27, 2019 · 4 min · jiezi

从零开始实现mvc框架

造个轮子— 从socket到mvc框架1. 缘起为什么要造这个轮子?现在Java领域的mvc框架层出不穷,springmvc,struts2,jfinal;容器方面有tomcat,jetty,undertow。为什么要造这个不成熟的东西出来?我想起我在大二刚接触java web是学的struts2,一大堆xml配置让我看到吐,感觉这掩盖了网络编程的本来面目,为什么不从底层的socket写起,解析http协议,封装请求和响应,我觉得这样更能理解本质。于是我造了第一个轮子作为这个想法的验证MineServer, 这个轮子现在放在github上,但这只是一个玩具项目,我想造的是一个完整的轮子,于是我开了这个坑Boomvc。 2. 我想要什么?我想要一个类似spring boot这种开发体验的web框架,可以用一行代码开启一个http server,没有xml配置,使用注解和简单的properties配置文件,最简的依赖,可以不用遵守servlet规范的web mvc框架。框架要包括3个部分:底层的http server和上层的mvc框架,以及ioc容器。http server可以自己从socket实现,也可以使用netty这样的网络框架实现。 3. 实现目前框架已经基本实现完成,还有很多bug,但是完成了自己的想法,我感到很开心。目前框架实现了一下的功能。 轻量级MVC框架,不依赖任何web容器可以直接运行jar包启动一个web服务支持cookie支持session使用jdk原生的nio api实现http serverRestful风格路由支持模板引擎支持JSON输出可以像spring boot 那样一行代码启动 :) public static void main(String[] args) { Boom.me().start(Main.class, args);}写一个controller @RestPathpublic class TestController { @GetRoute("/") public String hello(){ return "hello world"; }}同时还有过滤器和拦截器 public interface Filter { void init(FilterConfig config); void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws Exception; void destroy();}public interface Interceptor { boolean preHandle(HttpRequest request, HttpResponse response); void postHandle(HttpRequest request, HttpResponse response, ModelAndView modelAndView); void afterCompletion(HttpRequest request, HttpResponse response, Exception e);}然后像spring boot那样注册,写一个配置类继承WebMvcConfigurerAdapter ...

August 21, 2019 · 1 min · jiezi

全网首发-Spring-Cloud-Gateway-添加统一前缀思路探讨

1.前言今天学习一下Spring Cloud Gateway,就先再其他博客上逛了逛。遇到有java开发者在某博客问一个问题:Spring Cloud Gateway 如何添加统一的前缀? 当时没有在意,但是脑子里也带着这个问题看起了文档。随着慢慢了解Spring Cloud Gateway 这个问题就有了一点思路。 2. Gateway工作机制 这是官方文档上给的Spring Cloud Gateway工作流程图。大意上是客户端请求经过HandlerMapping的处理,如果匹配到路由(Router)就交给网关的web处理程序(Gateway Web Handler)来处理,经过一系列的调用过滤器链(肯定有责任链模式)后转发到被代理的服务执行真正的调用逻辑。 3. Gateway Handler Mapping根据上图,我想找到所谓的Gateway Handler Mapping,看看是何方神圣。我找到了RoutePredicateHandlerMapping,并确定该类就是那个handler Mapping。依赖于Spring Webflux响应式web编程模型。核心方法是getHandlerInternal,通过该方法进行内部处理。 从代码上看 就是处理了端口关系后,在request放入一些gateway特定的attribute。然后走的一个寻找路由的方法。最后通过Mono.just(webHandler)交给了一个FilteringWebHandler类型的webHandler来处理了。我们先来关注lookupRoute方法,该方法摆明了就是寻找路由。该方法的代码不贴了。就是通过routeLocator.getRoutes()来加载所有的路由并通过断言(Predicate)来进行匹配。匹配到就交给FilteringWebHandler 走过滤器链。在我明白这个流程之后,脑子里那个问题就有了眉目。 4. FilteringWebHandler该handler就是图中的Gateway Web Handler ,包含了一系列的GlobalFilter和GatewayFilter 来组成一个chain来处理前置和后置的逻辑。 5. 增加统一前缀的思路在以前我们知道zuul网关是可以添加一个统一前缀的。但是Spring Cloud Gateway是没有直接提供这个功能的。我已经搞清楚了Gateway的大致工作机制,甚至是一些细节。首先这个统一前缀肯定不能在断言中处理。断言是根据请求的个性化来找目的地路由的,而统一前缀是共性的。放在断言执行后也就是FilteringWebHandler来处理就更不合适了,因为断言执行在Filter之前。但是有一个东西我们没有注意到。Spring Cloud Gateway的机制依赖于Spring Webflux框架的。经过查一些资料RoutePredicateHandlerMapping处理之前是可以设置WebFilter的。因此我找到了一个解决方案:在Gateway Client 和 Gateway Handler Mapping 之间定制一个WebFilter来处理统一前缀。 6. 思路实现机制是这样的,请求带统一前缀请求经过我定制的WebFilter去掉前缀(当然你可以加一些其他你需要的逻辑)然后交给Gateway Handler Mapping处理。代码如下 经过测试有效。 7. 总结这里其实重点不是如何来实现这个功能,我想传达的是一个解决问题的思路。如何从框架的工作机制出发来分析你所需要解决的问题。找到那个合适的切入点。清晰的思路对于解决问题来说至关重要。其实我现在对Spring Cloud Gateway和Spring Webflux 都并不是很熟练,但是依然找了一个解决问题的方法。不知道你有没有更好的方法呢?相关代码已经上传到码云仓库:https://gitee.com/felord/tino... 关注公众号:码农小胖哥 获取更多资讯

August 21, 2019 · 1 min · jiezi

Springboot源码分析之项目结构

摘要:无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.github.dqqzj</groupId> <artifactId>springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot</name> <packaging>jar</packaging> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>parent标签的含义找到本地仓库的spring-boot-starter-parent 坐标 <?xml version="1.0" encoding="utf-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.6.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent> <!-- 省略无关代码...... --> <build> <!-- 省略无关代码...... --> <pluginManagement> <plugins> <!-- 省略无关代码...... --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> <configuration> <mainClass>${start-class}</mainClass> </configuration> </plugin> <!-- 省略无关代码...... --> </plugins> </pluginManagement> </build> </project>关注点 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.6.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>说明我们的工程可以进行改造进行替换掉原来工程的parent标签. ...

August 21, 2019 · 1 min · jiezi

Srping源码之BeanFactorygetBean

本文是针对Srping的BeanFactory.getBean来进行源码解析,如果您是第一次看请先看一下XMLBeanFactory解析:https://blog.csdn.net/qq_3025... 可以更好的理解Spring的注册原理,本篇博客是跟源码一步步看spring怎么实现getBean源码,Spring版本为5.X,源码已经在每一行上加了注释,方便读者学习。** GItHub:https://github.com/lantaoGitH...废话不多说,我们直接看源码:package org.springframework.lantao;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;public class XmlBeanFactoryTest { public static void main(String[] args) { ClassPathResource classPathResource = new ClassPathResource("spring-bean.xml"); BeanFactory beanFactory = new XmlBeanFactory(classPathResource); UserBean userBean = (UserBean) beanFactory.getBean("userBean"); System.out.println(userBean.getName()); }}XMLBeanFactory解析就不多说了,如果没看过的可以去看我上一篇文章,这里直接看BeanFactory.getBean()/** * Return an instance, which may be shared or independent, of the specified bean. * @param name the name of the bean to retrieve * @param requiredType the required type of the bean to retrieve * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @param typeCheckOnly whether the instance is obtained for a type check, * not for actual use * @return an instance of the bean * @throws BeansException if the bean could not be created */@SuppressWarnings("unchecked")protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { //获取实例名字 final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. // 检查单例缓存中是否存在实例 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. // 原型 循环引用 抛异常 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); }else if (args != null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); }else if (requiredType != null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); }else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (!typeCheckOnly) { // 将指定的bean标记为已经创建(或即将创建)。这允许bean工厂优化其缓存,以便重复创建指定的bean markBeanAsCreated(beanName); } try { final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } // Create bean instance. // 创建bean实例 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); }else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); }finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean;}doGetBean的源码比较长,那它都做了哪些事情呢:1:转换对应的beanname,可能有很多人不理解,传入进来的不就应该是beanName嘛,其实传入的可能是BeanFactory,也可能是别名,如果是BeanFactory,就要去除它的修饰符,比如传入进来的&aa,就要转换成aa,但如果传入进来的是别名,就要取alias对应的最终的beanName,例如别名A指向了B的bean则返回B的beanName,如果别名A指向了别名B,别名B指向了C,则要返回C的BeanName; ...

August 21, 2019 · 29 min · jiezi

Springboot源码分析之jar探秘

摘要:利用IDEA等工具打包会出现springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面说过它们之间的关系了,接下来我们就一探究竟,它们之间到底有什么联系。文件对比: 进入target目录,unzip springboot-0.0.1-SNAPSHOT.jar -d jar命令将springboot-0.0.1-SNAPSHOT.jar解压到jar目录进入target目录,unzip springboot-0.0.1-SNAPSHOT.jar.original -d original命令将springboot-0.0.1-SNAPSHOT.jar.original解压到original目录前面文章分析过springboot-0.0.1-SNAPSHOT.jar.original不能执行,将它进行repackage后生成springboot-0.0.1-SNAPSHOT.jar就成了我们的可执行fat jar,对比上面文件会发现可执行 fat jar和original jar目录不一样,最关键的地方是多了org.springframework.boot.loader这个包,这个就是我们平时java -jar springboot-0.0.1-SNAPSHOT.jar命令启动的奥妙所在。MANIFEST.MF文件里面的内容包含了很多关键的信息 Manifest-Version: 1.0Start-Class: com.github.dqqzj.springboot.SpringbootApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Build-Jdk-Spec: 1.8Spring-Boot-Version: 2.1.6.RELEASECreated-By: Maven Archiver 3.4.0Main-Class: org.springframework.boot.loader.JarLauncher相信不用多说大家都能明白Main-Class: org.springframework.boot.loader.JarLauncher是我们 java -jar命令启动的入口,后续会进行分析,Start-Class: com.github.dqqzj.springboot.SpringbootApplication才是我们程序的入口主函数。 Springboot jar启动源码分析public class JarLauncher extends ExecutableArchiveLauncher { static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; static final String BOOT_INF_LIB = "BOOT-INF/lib/"; public JarLauncher() { } protected JarLauncher(Archive archive) { super(archive); } /** * 判断是否归档文件还是文件系统的目录 可以猜想基于文件系统一样是可以启动的 */ protected boolean isNestedArchive(Entry entry) { return entry.isDirectory() ? entry.getName().equals("BOOT-INF/classes/") : entry.getName().startsWith("BOOT-INF/lib/"); } public static void main(String[] args) throws Exception { /** * 进入父类初始化构造器ExecutableArchiveLauncher * launch方法交给Launcher执行 */ (new JarLauncher()).launch(args); }}public abstract class ExecutableArchiveLauncher extends Launcher { private final Archive archive; public ExecutableArchiveLauncher() { try { /** * 使用父类Launcher加载资源,包括BOOT-INF的classes和lib下面的所有归档文件 */ this.archive = this.createArchive(); } catch (Exception var2) { throw new IllegalStateException(var2); } } protected ExecutableArchiveLauncher(Archive archive) { this.archive = archive; } protected final Archive getArchive() { return this.archive; } /** * 从归档文件中获取我们的应用程序主函数 */ protected String getMainClass() throws Exception { Manifest manifest = this.archive.getManifest(); String mainClass = null; if (manifest != null) { mainClass = manifest.getMainAttributes().getValue("Start-Class"); } if (mainClass == null) { throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); } else { return mainClass; } } protected List<Archive> getClassPathArchives() throws Exception { List<Archive> archives = new ArrayList(this.archive.getNestedArchives(this::isNestedArchive)); this.postProcessClassPathArchives(archives); return archives; } protected abstract boolean isNestedArchive(Entry entry); protected void postProcessClassPathArchives(List<Archive> archives) throws Exception { }}public abstract class Launcher { public Launcher() { } protected void launch(String[] args) throws Exception { /** *注册协议处理器,由于Springboot是 jar in jar 所以要重写jar协议才能读取归档文件 */ JarFile.registerUrlProtocolHandler(); ClassLoader classLoader = this.createClassLoader(this.getClassPathArchives()); /** * this.getMainClass()交给子类ExecutableArchiveLauncher实现 */ this.launch(args, this.getMainClass(), classLoader); } protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { List<URL> urls = new ArrayList(archives.size()); Iterator var3 = archives.iterator(); while(var3.hasNext()) { Archive archive = (Archive)var3.next(); urls.add(archive.getUrl()); } return this.createClassLoader((URL[])urls.toArray(new URL[0])); } /** * 该类加载器是fat jar的关键的一处,因为传统的类加载器无法读取jar in jar模型,所以springboot进行了自己实现 */ protected ClassLoader createClassLoader(URL[] urls) throws Exception { return new LaunchedURLClassLoader(urls, this.getClass().getClassLoader()); } protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { Thread.currentThread().setContextClassLoader(classLoader); this.createMainMethodRunner(mainClass, args, classLoader).run(); } /** * 创建应用程序主函数运行器 */ protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { return new MainMethodRunner(mainClass, args); } protected abstract String getMainClass() throws Exception; protected abstract List<Archive> getClassPathArchives() throws Exception; /** * 得到我们的启动jar的归档文件 */ protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = this.getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = codeSource != null ? codeSource.getLocation().toURI() : null; String path = location != null ? location.getSchemeSpecificPart() : null; if (path == null) { throw new IllegalStateException("Unable to determine code source archive"); } else { File root = new File(path); if (!root.exists()) { throw new IllegalStateException("Unable to determine code source archive from " + root); } else { return (Archive)(root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } } }}public class MainMethodRunner { private final String mainClassName; private final String[] args; public MainMethodRunner(String mainClass, String[] args) { this.mainClassName = mainClass; this.args = args != null ? (String[])args.clone() : null; } /** * 最终执行的方法,可以发现是利用的反射调用的我们应用程序的主函数 */ public void run() throws Exception { Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName); Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); mainMethod.invoke((Object)null, this.args); }}小结: ...

August 21, 2019 · 3 min · jiezi

Spring-cloud-一步步实现广告系统-22-广告系统回顾总结

到目前为止,我们整个初级广告检索系统就初步开发完成了,我们来整体回顾一下我们的广告系统。整个广告系统编码结构如下: mscx-ad 父模块 主要是为了方便我们项目的统一管理mscx-ad-db 这个模块主要有2个作用,本身只应该作为数据库脚本管理package来使用,但是我们在生成索引文件的过程中,为了方便,我就直接将导出全量索引的json文件生成也写在了该项目中。 主要目的还是通过flyway进行数据库脚本的管理。mscx-ad-common 这个主要是一些通用工具类的存放mscx-ad-feign-sdk 这个jar包主要是为了服务间的调用,为了统一管理各种pojo 以及CustomFeignClient而创建的,方便一次修改,全局应用,。当然如果项目团队不大的时候,你完全可以在不同的project中创建相同的vo对象,目前RPC中大多如此设计。mscx-ad-dashboard 这个是hystrix提供的可视化管理工具,当然,后期我同样会使用我们的阿里大大的sentinel将其替换掉,敬请期待。mscx-ad-discovery 这个我命名的时候没有使用ad-eureka,在项目中也是尽量使用的SpringCloud Common抽象的公共注解,比如@EnableDiscoveryClient,其实有心的同学能看的出来,我打的主意也是想要后续替换的,我们可以使用ZK,但是我后期同样会使用我们阿里大大的NACOS 来替换掉它。mscx-ad-zuul 网关路由组件,没啥特别的,后续使用gateway替换mscx-ad-sponsor 广告新增的主要模块,为广告主服务mscx-ad-search 整个广告系统的核心,对外暴露查询服务。为了我们系统的高可用,上述系统理论上都需要多实例部署。 我们在广告检索服务中使用到了监听 Mysql数据库的 Binlog来实现增量索引,大家不妨想想,如果我们的系统请求很高,我们的binlog就需要被N多的服务实例所监听,这样会有什么问题? 为什么会有这种问题? 怎么修改是合理的? 番外从2018年10月31号,我们阿里大大开源发布了Spring Cloud Alibaba,经过1年的项目孵化,终于在2019年8月1号毕业了小马哥威武, SC-Alibaba Team 威武。为了迎接这一伟大的国内Spring盛世,接下来我会写一个学习SCA的课程,途中遇到的所有问题都会和大家一起共享,加油。 奔跑的人生 | 博客园 | segmentfault | spring4all | csdn | 掘金 | OSChina | 简书 | 头条 | 知乎 | 51CTO

August 21, 2019 · 1 min · jiezi

Spring-cloud-一步步实现广告系统-21-系统错误汇总

广告系统学习过程中问题答疑博客园Eureka集群启动报错Answer 因为Eureka在集群启动过程中,会连接集群中其他的机器进行数据同步,在这个过程中,如果别的服务还没有启动完成,就会出现Connection refused: connecterror,当其他节点启动完成之后,报错就会消失。 AdSearch 服务启动报错2019-08-16 10:27:57.038 ERROR 73180 --- [ main] o.s.boot.SpringApplication : Application run failedjava.lang.IllegalStateException: Failed to introspect Class [org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2] at org.springframework.util.ReflectionUtils.getDeclaredFields(ReflectionUtils.java:760) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:725) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:710) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.boot.test.mock.mockito.DefinitionsParser.parse(DefinitionsParser.java:62) ~[spring-boot-test-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.postProcessBeanFactory(MockitoPostProcessor.java:141) ~[spring-boot-test-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.postProcessBeanFactory(MockitoPostProcessor.java:133) ~[spring-boot-test-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127) [spring-boot-test-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44) [spring-boot-test-autoconfigure-2.1.5.RELEASE.jar:2.1.5.RELEASE] at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.1.7.RELEASE.jar:5.1.7.RELEASE] at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12] at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) [junit-rt.jar:na] at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) [junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) [junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) [junit-rt.jar:na]Caused by: java.lang.NoClassDefFoundError: Lorg/springframework/kafka/listener/AfterRollbackProcessor; at java.lang.Class.getDeclaredFields0(Native Method) ~[na:1.8.0_171] at java.lang.Class.privateGetDeclaredFields(Class.java:2583) ~[na:1.8.0_171] at java.lang.Class.getDeclaredFields(Class.java:1916) ~[na:1.8.0_171] at org.springframework.util.ReflectionUtils.getDeclaredFields(ReflectionUtils.java:755) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE] ... 40 common frames omittedCaused by: java.lang.ClassNotFoundException: org.springframework.kafka.listener.AfterRollbackProcessor at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_171] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_171] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_171] at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_171] ... 44 common frames omitted2019-08-16 10:27:57.119 INFO 73180 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$2c3f141a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)查看这个错误,是在加载org.springframework.kafka.listener.AfterRollbackProcessor的时候,没有找到对应的class,可是我们明明添加了依赖呀? ...

August 19, 2019 · 2 min · jiezi

BlockLang-040-发布啦

BlockLang 源码: https://github.com/blocklanghttps://gitee.com/blocklangBlockLang 演示网址: https://blocklang.com犹如 Java 语言的 Maven,JavaScript 语言的 npmJs,Rust 语言的 Cargo 等依赖管理工具,Block Lang 0.4.0 版本也引入依赖管理功能。 Block Lang 项目依赖的对象是组件市场中注册的组件仓库。在组件市场时,我们称之为“组件仓库”;跟项目关联后,我们称之为“依赖”。 0.4.0 版本引入的依赖管理包括以下四个功能点: 创建依赖配置文件;添加一个依赖;删除一个依赖;更新依赖版本。依赖配置文件Block Lang 项目的依赖统一配置在位于项目根目录下的 DEPENDENCE.json 文件中。 依赖分为三大类: API - 对应 API 仓库开发 - 组件仓库,在开发阶段使用,是 Block Lang 设计器的扩展构建 - 组件仓库,在构建阶段使用,支持为不同的应用程序配置不同的依赖开发 和 构建 下配置的都是组件仓库,而 API 下是这些组件仓库对应的 API 仓库,因此无需直接配置。 开发 和 构建 下的依赖又按应用程序类型分组,如可以为 web 应用和微信小程序分别配置依赖。 用 json 描述的依赖结构大致如下: { "dev": { "web":{ "github/@owner1/repo1": {"git": "https://github.com/owner1/repo1.git", "tag": "v0.1.0"}, "gitee/@owner2/repo2": {"git": "https://gitee.com/owner2/repo2.git", "tag": "v0.1.0"} } }, "build": { "web":{ "default": { "github/@owner3/repo3": {"git": "", "tag": "v0.1.0"} } }, "wechatMiniApp": { "default": { "github/@owner4/repo4": {"git": "", "tag": "v0.1.0"} } } }}注意:build 分组下多了一层,名为 default,这是为后续版本预留的 Profile 功能。 ...

August 19, 2019 · 1 min · jiezi

Spring-Boot-Cloud-CLI-快速上手

导读在日常开发与测试中有一些Spring Cloud 的相关的组件如 eureka、configserver、zipkin、hystrixdashboard等相对来说不容易发生变动,这里就介绍一种Spring 官方为我们提供的开箱即用的 Spring Boot Cloud CLI 只需要一条命令就可以启动这些相关的组件服务。 Spring Boot Cloud CLI 是什么?Spring Boot Cloud CLI 官方是这样描述的: Spring Boot CLI provides Spring Boot command line features for Spring Cloud. You can write Groovy scripts to run Spring Cloud component applications (e.g. @EnableEurekaServer). You can also easily do things like encryption and decryption to support Spring Cloud Config clients with secret configuration values. With the Launcher CLI you can launch services like Eureka, Zipkin, Config Server conveniently all at once from the command line (very useful at development time).翻译之后: ...

August 17, 2019 · 2 min · jiezi

Swoft-205-更新新增高效秒级定时任务异常管理组件

什么是 Swoft ?Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。 Swoft 通过长达三年的积累和方向的探索,把 Swoft 打造成 PHP 界的 Spring Cloud, 它是 PHP 高性能框架和微服务治理的最佳选择。 高效秒级定时任务如下简单几行代码,就定义了一个每秒执行的定时任务,完全可以取代系统定时任务。 <?php declare(strict_types=1);namespace App\Crontab;use Swoft\Crontab\Annotaion\Mapping\Cron;use Swoft\Crontab\Annotaion\Mapping\Scheduled;/** * Class CronTask * * @since 2.0 * * @Scheduled() */class CronTask{ /** * @Cron("* * * * * *") */ public function secondTask() { printf("second task run: %s ", date('Y-m-d H:i:s', time())); }}定时任务跟随服务一起启动,将看到如下显示: ...

August 8, 2019 · 1 min · jiezi

iSpring-Suite教程如何在几次点击中将PDF文件转换为SCORM包

PDF指导手册、指南和操作方法文章是一种流行的知识传递方式。但是如何确保您的员工真正打开文件并仔细研究它们?答案很简单:将PDF文档转换为SCORM包,将其上传到LMS,并跟踪学习者正在阅读的内容以及他们的进展情况。 您可以使用特殊工具将任何PDF转换为SCORM。例如,您可以使用iSpring Suite创作工具包。iSpring Suite用于PowerPoint的完整的电子学习创作工具包。 点击下载iSpring Suite最新版 现在,让我们深入研究如何逐步将PDF文档导出到SCORM包。 1、打开iSpring Suite并选择Books选项卡。选择“浏览”按钮或在“创建自”部分中选择“PDF”以打开文档。 2、单击预览,然后选择设备和屏幕方向,来查看它在桌面、智能手机和平板电脑上的外观。 3、单击“发布”按钮,然后选择“LMS”选项卡。现在,您拥有LMS的所有发布选项。 首先命名您的文件并选择保存的位置。然后您可以自定义质量选项。可以选择从高质量到低质量的预设,或通过更改图像压缩或调整图像质量滑块来获得所需的质量。 您可能需要添加密码来保护您的文件,并使其仅对拥有密码的人可见。 4、选择系统支持的配置文件。iSpring Suite不仅可以发布SCORM,还可以发布SCORM 1.2和2004、AICC、Experience API和cmi5等各种电子教学标准。每个标准都有自己的一组自定义。 假设您要将文件发布到SCORM 1.2。您可以更改课程名称、撰写课程说明或添加关键字。 5、保存更改后,再次单击“发布”。您将获得一个ZIP文件,可以上传到任何支持SCORM的LMS上。 将SCORM课程上传到LMS后,您可以获得有关学员结果和进度的报告。 iSpring Learn LMS中的内容活动报告 iSpring Suite不仅仅是一个PDF-to-SCORM转换器。它是一个强大的创作工具,允许您使用测验,交互和对话模拟创建引人注目的课程,并以您喜欢的任何格式发布它们,包括SCORM。

July 26, 2019 · 1 min · jiezi

详述Spring对数据校验支持的核心APISmartValidator

每篇一句要致富,先修路。要使用,先...基础是需要垒砌的,做技术切勿空中楼阁相关阅读【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【小家Java】深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator) <center>对Spring感兴趣可扫码加入wx群:Java高工、架构师3群(文末有二维码)</center> 前言浩浩荡荡的把一般程序员都不太关注的Bean Validation话题讲了这么久,期间小伙伴wx我说一直还没看到他最想看到的内容,我问最想看到啥?他说显然是数据校验在Spring中的使用啊。我想若不出意外,这应该是众多小伙伴的共同心声吧,但路漫漫其修远兮,也得上下求索,本文将切入到最关心的Spring中来~ 要想深入了解Spring对Bean Validation的支持,org.springframework.validation.beanvalidation这个包里面的这几个关键API必须搞明白喽,这样再使用起@Valid结合Spring时时才能更加的收放自如~ 说明:这个包所在的jar是spring-context,属于Spring上下文的核心功能模块我把这个包内的类图截图如下,供以参考: Spring虽然没有直接实现Bean校验这块的JSR规范,但是从Spring3.0开始,Spring就提供了对Bean Validation的支持。 3.0提供了Bean级别的校验3.1提供了更加强大的方法级别的校验BeanValidationPostProcessor它就是个普通的BeanPostProcessor。它能够去校验Spring容器中的Bean,从而决定允不允许它初始化完成。 比如我们有些Bean某些字段是不允许为空的,比如数据的链接,用户名密码等等,这个时候用上它处理就非常的优雅和高级了~若校验不通过,在违反约束的情况下就会抛出异常,阻止容器的正常启动~ public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean { // 这就是我们熟悉的校验器 // 请注意这里是javax.validation.Validator,而不是org.springframework.validation.Validator @Nullable private Validator validator; // true:表示在Bean初始化之后完成校验 // false:表示在Bean初始化之前就校验 private boolean afterInitialization = false; ... // 省略get/set // 由此可见使用的是默认的校验器(当然还是Hibernate的) @Override public void afterPropertiesSet() { if (this.validator == null) { this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } } // 这个实现太简单了~~~ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!this.afterInitialization) { doValidate(bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (this.afterInitialization) { doValidate(bean); } return bean; } protected void doValidate(Object bean) { Assert.state(this.validator != null, "No Validator set"); Object objectToValidate = AopProxyUtils.getSingletonTarget(bean); if (objectToValidate == null) { objectToValidate = bean; } Set<ConstraintViolation<Object>> result = this.validator.validate(objectToValidate); // 拼接错误消息最终抛出 if (!result.isEmpty()) { StringBuilder sb = new StringBuilder("Bean state is invalid: "); for (Iterator<ConstraintViolation<Object>> it = result.iterator(); it.hasNext();) { ConstraintViolation<Object> violation = it.next(); sb.append(violation.getPropertyPath()).append(" - ").append(violation.getMessage()); if (it.hasNext()) { sb.append("; "); } } throw new BeanInitializationException(sb.toString()); } }}这个BeanValidationPostProcessor实现的功能确实非常的简单,无非就是对所有的Bean在初始化前/后进行校验。我们若是对Spring Bean想做约束的话(比如对属性、构造器等等),使用它就非常的方便~ ...

July 26, 2019 · 5 min · jiezi

三SpringCloud鉴权之OAuth20上篇

鉴权中心springsecurity +oauth2.01、前言必备知识学习本文之前你应该会熟练使用Springboot,并对SpringSecurity和OAuth2.0有所理解,如有需要请参考下面的一些内容,简单理解下相关知识 SpringSecuritySpring Security是一个功能强大、高度可定制的身份验证和访问控制框架。它用于保护基于Spring的应用程序。Spring Security是一个专注于向Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring安全的真正威力在于它可以很容易地扩展以满足定制需求。 OAuth2.0OAuth 2.0是用于授权的行业标准协议。OAuth2.0注重客户端开发人员的简单性,同时为Web应用程序、桌面应用程序、移动电话和客厅设备提供特定的授权流。更多请参考 OAuth2.0 OAuth2.0模式模式应用场景描述授权码(Auth Code)成功后跳转其他页面,如第三方微信登录等 简化模式(implicit)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌 密码模式(password credentials)web页面用户名密码登录 客户端模式(client credentials)主要针对openapi,使用apikey和secretkey方式 JWTJSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。更多请参考 JWT入门教程 2、前期必备核心pom依赖如下: <!-- 注意是starter,自动配置 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><!-- 不是starter,手动配置 --><dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.6.RELEASE</version></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><!-- 将token存储在redis中 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId></dependency>创建一个rest接口用于后面测试资源 @Slf4j@RestControllerpublic class TestSecurityController { @GetMapping("/product/{id}") public String getProduct(@PathVariable String id) { //for debug Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "product id : " + id; } @GetMapping("/order/{id}") public String getOrder(@PathVariable String id) { //for debug Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "order id : " + id; }}3、操作步骤很多文章写的特别复杂,其实主要的内容也就分为下面几步 ...

July 16, 2019 · 2 min · jiezi

JSR303-参数校验

JSR-303 参数校验代码见validator-demo 1、常规使用1.1、请求参数加上符合JSR-303校验注解(包括基本类型和自定义类)。如果请求参数是自定义类,则在类的属性上加校验注解。1.2、请求参数前面加上 @javax.validation.Valid 注解,或是 @org.springframework.validation.annotation.Validated 注解,告诉spring框架调用时进行参数校验。1.3、校验注解是在方法入参上,则需要在该方法所在的类上添加 @org.springframework.validation.annotation.Validated 注解,在入参前或是在方法上添加启用校验注解都不生效。1.4、如果请求参数列表中有 BindingResult,则springmvc框架不会向外抛异常,默认代码自行处理。2、自定义注解2.1、创建自定义注解import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.*;/** * 无敏感词校验注解 */@Documented@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = NoSensitiveWordsValidator.class)public @interface NoSensitiveWords { String message() default "包含敏感词"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}2.2、自定义注解对应的校验类import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.util.HashSet;import java.util.Set;/** * 敏感词校验逻辑 **/public class NoSensitiveWordsValidator implements ConstraintValidator<NoSensitiveWords, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null || "".equals(value.trim())) { return true; } // 这里只简单举例校验 Set<String> sensitiveWords = new HashSet<>(); sensitiveWords.add("毛泽东"); sensitiveWords.add("邓小平"); for (String word : sensitiveWords) { if (value.contains(word)) { return false; } } return true; }}2.3、在类属性或方法入参上使用自定义注解@Datapublic class Account { /** 昵称 */ @Length(min = 2, max = 20) @NoSensitiveWords private String nickName;}3、自定义异常3.1、校验注解使用在实体参数上时,spring抛出的是org.springframework.web.bind.MethodArgumentNotValidException异常。3.2、校验注解使用在方法入参上时,spring抛出的是javax.validation.ConstraintViolationException异常。3.3、针对上述两种情况,可以做统一的拦截并封装成统一的系统异常import lombok.extern.slf4j.Slf4j;import org.linbo.demo.validator.bean.HttpResult;import org.springframework.validation.FieldError;import org.springframework.validation.ObjectError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import javax.validation.ConstraintViolationException;import java.util.List;import java.util.stream.Collectors;@ControllerAdvice@ResponseBody@Slf4jpublic class DefaultExceptionHandler { @ExceptionHandler(value = {MethodArgumentNotValidException.class}) public HttpResult<Object> springValidException(MethodArgumentNotValidException e) { List<ObjectError> allErrors = e.getBindingResult().getAllErrors(); StringBuilder buf = new StringBuilder(); allErrors.forEach(error -> { String objectName = ((FieldError) error).getField(); String message = error.getDefaultMessage(); buf.append(objectName).append(":").append(message).append(", "); }); int len = buf.length(); if (len > 1) { buf.delete(len - 2, len); } HttpResult<Object> data = HttpResult.builder() .code("400") .message(buf.toString()) .build(); log.warn("参数校验错误: {}", data); return data; } @ExceptionHandler(value = {ConstraintViolationException.class}) public HttpResult<Object> jsr303ValidException(ConstraintViolationException e) { HttpResult<Object> data = HttpResult.builder() .code("400") .message(e.getMessage()) .build(); log.warn("参数校验错误: {}", data); return data; }}4、国际化校验异常信息4.1、在resources目录下新增ValidationMessages.properties文件,英文为ValidationMessages_en.properties,中文为英文为ValidationMessages_zh_CN.properties,与标准国际化命名类型,都是ValidationMessages开头命令。4.2、定义信息key和value4.3、在自定义校验注解的String message() default "{properties中定义的key}"中,或是在使用校验注解时(@Length(min = 2, max = 20, message = "{properties中定义的key}"))加入properties中定义的key。

July 15, 2019 · 1 min · jiezi

踩坑IDEA中项目顺利运行但Autowired报错的解决方法

今天在搭建SpringBoot+SpringMVC+mybaits项目的时候,遇到了一个奇怪的问题。 Controller中需要注入Service,Service中需要注入Mybatis的Dao接口,属性都是通过“@+标签名”的方式注入的。比如一个简单的查询用户的controller,需要注入一个与用户有关的service: @RequestMapping("/user")@RestControllerpublic class UserController { @Autowired private UserService userService; @GetMapping("/list/all") public List<User> listAll(){ return userService.listAll(); }}service中又要注入Dao的接口: @Servicepublic class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; public List<User> listAll(){ return userMapper.selectAll(); }}但是在Service层竟然出现了如此问题: 不过问题不大,项目能够正常运行,并且使用@Resource标签代替@Autowired就可以完全解决问题: 但是为什么@Autowired在IDEA里面会有问题呢,经过网上寻找+个人思考,有以下两点结论: 根据使用报错信息在网上搜索出的解决方案的总结首先是IDEA这个工具强大的检测报警机制,如果IDEA说你的代码没问题,那么它肯定能编译通过。给你报错,就算不影响项目运行,那也确实有些不合适的地方。看到网上有些答案很可笑,让你去settings里面把这个报警关掉,这不是掩耳盗铃吗?当然了,如果不影响项目的正常运行,关掉报警也是一种方法,毕竟程序员看不见warning。但是如果项目无法运行,仅关掉报警根本没卵用。 @Autowired与@Resource的区别@Autowired根据type注入,@Resource根据name注入,本质上均实现了注入效果,只是依据不同,那么为什么我在Controller中使用@Autowired就没问题呢,我认为原因在于两个地方注入Bean的类型不一样。以下是个人思考,如有不对请指教。一般来说,注入controller的service虽然一般来说我们都是注入一个接口,但是该接口有实现类,并且使用@Service进行关联,所以注入类型应该也可以视为一个类,但是mybatis仅需提供Dao接口,也就是说,注入service的dao只是一个接口,而没有实现类,虽然mybatis能够通过Dao接口和xml文件实现与数据库的操作,但是@Autowired并没有这个识别功能,可能它就认为你类型不匹配,无法使用通过类型注入的方法。这个理论我通过一个简单的方法验证通过,做法如下:我把service的实现类给取消了实现接口的语句‘implements UserService’,然后变成下面这样: @Servicepublic class UserServiceImpl{ @Resource private UserMapper userMapper; public List<User> listAll(){ return userMapper.selectAll(); }}此时,IDEA给controller中的注入也报出同样的警告: 所以我有充足的理由断定,应该是这个原因,也就是说,@Autowired不适用service层对于dao的注入。

July 15, 2019 · 1 min · jiezi

踩坑maven中mysqlconnectorjava版本更新后driver名更改的问题

跟着教程做SpringBoot的项目,发现教程导入的mybatis-connector-java没有说明版本,可能是教程发布的时候只有5.x版本于是使用最新的版本毫无问题,然而随着mybatis-connector-java发布了6.x之后,莫名其妙把com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver总是报数据库连接的问题,以后一定要注意这种问题。maven依赖中最好是指定确定的版本,不然随着依赖包不断更新,容易出这种问题。

July 15, 2019 · 1 min · jiezi

SpringCloud脚手架之artemis

ArtemisArtemis:月亮女神,希腊奥林珀斯十二主神之一。阿耳忒弥斯与阿波罗一样,司掌光明,她所掌管的就是月亮。除了是月亮女神外,她还很喜欢狩猎,她射箭的技艺很高,经常在山林中追逐野兽。因此她除了是月亮女神外,也被称为狩猎女神。 期望该项目能够项月亮女神一样给刚入门的小伙伴们带来光明Github地址:(https://github.com/liangliang... 说明该项目会是一个完整的Springcloud脚手架,完整版在可以在企业中以直接使用的版本。项目基于SpringCloud Greenwich.SR2版本开发。 项目规划该项目会用maven parent版本进行管理,提供可插拔式通用组件,包含redis,mongodb,mysql,es,kafka等。会有 其他目前该项目还在开发过程中,建议从我的另一个系列开始入门。 SpringCloud快速入门 更多更多内容请关注,公众号会每周更新项目进度及计划

July 15, 2019 · 1 min · jiezi

如何使用OpenFeignWebClient实现非阻塞的接口聚合

随着微服务的遍地开花,越来越多的公司开始采用SpringCloud用于公司内部的微服务框架。 按照微服务的理念,每个单体应用的功能都应该按照功能正交,也就是功能相互独立的原则,划分成一个个功能独立的微服务(模块),再通过接口聚合的方式统一对外提供服务! 然而随着微服务模块的不断增多,通过接口聚合对外提供服务的中层服务需要聚合的接口也越来越多!慢慢地,接口聚合就成分布式微服务架构里一个非常棘手的性能瓶颈! 举个例子,有个聚合服务,它需要聚合Service、Route和Plugin三个服务的数据才能对外提供服务: @Headers({ "Accept: application/json" })public interface ServiceClient { @RequestLine("GET /") List<Service> list();}@Headers({ "Accept: application/json" })public interface RouteClient { @RequestLine("GET /") List<Route> list();}@Headers({ "Accept: application/json" })public interface PluginClient { @RequestLine("GET /") List<Plugin> list();}使用声明式的OpenFeign代替HTTP Client进行网络请求 编写单元测试 public class SyncFeignClientTest { public static final String SERVER = "http://devops2:8001"; private ServiceClient serviceClient; private RouteClient routeClient; private PluginClient pluginClient; @Before public void setup(){ BasicConfigurator.configure(); Logger.getRootLogger().setLevel(Level.INFO); String service = SERVER + "/services"; serviceClient = Feign.builder() .target(ServiceClient.class, service); String route = SERVER + "/routes"; routeClient = Feign.builder() .target(RouteClient.class, route); String plugin = SERVER + "/plugins"; pluginClient = Feign.builder() .target(PluginClient.class, plugin); } @Test public void aggressionTest() { long current = System.currentTimeMillis(); System.out.println("开始调用聚合查询"); serviceTest(); routeTest(); pluginTest(); System.out.println("调用聚合查询结束!耗时:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void serviceTest(){ long current = System.currentTimeMillis(); System.out.println("开始获取Service"); String service = serviceClient.list(); System.out.println(service); System.out.println("获取Service结束!耗时:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void routeTest(){ long current = System.currentTimeMillis(); System.out.println("开始获取Route"); String route = routeClient.list(); System.out.println(route); System.out.println("获取Route结束!耗时:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void pluginTest(){ long current = System.currentTimeMillis(); System.out.println("开始获取Plugin"); String plugin = pluginClient.list(); System.out.println(plugin); System.out.println("获取Plugin结束!耗时:" + (System.currentTimeMillis() - current) + "毫秒"); }}测试结果:开始调用聚合查询开始获取Service{"next":null,"data":[]}获取Service结束!耗时:134毫秒开始获取Route{"next":null,"data":[]}获取Route结束!耗时:44毫秒开始获取Plugin{"next":null,"data":[]}获取Plugin结束!耗时:45毫秒调用聚合查询结束!耗时:223毫秒Process finished with exit code 0可以明显看出:聚合查询查询所用的时间223毫秒 = 134毫秒 + 44毫秒 + 45毫秒 ...

July 15, 2019 · 2 min · jiezi

如何实现Spring框架中的AOP

声明一个AdvisedSupport类,用于保存被代理对象和拦截方法的元数据对象 创建织入点AopProxy,可以通过getProxy方法获取代理后的对象。使用CGLIB生成动态代理,生成Enhancer实例,并指定用于处理代理业务的回调类 完成了织入之后,我们要考虑另外一个问题:对什么类以及什么方法进行AOP?对于“在哪切”这一问题的定义,我们又叫做“Pointcut”。Spring中关于Pointcut包含两个角色:ClassFilter和MethodMatcher,分别是对类和方法做匹配。Pointcut有很多种定义方法,例如类名匹配、正则匹配等,但是应用比较广泛的应该是和AspectJ表达式的方式,Spring借鉴了这种方式 万事俱备,只欠东风!现在我们有了Pointcut和Weave技术,一个AOP已经算是完成了,但是它还没有结合到Spring中去。怎么进行结合呢?Spring给了一个巧妙的答案:使用BeanPostProcessor BeanPostProcessor是BeanFactory提供的,在Bean初始化过程中进行扩展的接口。只要你的Bean实现了BeanPostProcessor接口,那么Spring在初始化时,会优先找到它们,并且在Bean的初始化过程中,调用这个接口,从而实现对BeanFactory核心无侵入的扩展 那么我们的AOP是怎么实现的呢?我们知道,在AOP的xml配置中,我们会写这样一句话: <aop:aspectj-autoproxy/>它其实相当于: <bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>AspectJAwareAdvisorAutoProxyCreator就是AspectJ方式实现织入的核心。它其实是一个BeanPostProcessor。在这里它会扫描所有Pointcut,并对bean做织入 文章来源:www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构

July 15, 2019 · 1 min · jiezi

注解处理器是干嘛的

注解处理器初探    平时做项目中有个非常好用的一个插件,叫lombok.它提供了一些简单的注解,可以用来生成javabean和一些getter/setter方法,提高了开发的效率节省了开发时间.今天我们就来看看lombok使用的什么方式来实现这种操作的.其实lombok使用的是annotation processor,这个是jdk1.5中增加的新功能.像@Getter只是一个注解,它真正的处理部分是在注解处理器里面实现的.官方参考链接. 背景介绍    注解处理器其实全称叫Pluggable Annotation Processing API,插入式注解处理器,它是对JSR269提案的实现,具体可以看链接里面的内容,JSR269链接.它是怎么工作的呢?可以参考下图:1.parse and enter:解析和输入,java编译器这个阶段会把源代码解析生成AST(抽象语法分析树)2.annotation processing:注解处理器阶段,此时将调用注解处理器,这时候可以校验代码,生成新文件等等(处理完可以循环到第一步)3.analyse and generate:分析和生成,此时前两步完成后,生成字节码(这个阶段进行了解糖,比如类型擦除)这些其实只是为了给大家留有一个粗浅的印象,它是怎么执行的. 实践    看了上面的资料,大脑中应该有了一个大概的印象,现在我们实际操作一下写一个简单的例子,实践一下.要使用注解处理器需要两个步骤:1.自定义一个注解2.继承AbstractProcessor并且实现process方法 我们接下来写一个很简单的例子,就是在一个类上加上@InterfaceAnnotation,编译的时候去生成一个"I"+类名的接口类.首先我这里是定义了两个moudle,一个用来写注解和处理器,另一个用来调用注解. 第一步:自定义一个注解@Target({ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface InterfaceAnnotation {}1.@Target:表示的是这个注解在什么上面使用,这里ElementType.TYPE是指在类上使用该注解2.@Retention:表示的是保留到什么阶段,这里RetentionPolicy.SOURCE是源代码阶段,编译后的class上就没有这个注解了 第二步:继承AbstractProcessor并且实现process方法@SupportedAnnotationTypes(value = {"com.example.processor.InterfaceAnnotation"})@SupportedSourceVersion(value = SourceVersion.RELEASE_8)public class InterfaceProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Messager messager = processingEnv.getMessager(); messager.printMessage(Diagnostic.Kind.NOTE, "进入到InterfaceProcessor中了~~~"); // 将带有InterfaceProcessor的类给找出来 Set<? extends Element> clazz = roundEnv.getElementsAnnotatedWith(InterfaceAnnotation.class); clazz.forEach(item -> { // 生成一个 I + 类名的接口类 String className = item.getSimpleName().toString(); className = "I" + className.substring(0, 1) + className.substring(1); TypeSpec typeSpec = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC).build(); try { // 生成java文件 JavaFile.builder("com.example.processor", typeSpec).build().writeTo(new File("./src/main/java/")); } catch (IOException e) { e.printStackTrace(); } }); return true; }}1.@SupportedAnnotationTypes:表示这个processor类要对什么注解生效2.@SupportedSourceVersion:表示支持的java版本3.annotations:被要求的注解,就是@SupportedAnnotationTypes对应的注解4.roundEnv:存放着当前和上一轮processing的环境信息5.TypeSpec这个可能有点没看懂是干嘛的,它是javaPoet中的一个类,javaPoet是java用于生成java文件的一款第三方插件很好用,所以这里使用了这个类来生成java文件,实际上这里用java自带的PrintWriter等输入输出流也可以生成java文件,生成文件有很多方式.javaPoet的链接.javaPoet使用指南.6.Messager是用来打印输出信息的,System.out.println其实也可以;7.process如果返回是true后续的注解处理器就不会再处理这个注解,如果是false,在下一轮processing中,其他注解处理器也会来处理改注解. ...

July 14, 2019 · 2 min · jiezi

Spring-注解编程之-AnnotationMetadata

在上篇文章 Spring 注解编程之模式注解 中我们讲到 Spring 模式注解底层原理,依靠 AnnotationMetadata 接口判断是否存在指定元注解。 这篇文章我们主要深入 AnnotationMetadata,了解其底层原理。 Spring 版本为 5.1.8-RELEASEAnnotationMetadata 结构使用 IDEA 生成 AnnotationMetadata 类图,如下: AnnotationMetadata 存在两个实现类分别为 StandardAnnotationMetadata与 AnnotationMetadataReadingVisitor。StandardAnnotationMetadata主要使用 Java 反射原理获取元数据,而 AnnotationMetadataReadingVisitor 使用 ASM 框架获取元数据。 Java 反射原理大家一般比较熟悉,而 ASM 技术可能会比较陌生,下面主要篇幅介绍 AnnotationMetadataReadingVisitor 实现原理。 基于 AnnotationMetadata#getMetaAnnotationTypes方法,查看两者实现区别。AnnotationMetadataReadingVisitorASM 是一个通用的 Java 字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。 ASM 虽然提供与其他 Java 字节码框架如 Javassist,CGLIB 类似的功能,但是其设计与实现小而快,且性能足够高。 Spring 直接将 ASM 框架核心源码内嵌于 Spring-core中,目前 Spring 5.1 使用 ASM 7 版本。 ASM 框架简单应用Java 源代码经过编译器编译之后生成了 .class 文件。 Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8个字节的数据,将按 照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,因为 PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文 件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。Class 文件中包含类的所有信息,如接口,字段属性,方法,在内部这些信息按照一定规则紧凑排序。ASM 框会以文件流的形式读取 class 文件,然后解析过程中使用观察者模式(Visitor),当解析器碰到相应的信息委托给观察者(Visitor)。 ...

July 13, 2019 · 1 min · jiezi

spring-statemachine的企业可用级开发指南8复杂状态机的实现

1、讲讲复杂流程的需求除了上面文章里面提到的一根筋状态机流程,实际的企业应用中状态机的流程会更加复杂,而我们最常用到的就是choice。它类似于java的if语句,作为条件判断的分支而存在,让我们先看一张图: 这张图表现的是一个表单(form)的整个状态流程: 创建初始的空白表单( BLANK_FORM)填写(WRITE)表单,成为填充完表单(FULL_FORM)检查(CHEKC)表单如果是表单名(formName)不为null,表单成为待提交表单(CONFIRM_FROM)提交(SUBMIT)表单,成为成功提交表单(SUCCESS_FORM) 检查(CHECK)表单如果表单名为null,表单成为待处理表单(DEAL_FORM),修改formName处理(DEAL)表单如果表单名还是null或者里面有个“坏”字,表单状态变成废单(FAILED_FORM)如果表单名称没问题,表单状态变成填充完表单状态(FULL_FORM),重新走流程 大家不要在意这个例子的幼稚,毕竟它还是能用简单的方式体现出流程的复杂性(这话多么的辩证统一)。它有判断分支(还有两个),还有流程的循环,还有分支判断后的直接失败和分支判断后的后续环节,后面我们会在代码中告诉大家这里面需要注意的东西。 2、代码实现先上状态机的四件套:States,Events,Builder和EventConfig(spring statemachine还是蛮简单的,来来回回就这几样东西) public enum ComplexFormStates { BLANK_FORM, // 空白表单FULL_FORM, // 填写完表单CHECK_CHOICE,//表单校验判断DEAL_CHOICE,//表单处理校验DEAL_FORM,//待处理表单CONFIRM_FORM, // 校验完表单SUCCESS_FORM,// 成功表单FAILED_FORM//失败表单}注意一点,choice判断分支本身也是一种状态,要声明出来,这里是CHECK_CHOICE和DEAL_CHOICE public enum ComplexFormEvents { WRITE, // 填写CHECK,//校验DEAL,//处理SUBMIT // 提交}同样的分支事件也要声明,这里是CHECK和DEAL。 大头是MachineBuilder,这个代码就比较多,大家最好对照着上面这张图来看,会比较清晰 /** 复杂订单状态机构建器*/@Componentpublic class ComplexFormStateMachineBuilder { private final static String MACHINEID = "complexFormMachine"; /** * 构建状态机 * * @param beanFactory * @return * @throws Exception */public StateMachine<ComplexFormStates, ComplexFormEvents> build(BeanFactory beanFactory) throws Exception { StateMachineBuilder.Builder<ComplexFormStates, ComplexFormEvents> builder = StateMachineBuilder.builder(); System.out.println("构建复杂表单状态机"); builder.configureConfiguration() .withConfiguration() .machineId(MACHINEID) .beanFactory(beanFactory); builder.configureStates() .withStates() .initial(ComplexFormStates.BLANK_FORM) .choice(ComplexFormStates.CHECK_CHOICE) .choice(ComplexFormStates.DEAL_CHOICE) .states(EnumSet.allOf(ComplexFormStates.class)); builder.configureTransitions() .withExternal() .source(ComplexFormStates.BLANK_FORM).target(ComplexFormStates.FULL_FORM) .event(ComplexFormEvents.WRITE) .and() .withExternal() .source(ComplexFormStates.FULL_FORM).target(ComplexFormStates.CHECK_CHOICE) .event(ComplexFormEvents.CHECK) .and() .withChoice() .source(ComplexFormStates.CHECK_CHOICE) .first(ComplexFormStates.CONFIRM_FORM, new ComplexFormCheckChoiceGuard()) .last(ComplexFormStates.DEAL_FORM) .and() .withExternal() .source(ComplexFormStates.CONFIRM_FORM).target(ComplexFormStates.SUCCESS_FORM) .event(ComplexFormEvents.SUBMIT) .and() .withExternal() .source(ComplexFormStates.DEAL_FORM).target(ComplexFormStates.DEAL_CHOICE) .event(ComplexFormEvents.DEAL) .and() .withChoice() .source(ComplexFormStates.DEAL_CHOICE) .first(ComplexFormStates.FULL_FORM, new ComplexFormDealChoiceGuard()) .last(ComplexFormStates.FAILED_FORM); return builder.build(); }}这里面出现了几个新东西,要说一下: ...

July 12, 2019 · 5 min · jiezi

手撕面试官系列二开源框架面试题SpringSpringMVCMyBatis

跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技术原因(影响自己的发展,偏移自己规划的轨迹),还是钱给少了,不受重视。 闲话不多说开始主题(面试题+答案领取方式见个人主页) 以下为常见spring面试题: 1 、什么是 Spring 框架?Spring 框架有哪些主要模块?2 、使用 Spring 框架能带来哪些好处?3 、什么是控制反转(IOC) ?什么是依赖注入?4 、请解释下 Spring 框架中的 IoC ?5 、BeanFactory 和 和 ApplicationContext 有什么区别?6 、Spring 有几种配置方式?7 、如何用基于 XML 配置的方式配置 Spring ?8 、如何用基于 Java 配置的方式配置 Spring?9 、怎样用注解的方式配置 Spring10 、请解释 Spring Bean 的生命周期?11 、Spring Bean 的作用域之间有什么区别?12 、什么是 Spring inner beans?13 、Spring 框架中的单例 Beans 是线程安全的么?14 、请举例说明如何在 Spring 中注入一个 Java Collection?15 、如何向 Spring Bean 中注入一个 Java.util.Properties ?16 、请解释 Spring Bean 的自动装配?17 、请解释自动装配模式的区别?18 、如何开启基于注解的自动装配?19 、请举例解释@Required 注解?20 、请举例解释@Autowired 注解?21 、请举例说明@Qualifier 注解?22 、构造方法注入和设值注入有什么区别?23 、Spring 框架中有哪些不同类型的事件?24 、FileSystemResource 和 和 ClassPathResource 有何区别?25 、Spring 框架中都用到了哪些设计模式? ...

July 11, 2019 · 2 min · jiezi

spring-cloud-微服务之间上传文件

今天开发项目时,遇到一个需求,需要上传一个excel文件到微服务上,微服务是国外同事写的,我负责处理gateway和前端部分。在postman上测试接口没有问题了,就准备在代码就实现。但是遇到了一个问题,无论怎样都调用不成功。此时前端到gateway的调用是成功的,因为我可以把controller拿到的数据成功写入到本地。问题就出在gateway调用另一个微服务上。@RequestMapping(value = "/import/excel", method = RequestMethod.POST) public Object importTableExcel(@RequestParam("file") MultipartFile file, @RequestParam("tableName") String tableName, @RequestParam("importType") String importType) { return ep2LookUpDBFeignClient.importTableExcel(file, tableName, importType); }这个是controller代码,前端用formData封装提交,这里用MultipartFile 接收。到这一步是没有问题的@RequestMapping(value = "/api/tables/import/excel", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) Object importTableExcel(@RequestParam("file") MultipartFile file,@RequestParam("tableName")String tableName, @RequestParam("importType")String importType);这个是调用微服务的代码,问题就出在这里。但是我一开始也是百思不得其解,参数设置什么的都没有问题了,那怎么还会出错了。于是我各种找资料,然后在一篇文中,看到原来是注解写错了,MultipartFile 的注解应该是@RequestPart,而不是@RequestParam。改了之后果然可以了。@RequestMapping(value = "/api/tables/import/excel", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) Object importTableExcel(@RequestPart("file") MultipartFile file,@RequestParam("tableName")String tableName, @RequestParam("importType")String importType);这个是可以成功运行的代码,在此记录一下,以免下次还犯同样的错误。以下是 @RequestParam和@RequestPart的区别1.@RequestPart这个注解用在multipart/form-data表单提交请求的方法上。 2.支持的请求方法的方式MultipartFile,属于Spring的MultipartResolver类。这个请求是通过 http协议传输的。 3.@RequestParam也同样支持multipart/form-data请求。 4.他们最大的不同是,当请求方法的请求参数类型不再是String类型的时候。 5.@RequestParam适用于name-valueString类型的请求域,@RequestPart适用于复杂的请求域(像JSON,XML)。

July 11, 2019 · 1 min · jiezi

敏捷项目的不确定性管理

敏捷方法最大的固有优势之一是管理敏捷项目的不确定性。为了更好地理解这一点,我们需要先了解以下两者之间的区别: 一种经验过程控制模型定义的过程控制模型经验和定义的过程模型在这种情况下要理解的关键是与经验过程和定义过程之间的区别。 经验过程控制模型敏捷基于经验过程方法 - “经验”一词意味着“基于实验或观察”。 当您使用经验过程方法时, 你接受在开始之前你不知道你可能想知道的关于项目的一切该过程旨在调整解决方案和流程,以便随着项目的进展发现学习的解决方案。定义的过程控制模型“定义过程”是可重复的,并且从一个项目到下一个项目并没有显着变化 它产生的结果是非常可预测的,而“经验过程”专门用于支持适应性而不是可预测性。因此,经验过程更适合具有高度不确定性的项目。敏捷项目的不确定性理解敏捷项目不确定性的一个非常强大的概念是“Stacey复杂性模型”,如下所示: 这个模型有两个不确定因素: 要求不确定性一个方面是需求的不确定性 - 项目的目标和要求以及他们知道客户是否真能确定自己想要的是甚么? 技术不确定性另一个方面是技术的不确定性 - 对问题的技术解决方案以及与技术解决方案相关的风险级别的理解程度如何? 这是一个非常重要的概念,因为处理不确定性的能力在当今项目开发中最关键的,而且大量计划驱动的项目并不能很好地应对高水平的不确定性。 不确定性管理计划驱动项目中通常发生的事情是项目经理在启动项目之前尝试将不确定性水平降低到可接受的水平: 在项目开始之前尽可能地解决需求中的任何不确定性,并且尽量消除尽可能多的技术风险这通常会导致使用久经考验的技术,并且在进入新的和未定义的用户需求方面并没有太大的作用。当然,其缺点是技术方法可能会在发布后的相对较短的时间内过时,也可能导致解决方案非常平庸。 “管理不确定性”是什么意思?让我澄清“管理不确定性”的含义。 对某些人而言,不确定性就像是攻击项目的不冶之症并导致其失败。传统的项目管理思想强化了这种方法,以尽可能地降低项目中的风险和不确定性我不这么认为,不确定性也可能代表超出预期的机会,项目产生所创做的价值及机会以倍数的高於其风险和不确定性直如果通过降低风险和不确定性来强制项目去适应计划驱动模型,您可能会最大化项目的可预测性,以满足成本和进度目标,但最大限度地降低项目产生的价值总结以下是一些重要观点的摘要: 不应忽视不确定性,根本不管理。不确定性通常与机会直接相关幸运的是,这不是一个黑白决定: 一种完全僵化的,计划驱动的方法,几乎没有不确定性和完全自适应的方法,具有极高的不确定性。正确的做法是使方法适应项目中的不确定性水平,而不是将项目强制拟合到某种一成文变的預訂好的方法(无论它可能是什么)。开发一种管理不确定性的智能方法需要更多的技能; 这个需要: 能够客观地评估项目中的不确定性水平理解经验和定义的过程模型更深入地了解这些方法背后的原理,了解如何将两者融合在一起以适应这种情况这才是有效的敏捷项目管理方法的本质。 References What is Burndown Chart in Scrum?What is the Role-Feature-Reason Template?Sprint Increment vs Potential Shippable Product vs MVP vs MMPWrite SMART Goals & INVEST for User StoriesWhat is DEEP in Product Backlog?How to Write Product Vision for Scrum Project?How to Use Scrum Board for Agile Development?Who Create Product Backlog Items or User Stories in Scrum?What is Agile Estimation?What is Story Point in Agile? How to Estimate a User Story?

July 11, 2019 · 1 min · jiezi

springmvc使用Valid和ControllerAdvise实现请求参数校验统一异常处理

springmvc使用@Valid和@ControllerAdvise实现请求参数校验统一异常处理最开始我使用的是jsp+servlet。后台接口使用 request.getParameter(key) 方法接收参数,特别麻烦。接收之后,我们还得进行一连串的参数校验。现在使用springboot的mvc。使用@ReuestBody接收参数,自动将前端参数解析封装成实体类。很方便,但是还是需要校验参数。下面我介绍一个简单的方式,通过@Valid和@ControllerAdvice注解实现参数校验和统一异常处理。 统一异常处理首先介绍统一异常处理,创建类GlobalExceptionHandler通过ControllerAdvice和@ExceptionHandler注解,在Controller中发生的异常错误就到指定异常处理方法进行处理。查看一下MethodArgumentNotValidException这个类的源码它继承了Exception,包含参数和错误。BindError参数实现了error。统一异常处理类实现完成。 请求参数校验我们在@RequestBody或者其它注解前面加上@Valid,就可以对实体类参数进行校验通过@Valid注解我们就对请求的参数进行验证通过javax的@NotNUll等注解可以进行校验。如果参数是一个实体类,这个实体类的参数也需校验,需要在参数上加上@Valid注解ok,现在参数不对的话,自动跳到GlobalExceptionHandler类的指定方法进行处理。 未完待续,有问题请留言!个人博客地址: https://blog.ailijie.top/arch...

July 11, 2019 · 1 min · jiezi

spring实战使用注解反射来解决switch或者if多条件判断的问题

业务场景在与仓库系统的对接过程中,我们使用了阿里巴巴的奇门规范。该规范中根据不同的method方法参数来确定不同的业务,比如: # 入库单创建method=taobao.qimen.entryorder.create# 库存查询method=taobao.qimen.inventory.query# 商品同步接口method=taobao.qimen.singleitem.synchronize那么我们在解析的时候,常用的方式就是使用switch或者if来处理,以switch为例,实现代码如下: switch (method) { case "taobao.qimen.entryorder.create": return entryorderCreate(); case ""taobao.qimen.inventory.query: return inventoryQuery(); case "taobao.qimen.singleitem.synchronize": return singleitemSyncronize(); default: return "";}通过switch,我们根据不同的method能够返回不同的执行逻辑结果。从功能上来说,没有任何的毛病。但是作为一个程序员,如果只是为了完成功能而写代码,那这又的程序员是没有灵魂的。 问题在奇门api技术文档中,大概有50多个不同的业务接口method,这也就意味着我们至少要case 50次以上。你觉得一个switch中case 50次合理吗?答案当然是不合理的。 在这了再分享一句话: 任何一个傻瓜都能写出计算机能理解的程序,而优秀的程序员却能写出别人能读得懂的程序。—— Martin Fowler解决方案解决思路:每次接受请求之后,根据method的不同,来执行不同的业务逻辑。那么我们能不能将请求的method和需要执行的业务逻辑方法做一个映射,这样我们根据method就能直接找到具体的业务逻辑处理方法。那么我们的method怎么和我们的业务方法映射绑定呢?解决方法是在每个业务方法上面增加一个注解(比如@Name)。那么问题来了,我们什么时候生成这样的映射关系呢?我们可以在容器启动的时候,就去生成这样的映射关系。那么我们怎么知道哪些类包含了具有@Name注解的方法呢?为了能快速获取到包含@Name的类,我们增加一个类注解@MethodHandler,在方法上使用了@Name注解的类上我们加上一个@MethodHandler注解。这样我们就能快速找到这样的类了。 具体实现@Name注解 @Target(ElementType.METHOD)@Documented@Retention(RetentionPolicy.RUNTIME)public @interface Name { String[] value() default {};}@Target(ElementType.METHOD)表示@Name是个方法注解。同时里面的value是个数组,是因为可能存在多个method执行相同业务逻辑的情况 @MethodHandler注解 @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MethodHandler {}@Target({ElementType.TYPE})表示@MethodHandler是个类或者接口注解,次注解的作用是让我们能快速找到包含@Name注解的方法。 MethodMappering /** * 方法映射 */public class MethodMapping { //方法注解对应的名字 public String[] names; //具体的执行方法 public Method method; public MethodMapping(String[] names, Method method) { this.names = names; this.method = method; }}这个类主要存储奇门method和具体执行的方法的映射 ...

July 11, 2019 · 3 min · jiezi

spring-statemachine的企业可用级开发指南7伪持久化和中间段状态机

1、伪持久化和中间段的状态机我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,我们只需要从订单表里面把状态取出来,知道当前是什么状态,然后伴随着业务继续流浪到下一个状态节点就好了(流浪远方,流~浪~~)。 我们先实现一个StateMachinePersist,因为我不想真的持久化,所以就敷衍一下,持久化是什么,啥也不干。 import org.springframework.statemachine.StateMachineContext;import org.springframework.statemachine.StateMachinePersist;import org.springframework.statemachine.support.DefaultStateMachineContext;import org.springframework.stereotype.Component; @Componentpublic class OrderStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, Order> { @Overridepublic void write(StateMachineContext<OrderStates, OrderEvents> context, Order contextObj) throws Exception { //这里不做任何持久化工作}@Overridepublic StateMachineContext<OrderStates, OrderEvents> read(Order contextObj) throws Exception { StateMachineContext<OrderStates, OrderEvents> result = new DefaultStateMachineContext<OrderStates, OrderEvents>(OrderStates.valueOf(contextObj.getState()), null, null, null, null, "orderMachine"); return result;}}然后在PersistConfig里面转换成StateMachinePersister @Configurationpublic class PersistConfig {@Autowired private OrderStateMachinePersist orderStateMachinePersist;@Bean(name="orderPersister") public StateMachinePersister<OrderStates, OrderEvents, Order> orderPersister() { return new DefaultStateMachinePersister<OrderStates, OrderEvents, Order>(orderStateMachinePersist);}}现在问题来了,不持久化的持久化类是为啥呢,主要就是为了取一个任何状态节点的状态机,方便继续往下执行,请看controller @RestController@RequestMapping("/statemachine")public class StateMachineController { ...

July 11, 2019 · 1 min · jiezi