Spring-security-一架构框架ComponentServiceFilter分析

想要深入spring security的authentication (身份验证)和access-control(访问权限控制)工作流程,必须清楚spring security的主要技术点包括关键接口、类以及抽象类如何协同工作进行authentication 和access-control的实现。 1.spring security 认证和授权流程常见认证和授权流程可以分成: A user is prompted to log in with a username and password (用户用账密码登录)The system (successfully) verifies that the password is correct for the username(校验密码正确性)The context information for that user is obtained (their list of roles and so on).(获取用户信息context,如权限)A security context is established for the user(为用户创建security context)The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.(访问权限控制,是否具有访问权限)1.1 spring security 认证上述前三点为spring security认证验证环节: ...

October 7, 2019 · 4 min · jiezi

SpringBoot2-整合-ClickHouse数据库实现高性能数据查询分析

本文源码:GitHub·点这里 || GitEE·点这里 一、ClickHouse简介1、基础简介Yandex开源的数据分析的数据库,名字叫做ClickHouse,适合流式或批次入库的时序数据。ClickHouse不应该被用作通用数据库,而是作为超高性能的海量数据快速查询的分布式实时处理平台,在数据汇总查询方面(如GROUP BY),ClickHouse的查询速度非常快。2、数据分析能力OLAP场景特征· 大多数是读请求· 数据总是以相当大的批(> 1000 rows)进行写入· 不修改已添加的数据· 每次查询都从数据库中读取大量的行,但是同时又仅需要少量的列· 宽表,即每个表包含着大量的列· 较少的查询(通常每台服务器每秒数百个查询或更少)· 对于简单查询,允许延迟大约50毫秒· 列中的数据相对较小: 数字和短字符串(例如,每个URL 60个字节)· 处理单个查询时需要高吞吐量(每个服务器每秒高达数十亿行)· 事务不是必须的· 对数据一致性要求低· 每一个查询除了一个大表外都很小· 查询结果明显小于源数据,换句话说,数据被过滤或聚合后能够被盛放在单台服务器的内存中列式数据存储(1)、行式数据 (2)、列式数据 (3)、对比分析 分析类查询,通常只需要读取表的一小部分列。在列式数据库中可以只读取需要的数据。数据总是打包成批量读取的,所以压缩是非常容易的。同时数据按列分别存储这也更容易压缩。这进一步降低了I/O的体积。由于I/O的降低,这将帮助更多的数据被系统缓存。二、整合SpringBoot框架该案例基于:Druid连接池和mybatis进行整合。Druid 1.1.10 版本 SQL Parser对clickhouse的开始提供支持。1、核心依赖<dependency> <groupId>ru.yandex.clickhouse</groupId> <artifactId>clickhouse-jdbc</artifactId> <version>0.1.53</version></dependency>2、配属数据源spring: datasource: type: com.alibaba.druid.pool.DruidDataSource click: driverClassName: ru.yandex.clickhouse.ClickHouseDriver url: jdbc:clickhouse://127.0.0.1:8123/default initialSize: 10 maxActive: 100 minIdle: 10 maxWait: 60003、Druid连接池配置@Configurationpublic class DruidConfig { @Resource private JdbcParamConfig jdbcParamConfig ; @Bean public DataSource dataSource() { DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(jdbcParamConfig.getUrl()); datasource.setDriverClassName(jdbcParamConfig.getDriverClassName()); datasource.setInitialSize(jdbcParamConfig.getInitialSize()); datasource.setMinIdle(jdbcParamConfig.getMinIdle()); datasource.setMaxActive(jdbcParamConfig.getMaxActive()); datasource.setMaxWait(jdbcParamConfig.getMaxWait()); return datasource; }}4、参数配置类@Component@ConfigurationProperties(prefix = "spring.datasource.click")public class JdbcParamConfig { private String driverClassName ; private String url ; private Integer initialSize ; private Integer maxActive ; private Integer minIdle ; private Integer maxWait ; // 省略 GET 和 SET}这样整合代码就完成了。 ...

October 7, 2019 · 2 min · jiezi

SpringBoot2-整合-Drools规则引擎实现高效的业务规则

本文源码:GitHub·点这里 || GitEE·点这里 一、Drools引擎简介1、基础简介Drools是一个基于java的规则引擎,开源的,可以将复杂多变的规则从硬编码中解放出来,以规则脚本的形式存放在文件中,使得规则的变更不需要修正代码重启机器就可以立即在线上环境生效。具有易于访问企业策略、易于调整以及易于管理的特点,作为开源业务规则引擎,符合业内标准,速度快、效率高。2、规则语法(1)、演示drl文件格式 package droolRule ;import org.slf4j.Loggerimport org.slf4j.LoggerFactory ;dialect "java"rule "paramcheck1" when then final Logger LOGGER = LoggerFactory.getLogger("param-check-one 规则引擎") ; LOGGER.info("参数");end(2)、语法说明 · 文件格式可以 .drl、xml文件,也可以Java代码块硬编码;· package规则文件中,package是必须定义的,必须放在规则文件第一行;· import规则文件使用到的外部变量,可以是一个类,也可以是类中的可访问的静态方法;· rule定义一个规则。paramcheck1规则名。规则通常包含三个部分:属性、条件、结果;二、整合SpringBoot框架1、项目结构 2、核心依赖<!--drools规则引擎--><dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>7.6.0.Final</version></dependency><dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>7.6.0.Final</version></dependency><dependency> <groupId>org.drools</groupId> <artifactId>drools-templates</artifactId> <version>7.6.0.Final</version></dependency><dependency> <groupId>org.kie</groupId> <artifactId>kie-api</artifactId> <version>7.6.0.Final</version></dependency><dependency> <groupId>org.kie</groupId> <artifactId>kie-spring</artifactId> <version>7.6.0.Final</version></dependency>3、配置文件@Configurationpublic class RuleEngineConfig { private static final Logger LOGGER = LoggerFactory.getLogger(RuleEngineConfig.class) ; private static final String RULES_PATH = "droolRule/"; private final KieServices kieServices = KieServices.Factory.get(); @Bean public KieFileSystem kieFileSystem() throws IOException { KieFileSystem kieFileSystem = kieServices.newKieFileSystem(); ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*"); String path = null; for (Resource file : files) { path = RULES_PATH + file.getFilename(); LOGGER.info("path="+path); kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8")); } return kieFileSystem; } @Bean public KieContainer kieContainer() throws IOException { KieRepository kieRepository = kieServices.getRepository(); kieRepository.addKieModule(kieRepository::getDefaultReleaseId); KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem()); kieBuilder.buildAll(); return kieServices.newKieContainer(kieRepository.getDefaultReleaseId()); } @Bean public KieBase kieBase() throws IOException { return kieContainer().getKieBase(); } @Bean public KieSession kieSession() throws IOException { return kieContainer().newKieSession(); } @Bean public KModuleBeanFactoryPostProcessor kiePostProcessor() { return new KModuleBeanFactoryPostProcessor(); }}这样环境整合就完成了。 ...

October 7, 2019 · 2 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

dubbospringboot的autoconfigure示例找不到服务的解决办法

本示例基于dubbo-spring-boot-project 2.7.3版本,可能会根据新版的发布而过时,阅读时请注意。关于dubbo在spring-boot中该如何使用,网上有很多例子,但因为时间跨度太久,很多例子已经过时了,一切还是要以官方的例子为准。 在github上搜索dubbo和spring-boot整合的项目的话,可能会找到下面两个,分别是 alibaba / dubbo-spring-boot-starterapache / dubbo-spring-boot-project第一个项目,已经归档了(archived),不再更新,所以我们要以第二个项目为准,千万别搞错了。 打开第二个项目的主页,就开始浏览README中的Getting Started章节。 这个章节给我们展示了一个无注册中心(dubbo.registry.address=N/A)的例子。 但是它却跑不起来,消费者启动后无法找到service provider,报Not found exported service的错误。 解决办法如下:需要在消费者Reference服务提供者时,url里指明version。其实version已经指明了,但不知为何还要在url里再次指定。 // @Reference(version = "1.0.0", url = "dubbo://127.0.0.1:12345") @Reference(version = "1.0.0", url = "dubbo://127.0.0.1:12345?version=1.0.0") private DemoService demoService;另外,Getting Started中的pom依赖也比较简略,省略了spring-boot原本需要的依赖,您可以参考我这个修复版里pom中的依赖。 源码地址:https://github.com/kongxiangxin/dubbo-spring-boot-samples

October 7, 2019 · 1 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

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

使用Spring-Boot-DevTools加快开发速度

2018年11月2日 上次更新时间:2019年4月28日 Spring Boot开发者工具如何使用DevTools进一步加快Spring Boot开发的速度,并使之更加有趣和高效? 设定像通常使用Spring Boot一样,设置非常简单。您需要做的就是添加正确的依赖关系,您就可以开始工作了。Spring Boot会检测到这一点,并相应地自动配置DevTools。 如果您使用的是Maven: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency>另外,在使用Gradle时: configurations { developmentOnly runtimeClasspath { extendsFrom developmentOnly }}dependencies { developmentOnly("org.springframework.boot:spring-boot-devtools")}请注意,该依赖项被声明为可选。这个很重要。这样可以防止将DevTools依赖项过渡性地应用于依赖于项目的其他模块。 自动重启只要您的类路径上的文件发生更改,DevTools就会在应用了新更改的情况下自动重启正在运行的应用程序。在本地进行开发时,这很有用,因为您无需手动重新部署应用程序。 就其本身而言,它并不是那么有用,因为重新启动仍会花费大量时间。幸运的是,由于DevTools使用了一个巧妙的技巧,这些重启比常规重启快得多。 您会看到,在开发应用程序时,通常会更改一个或几个类,并希望在运行的应用程序中检查结果以获取反馈。您更改了应用程序的一小部分,因为大多数已加载的类来自框架和第三方库。 引擎盖下,春DevTools使用两个类加载器-基地和重启。不变的类由基本类加载器加载。您正在使用的类由重新启动类加载器加载。每当触发重新启动时,重新启动类加载器都会被丢弃并重新创建。这种重启应用程序的速度比平常快得多,并且可以替代使用诸如JRebel之类的动态类来重新加载应用程序。 在IDE中触发重启只要类路径发生更改,就会触发重新启动。但是,这取决于您的IDE。这意味着仅更改.java文件是不够的。重要的是您的IDE实际上会更新.class类路径上的文件。 使用IntelliJ IDEA时,需要构建项目(Ctrl+ F9或Build → Build Project)。您还可以将IDEA配置为自动重建。另外,您可以打开Spring Boot运行配置并定义触发应用程序更新(Ctrl+ F10)时发生的情况: Intellij IDEA Spring Boot运行配置 在第一个组合框中,可以选择Update trigger file在调用Update操作时触发DevTools重新启动。或者,您甚至可以选择尝试热插拔的选项,并且仅在热插拔失败时才使用DevTools重新启动。 在第二个组合框中,您可以配置在IDEA窗口失去焦点时(例如,切换到浏览器窗口时)重新加载所有静态资源和模板。 在Eclipse中,仅保存文件就足够了。 仅开发Spring Boot DevTools的使用仅用于开发,而不用于生产。如果您的应用程序检测到您正在生产中,则将自动禁用DevTools。 为此,每当您将应用程序作为完全打包的工件(例如带有嵌入式应用程序服务器的jar)运行时,都将其视为生产应用程序: java -jar devtools-example-1.0.0.jar通过特殊的类加载器(例如在应用程序服务器上)启动应用程序时,同样适用。相反,当您运行分解的工件(例如在IDE中)时,您的应用程序将被视为处于开发模式。使用spring-boot-plugin运行应用程序时也是如此: Maven: mvn spring-boot:runGradle: gradle bootRun实时重载LiveReload是一个有用的工具,它使您可以在更改HTML,CSS,图像等文件时立即在浏览器中更新页面。它甚至可以根据需要对文件进行预处理-这意味着会自动编译您的SASS或LESS文件。 实时重新加载Spring DevTools自动启动LiveReload服务器的本地实例,该实例监视您的文件。您所需要做的就是安装浏览器扩展程序,一切顺利。它不仅对开发应用程序的前端很有用(以防您将其作为Spring应用程序工件的一部分进行分发),而且还可以用于监视和重新加载REST API的输出。 属性覆盖在本地开发应用程序时,通常与在生产环境中运行时具有不同的配置需求。缓存就是一个例子。在生产中,至关重要的是依赖于各种缓存(例如,模板引擎的缓存,静态资源的缓存头等)。在开发中,它可能会因提供旧数据而没有反映您的最新更改而使您感到痛苦。另一个示例可能是增强的日志记录,它在开发中可能有用,但对于生产而言却过于详细。 自己管理双套配置不必要地复杂。好消息是,Spring Boot DevTools开箱即用为您的本地开发配置了许多属性。 spring.thymeleaf.cache=falsespring.freemarker.cache=falsespring.groovy.template.cache=falsespring.mustache.cache=falseserver.servlet.session.persistent=truespring.h2.console.enabled=truespring.resources.cache.period=0spring.resources.chain.cache=falsespring.template.provider.cache=falsespring.mvc.log-resolved-exception=trueserver.servlet.jsp.init-parameters.development=truespring.reactor.stacktrace-mode.enabled=true您可以在DevToolsPropertyDefaultsPostProcessor中检查所有属性的列表。 ...

October 4, 2019 · 2 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

SpringBoot整合websocket

什么是WebSocket?WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 话不多说,马上进入干货时刻。 maven依赖SpringBoot2.0对WebSocket的支持简直太棒了,直接就有包可以引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> WebSocketConfig启用WebSocket的支持也是很简单,几句代码搞定 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;/** * 开启WebSocket支持 * @author zhengkai */@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } WebSocketServer因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller直接@ServerEndpoint("/websocket")、@Component启用即可,然后在里面实现@OnOpen,@onClose,@onMessage等方法 import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;import org.springframework.stereotype.Component;import cn.hutool.log.Log;import cn.hutool.log.LogFactory;import lombok.extern.slf4j.Slf4j;@ServerEndpoint("/websocket/{sid}")@Componentpublic class WebSocketServer { static Log log=LogFactory.get(WebSocketServer.class); //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //接收sid private String sid=""; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(Session session,@PathParam("sid") String sid) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount()); this.sid=sid; try { sendMessage("连接成功"); } catch (IOException e) { log.error("websocket IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 log.info("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message, Session session) { log.info("收到来自窗口"+sid+"的信息:"+message); //群发消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("发生错误"); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群发自定义消息 * */ public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException { log.info("推送消息到窗口"+sid+",推送内容:"+message); for (WebSocketServer item : webSocketSet) { try { //这里可以设定只推送给这个sid的,为null则全部推送 if(sid==null) { item.sendMessage(message); }else if(item.sid.equals(sid)){ item.sendMessage(message); } } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; }}消息推送至于推送新信息,可以再自己的Controller写个方法调WebSocketServer.sendInfo();即可 ...

October 4, 2019 · 2 min · jiezi

Java从单体到微服务打造房产销售平台

概述最近在学习某课的《Java从单体到微服务打造房产销售平台》,也算是我学习的第一门微服务课程,在此开贴记录一下知识点,如有不当请多指教! Spring Mail发送激活链接功能实现:在注册用户时通过spring mail发送激活链接到用户的邮箱,在有效期内用户点击链接后更新用户状态为激活状态。 引入spring-mail依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>配置appliacation.properties文件 #用来发送邮件domain.name=127.0.0.1:8090#spring-mailspring.mail.host=smtp.163.com #163邮箱spring.mail.username=chenwuguii@163.com spring.mail.password=czy123456 #163邮箱授权码spring.mail.properties.mail.smtp.auth=truehousespring.mail.properties.mail.smtp.starttls.enable=truespring.mail.properties.mail.smtp.starttls.required=trueMailService类 @Servicepublic class MailService { @Autowired private JavaMailSender mailSender; @Value("${spring.mail.username}") private String from; @Value("${domain.name}") private String domainName; @Autowired private UserMapper userMapper; //缓存key-email键值对,当超过15分钟有效期后,若用户还未激活则从数据库删除用户信息 private final Cache<String, String> registerCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(15, TimeUnit.MINUTES) .removalListener(new RemovalListener<String, String>() { @Override public void onRemoval(RemovalNotification<String, String> notification) { String email = notification.getValue(); User user = new User(); user.setEmail(email); List<User> targetUser = userMapper.selectUsersByQuery(user); if (!targetUser.isEmpty() && Objects.equal(targetUser.get(0).getEnable(), 0)) { userMapper.delete(email);// 代码优化: 在删除前首先判断用户是否已经被激活,对于未激活的用户进行移除操作 } } }).build(); /** * 发送邮件 */ @Async public void sendMail(String title, String url, String email) { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from);//发送方邮箱 message.setSubject(title);//标题 message.setTo(email);//接收方邮箱 message.setText(url);//内容 mailSender.send(message); } /** * 1.缓存key-email的关系 * 2.借助spring mail 发送邮件 * 3.借助异步框架进行异步操作 * * @param email */ @Async public void registerNotify(String email) { String randomKey = RandomStringUtils.randomAlphabetic(10); registerCache.put(randomKey, email); String url = "http://" + domainName + "/accounts/verify?key=" + randomKey; //发送邮件 sendMail("房产平台激活邮件", url, email); }}Nginx代理前提:当用户上传图片时,我们在数据库存放的是相对地址,然后保存图片到本地,在浏览器需要展示图片时我们取出相对路径后拼接上前缀路径,这里我们使用nginx代理我们图片的存放位置 ...

October 3, 2019 · 1 min · jiezi

简明易理解的SpringBootApplication注解源码分析

springApplication一、@SpringBootApplication 的作用是什么? Q:springboot项目的启动类上,都会有个注解@SpringBootApplication,这个注解起到了什么作用? @SpringBootApplicationpublic class MicroServiceApplication { public static void main(String[] args) { SpringApplication.run(MicroServiceApplication.class, args); }} 我们进入@SpringBootApplication注解,发现它等价于三个注解:@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { ...}1) @SpringBootConfiguration 就相当于 @Configuration @Configurationpublic @interface SpringBootConfiguration {}2) @EnabelAutoConfiguration 相当于将这两个类的实例加入到容器中AutoConfigurationImportSelector.class AutoConfigurationPackages.Registrar.class @AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { ...}@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {}AutoConfigurationImportSelector.class的作用是,注入spring.factories文件中EnableAutoConfiguration对应的类的实例,当然要经过spring.factories文件中AutoConfigurationImportFilter对应的过滤器(OnBeanCondition、OnClassCondition、OnWebApplicationCondition等等)的过滤。还要排除掉@EnableAutoConfiguration中的exclude和excludeName 具体见ConfigurationClassParser的getImports方法,其中调用了AutoConfigurationImportSelector的process方法和selectImports方法。 public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); }AutoConfigurationImportSelector实现了DeferredImportSelector,所以它是解析@Configuration的最后一步。DeferredImportSelector可以和@Order搭配使用。AutoConfigurationImportSelector的意义在于引入其他包时,可以直接注入其他包的@Configuration,当然需要在其他包的resources文件夹下,新建META-INF目录,在META-INF目录下新建spring.factories文件,加入org.springframework.boot.autoconfigure.EnableAutoConfiguration = @Configuration标注的类的路径 ...

October 2, 2019 · 3 min · jiezi

SpringBoot自定义Spring-boot-Starter原理demo代码实现以及解决面试问题

github:https://github.com/Ccww-lx/Sp... 模块:spring-boot-starter-base-service SpringBoot的方便快捷主要体现之一starter pom,Spring Boot为我们提供了简化企业级开发绝大多数场景的starter pom, 只要使用了应用场景所需要的starter pom,只需要引入对应的starter即可,即可以得到Spring Boot为我们提供的自动配置的Bean。 然而,可能在很多情况下,我们需要自定义stater,这样可以方便公司内部系统调用共同的配置模块的时候可以自动进行装载配置。比如,很多公司将生产数据库的密码托管在公司的另外一个专门管理生产密码的系统上,公司每个系统需要使用的时候都需要调用其方法进行使用,现在可以通过starter自动配置的形式进行配置。 1. SpringBoot Starter源码分析 Q:@SpringBootApplication 注解中核心注解@EnableAutoConfiguration注解在starter起什么作用呢? @EnableAutoConfiguration源码分析: @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {};} 可以从源码看出关键功能是@import注解导入自动配置功能类AutoConfigurationImportSelector类,主要方法getCandidateConfigurations()使用了SpringFactoriesLoader.loadFactoryNames()方法加载META-INF/spring.factories的文件(spring.factories声明具体自动配置)。 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations;} ...

October 2, 2019 · 2 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

全栈之路微服务课程12Hystrix之初遇见

简介Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。 断路器刨析实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如15秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。 解决方案熔断模式 这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。隔离模式这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火烧光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。 限流模式上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。 降级降级与熔断紧密相关,熔断后业务如何表现,约定一个快速失败的 Fallback,即为服务降级代码实现加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>启动类加注解@EnableCircuitBreaker 控制器加实现 测试http://localhost:8010/movies/usersByFeign/1 { "id": 1, "username": "默认用户", "name": "默认用户", "age": 0, "balance": 1}监控持续不断地访问http://localhost:8010/movies/users/1 多次(至少20次),再访问http://localhost:8010/actuator/health,返现断路器已被开启。 { "status": "UP", "details": { "diskSpace": { "status": "UP", "details": { "total": 107374178304, "free": 15773237248, "threshold": 10485760 } }, "db": { "status": "UP", "details": { "database": "MySQL", "hello": 1 } }, "refreshScope": { "status": "UP" }, "discoveryComposite": { "status": "UP", "details": { "discoveryClient": { "status": "UP", "details": { "services": [ "shop-discovery-eureka-ha", "shop-consumer-movie" ] } }, "eureka": { "description": "Remote status from Eureka server", "status": "UP", "details": { "applications": { "SHOP-DISCOVERY-EUREKA-HA": 1, "SHOP-CONSUMER-MOVIE": 1 } } } } }, "hystrix": { "status": "CIRCUIT_OPEN", "details": { "openCircuitBreakers": [ "MovieController::findByIdByFeign" ] } } }}

September 20, 2019 · 1 min · jiezi

全栈之路微服务课程13Feign之Hystrix

前言默认Feign是不启用Hystrix的,需要添加如下配置启用Hystrix,这样所有的Feign Client都会受到Hystrix保护! 新增配置feign: hystrix: enabled: true提供Fallback@FeignClient(name = "microservice-provider-user", fallback = UserFeignClientFallback.class)public interface UserFeignClient { @GetMapping("/users/{id}") User findById(@PathVariable("id") Long id);}@Componentclass UserFeignClientFallback implements UserFeignClient { @Override public User findById(Long id) { return new User(id, "默认用户", "默认用户", 0, new BigDecimal(1)); }}获取原因@FeignClient(name = "shop-provider-user", fallbackFactory = UserFeignClientFallbackFactory.class)public interface UserFeignClient { @GetMapping("/users/{id}") User findById(@PathVariable("id") Long id);}@Component@Slf4jclass UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> { @Override public UserFeignClient create(Throwable throwable) { return new UserFeignClient() { @Override public User findById(Long id) { log.error("进入回退逻辑", throwable); return new User(id, "默认用户", "默认用户", 0, new BigDecimal(1)); } }; }}

September 20, 2019 · 1 min · jiezi

喜大普奔两个开源的-Spring-Boot-Vue-前后端分离项目可以在线体验了

折腾了一周的域名备案昨天终于搞定了。 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了。 1. 也曾经上过线其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了,以帮助小伙伴们更好的查看效果。但是那个是一台国外服务器,之所以购买国外服务器,主要是嫌国内备案麻烦,当然也有其他大家都懂的原因。 国外服务器有方便的地方,同时也有很多不便,例如网络不稳,随时有失联的风险。所以我在 2018 年年初,虽然把这两个项目都部署在服务器上,但是很多小伙伴的访问体验都不好,主要还是网络的问题。后来一段时间,经过几轮围剿与反围剿,这台服务器就彻底和松哥失联了。 失联之后,因为工作比较忙,我也就懒得去折腾了,所以导致微人事和 V 部落大家在很长一段时间内无法在线查看效果。 2. 重新上线最近因为有一些其他的计划,于是购买了阿里云服务,完事之后就是备案,所有东西都搞定之后,想着先把微人事和 V 部落部署起来,方便大家查看效果。 说干就干,我首先规划了两个二级域名: vblog.itboyhub.comvhr.itboyhub.com这两个二级域名分别用来部署 V 部落和微人事。 大家可以通过这两个地址查看效果: 微人事 V 部落 为了确保每位小伙伴都能看到完整的演示效果,防止有的小伙伴不慎把所有数据清空了,导致其他小伙伴啥都看不到,我只开通了演示账户的查询和部分字段的更新权限,因此大家在查看演示效果时,可能会有一些涉及到增删改的操作会执行失败,请勿见怪,将项目部署到本地运行之后,就可以查看完整效果了。 3. 技能树既然都写到这儿了,就和大家聊一聊这两个部署是怎么实现的。 3.1 部署方案选择大家知道前后端分离部署的时候,我们有两种不同的方案: 一种就是将前端项目打包编译之后,放到后端项目中(例如 Spring Boot 项目的 src/main/resources/static 目录下)另外一种则是将前端打包之后的静态资源用 Nginx 来部署,后端单独部署只需要单纯的提供接口即可。一般在公司项目中,我们更多的是采用后者。不过松哥这里部署为了省事,我采用了第一种方案。(以后抽空我会和大家聊聊第二种部署方案) 3.2 域名映射域名映射这块简单,登录阿里云后台,添加两个 A 记录即可。 3.3 启动 Spring Boot将微人事和 V 部落分别打包上传到服务器,这个过程应该就不用我多说了吧,然后分别启动这两个项目,两个项目的默认端口分别是 8081 和 8082,命令如下: nohup java -jar vblog.jar > vblog.log &nohup java -jar vhr.jar > vhr.log &将两个项目的运行日志分别写入到 vblog.log 和 vhr.log 文件中。 ...

September 20, 2019 · 2 min · jiezi

SpringBoot-Activiti6系列教程四流程部署

说明在上一章节中,介绍了如何基于bpmn2.0的xml文件发起流程和获取待办,其中流程文件和代码打包在一起,但实际项目中很少会把流程文件和代码一起打包部署,这样的话,每次流程更新或者发布新流程都需要重新部署应用,因此我们制定了以下部署方案: 提供流程部署接口,可以通过上传流程文件对流程进行部署。如果流程文件没有发生变化,不做新的部署,防止因为重新部署导致版本号上升。资源部署activit部署资源文件需要通过RepositoryService创建一个deployment,通过该deployment进行资源的部署,不单单是bpmn流程文件,activiti可以部署任何文件。 上传资源到activiti@Servicepublic class DeploymentService { @Autowired private RepositoryService repositoryService; /** * deploy resource * * @param name resource name * @param fin resource inputstream * @return */ public String deploy(String name, InputStream fin) { String deploymentId = repositoryService.createDeployment() .addInputStream(name, fin) .name(name) .key(name) .deploy() .getId(); return deploymentId; }}部署的时候指定部署的name和key,方便后续对部署进行进一步操作。 声明restController@RestControllerpublic class DeploymentController { @Autowired private DeploymentService service; @PostMapping(value = "/deploy") public String deploy(@RequestParam("file") MultipartFile file) { try { return service.deploy(file.getOriginalFilename(), file.getInputStream()); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("upload failed"); } }}这里以文件名作为部署的名称,可以根据实际情况指定名称。 ...

September 20, 2019 · 2 min · jiezi

全栈之路微服务课程10Feign之初遇见

Feign是什么Feign是一个受到Retrofit,JAXRS-2.0和WebSocket启发的Java到HTTP客户端绑定器。Feign的第一个目标是降低将Denominator统一绑定到HTTP API 的复杂性。Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器。RestTemplate与Feign对比 如何使用引入依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>创建feign Client@FeignClient(name = "shop-provider-user")public interface UserFeignClient { @GetMapping("/users/{id}") User findById(@PathVariable("id") Long id);}添加注解@EnableFeignClients@EnableFeignClients@SpringBootApplication//(scanBasePackages = {"com.dream.shop"})public class MovieApplication { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(MovieApplication.class, args); }}控制器调用@RequestMapping("/movies")@RestControllerpublic class MovieController { @Autowired private RestTemplate restTemplate; @Autowired private UserFeignClient userFeignClient; @GetMapping("/usersByFeign/{id}") public User findByIdByFeign(@PathVariable Long id) { return this.userFeignClient.findById(id); } @GetMapping("/users/{id}") public User findById(@PathVariable Long id) { // 这里用到了RestTemplate的占位符能力// User user = this.restTemplate.getForObject("http://localhost:8000/users/{id}", User.class, id); User user = this.restTemplate.getForObject("http://shop-provider-user/users/{id}", User.class, id); // ...电影微服务的业务... return user; }}

September 19, 2019 · 1 min · jiezi

全栈之路微服务课程9Ribbon深入刨析

内置规则RoundRobinRule:系统默认规则,也是用的较多的一种规则。通过简单的轮询服务列表来选择服务器,其他的规则在很多情况下仍然使用RoundRobinRule。AvailabilityFilteringRule:顾名思义,有效性过滤规则。该规则会忽略一下服务器:无法连接的服务器:在默认情况下,如果 3 次连接失败,该服务器将会被置为 “短路”的状态,该状态将持续 30 秒,如果再次连接失败,“短路”状态的持 续 时 间 将 会 以 几 何 级 增 加 。 可 以 通 过 修 改 niws.loadbalancer<clientName>.connectionFailureCountThreshold 属性,来 配置连接失败的次数。高并发数过高的服务器:如果连接到该服务器的并发数过高,也会被这个规则忽略,可以通过修改<clientName>.ribbon.ActiveConnectionsLimit 属性来设定最高并发数。WeightedResponseTimeRule:为每个服务器赋予一个权重值,服务器的响应时间 越长,该权重值就是越少,这个规则会随机 选择服务器,这个权重值有可能会决定 服务器的选择。ZoneAvoidanceRule:该规则以区域、可用服务器为基础,进行服务器选择。使用 Zone 对服务器进行分类,可以理解为机架或者机房。BestAvailableRule:忽略“短路”的服务器,并选择并发数较低的服务器。RandomRule:顾名思义,随机选择可用的服务器。RetryRule:含有重试的选择逻辑,如果使用 RoundRobinRule 选择服务器无法连 接,那么将会重新选择服务器。代码配置/** * 该类为Ribbon的配置类 * 注意:该类不能放在主应用程序上下文@ComponentScan所扫描的包中,否则配置将会被所有Ribbon Client共享。 */@Configurationpublic class RibbonConfiguration { @Bean public IRule ribbonRule() { // 负载均衡规则,改为随机 return new RandomRule(); }}属性配置(推荐)属性配置的优先级高于代码配置。 shop-provider-user: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule饥饿加载默认情况下Ribbon是懒加载的——首次请求Ribbon相关类才会初始化,这会导致首次请求过慢的问题,你可以配置饥饿加载,让Ribbon在应用启动时就初始化。 ribbon: eager-load: enabled: true # 多个用,分隔 clients: shop-provider-user

September 19, 2019 · 1 min · jiezi

全栈之路微服务课程8Ribbon初识

简介nginx:服务器端负载均衡ribbon: 客户端负载均衡Ribbon是Netflix发布的负载均衡器,它可以帮我们控制HTTP和TCP客户端的行为。只需为Ribbon配置服务提供者地址列表,Ribbon就可基于负载均衡算法计算出要请求的目标服务地址。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等——当然,为Ribbon自定义负载均衡算法也非常容易,只需实现IRule 接口即可。图解Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,选择其中一个服务提供者实例。下图展示了Ribbon与Eureka配合使用时的大致架构。 客服端配置 以shop-consumer-movie服务为例修改MovieApplication.java 修改调用方式目标服务改成了http://shop-provider-user/users/{id} ,也就是http://{目标服务名称}/{目标服务端点} 的形式,Ribbon会自动在实际调用时,将目标服务名替换为该服务的IP和端口。 @RequestMapping("/movies")@RestControllerpublic class MovieController { @Autowired private RestTemplate restTemplate; @GetMapping("/users/{id}") public User findById(@PathVariable Long id) { // 这里用到了RestTemplate的占位符能力// User user = this.restTemplate.getForObject("http://localhost:8000/users/{id}", User.class, id); User user = this.restTemplate.getForObject("http://shop-provider-user/users/{id}", User.class, id); // ...电影微服务的业务... return user; }}服务端多端口启动 访问http://localhost:8010/movies/users/1 多次,会发现两个user服务实例都会打印日志。

September 19, 2019 · 1 min · jiezi

在spring-boot中一种综合查询的新想法

在实际的开发中,会遇到这样的问题:我们在综合查询中,接收到了很多个参数,比如:pageAllOfCurrentUserBySpecification(Long districtId, Long departmentId, String name, String code, Pageable pageable),此方法一般的,会被其它多个方法来调用来实现多种查询功能。但如果此方法一旦发生参数变更,那么其它调用它的方法就需要全部跟着变更一边。 示例代码: public void a(Integer a, Integer b, Integer c) { // 这里是大家统一调用的方法,单元测试的时候,只测试它即可 } public void b(Integer a, Integer b) { a(a, b, null); } public void c(Integer b, Integer c) { a(null, b, c); }此时,a方法发生变更,需要增加1个参数d。那么a,b,c三个方法全部要变一边. public void a(Integer a, Integer b, Integer c, Integer d) { // 这里是大家统一调用的方法,单元测试的时候,只测试它即可 } public void b(Integer a, Integer b) { a(a, b, null, null); } public void c(Integer b, Integer c) { a(null, b, c, null); }本文旨在探索一种方案来解决此类问题,当a方法的参数发生变更时,不影响其它调用此方法的方法。 ...

September 10, 2019 · 2 min · jiezi

spring-boot-20x-21x-如何设置mysql56引擎为innodb

最近更新为spring boot 2.1.7后,遇到了一系列小的问题。本文阐述下spring boot对mysql引擎的支持。 解决方法spring: jpa: properties: hibernate: dialect: org.hibernate.dialect.MySQL55Dialect问题描述当我们配置spring.jap.hibernate.ddl-auto: create或是update等属性后,hibernate为我们自己动生成了数据表。但系统启动时在控制台中有报错,报错内容指明hibernate在给字段添加外键时产生了错误。经排查,错误产生的原因在于hibernate为我们自己动生成的表的引擎为MyISAM,而MyISAM并不支持外键。其实我们想要的引擎是Innodb。 尝试解决由于spring 1.x版本中,是不需要配置此项的,所以我首先来到了spring boot官方参考文档: https://docs.spring.io/spring-boot/docs/2.x.x.RELEASE/reference/html/common-application-properties.html具体使用时,请将2.x.x换成自己使用的版本,比如2.1.7。很遗憾,自2.0.0开始至2.1.8结束,我们以关键字dialect查询,并没有找到关于dialect的选项。而且我们查看jpa的配置项,也没有找到相关的记录: spring.data.jpa.repositories.bootstrap-mode=default # Bootstrap mode for JPA repositories.spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.spring.jpa.generate-ddl=false # Whether to initialize the schema on startup.spring.jpa.hibernate.ddl-auto= # DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Defaults to "create-drop" when using an embedded database and no schema manager was detected. Otherwise, defaults to "none".spring.jpa.hibernate.naming.implicit-strategy= # Fully qualified name of the implicit naming strategy.spring.jpa.hibernate.naming.physical-strategy= # Fully qualified name of the physical naming strategy.spring.jpa.hibernate.use-new-id-generator-mappings= # Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.spring.jpa.mapping-resources= # Mapping resources (equivalent to "mapping-file" entries in persistence.xml).spring.jpa.open-in-view=true # Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.spring.jpa.properties.*= # Additional native properties to set on the JPA provider.spring.jpa.show-sql=false # Whether to enable logging of SQL statements.除此以外,spring.DATASOURCE中的配置项目也未找到相关的配置信息。莫非,官方将其取消了? ...

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

基于-Spring-Cloud-GreenwichSR1-的微服务权限系统-FEBS-Cloud

FEBS Cloud 微服务权限系统FEBS Cloud是一款使用Spring Cloud Greenwich.SR1、Spring Cloud OAuth2和Spring Cloud Security构建的权限管理系统,前端(FEBS Cloud Web)采用vue element admin构建。FEBS意指:Fast,Easy use,Beautiful和Safe。该系统具有如下特点: 前后端分离架构,客户端和服务端纯Token交互;认证服务器与资源服务器分离,方便接入自己的微服务系统;微服务防护,客户端请求资源只能通过微服务网关获取;集成Spring Boot Admin,多维度监控微服务;集成Zipkin,方便跟踪Feign调用链;集成ELK,集中管理日志,便于问题分析;微服务Docker化,使用Docker Compose一键部署;提供详细的使用文档和搭建教程;前后端请求参数校验,Excel导入导出,代码生成等。文档与教程项目文档及手摸手搭建教程地址:https://www.kancloud.cn/mrbird/spring-cloud/1263679 系统架构 项目地址平台FEBS Cloud(后端)FEBS Cloud Web(前端)GitHubhttps://github.com/wuyouzhuguli/FEBS-Cloudhttps://github.com/wuyouzhuguli/FEBS-Cloud-Web演示地址http://49.234.20.223:9527 演示环境账号密码: 账号密码权限scott1234qwer注册账户,拥有查看,新增权限(新增用户除外)和导出Excel权限本地部署账号密码: 账号密码权限mrbird1234qwer超级管理员,拥有所有增删改查权限scott1234qwer注册账户,拥有查看,新增权限(新增用户除外)和导出Excel权限jane1234qwer系统监测员,负责整个系统监控模块服务模块FEBS模块: 服务名称端口描述FEBS-Register8001微服务注册中心FEBS-Auth8101微服务认证服务器FEBS-Server-System8201微服务子系统(资源服务器)FEBS-Server-Test8202微服务子系统(资源服务器)FEBS-Gateway8301微服务网关FEBS-Monitor-Admin8401微服务监控子系统Zipkin-Server8402Zipkin服务器FEBS-Config8501微服务配置子系统第三方模块: 服务名称端口描述MySQL3306MySQL数据库RabbitMQ5672RabbitMQ消息中间件Redis6379K-V缓存数据库Elasticsearch9200日志存储Logstash4560日志收集Kibana5601日志展示目录结构├─febs-auth ------ 微服务认证服务器├─febs-cloud ------ 整个项目的父模块│ └─docker compose ------ 存放docker compose文件│ ├─elk ------ ELK docker compose文件│ ├─febs-cloud ------ 聚合所有微服务子项目的docker compose文件│ └─third-part ------ 第三方服务(MySQL,Redis等)docker compose文件├─febs-common ------ 通用模块├─febs-config ------ 微服务配置中心├─febs-gateway ------ 微服务网关├─febs-monitor ------ 微服务监控父模块│ ├─febs-monitor-admin ------ 微服务监控中心│ └─zipkin-server ------ zipkin 服务├─febs-register ------ 微服务注册中心└─febs-server ------ 资源服务器 ├─febs-server-system ------- 资源服务器系统模块 └─febs-server-test ------ 资源服务器demo,演示如何整合自己的微服务系统系统截图 ...

September 9, 2019 · 1 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

Spring-Boot-Condition-注解组合条件你知道吗

上一篇文章 你应该知道的 @ConfigurationProperties 注解的使用姿势,这一篇就够了 介绍了如何通过 @ConfigurationProperties 注解灵活读取配置属性,这篇文章将介绍如何灵活配置 Spring Bean 写在前面当我们构建一个 Spring 应用的时候,有时我们想在满足指定条件的时候才将某个 bean 加载到应用上下文中, 在Spring 4.0 时代,我们可以通过 @Conditional 注解来实现这类操作 我们看到 @Conditional 注解接收的参数是 extends Condition 接口的泛型类,也就是说,我们要使用 @Conditional 注解,只需要实现 Condition 接口并重写其方法即可: 看到接口的 matches 方法返回的是 boolean 类型,是不是和我们自定义 validation annotation 有些类似,都是用来判断是否满足指定条件。另外注意看,以上注解和接口都在 org.springframework.context.annotation package 中 终于到了 Spring Boot 时代,在这个全新的时代,Spring Boot 在 @Conditional 注解的基础上进行了细化,无需出示复杂的介绍信 (实现 Condition 接口),只需要手持预定义好的 @ConditionalOnXxxx 注解印章的门票,如果验证通过,就会走进 Application Context 大厅 注解详解Spring Boot 对 @Conditional 注解为我们做了细化,这些注解都定义在 org.springframework.boot.autoconfigure.condition package 下 逐个打开这 13 个注解,我们发现这些注解上有相同的元注解: ...

September 9, 2019 · 2 min · jiezi

RequestBodyAdvice-和-ResponseBodyAdvice-全局处理输入输出

使用场景需要对项目中的所有输入进行前后空格的过滤替换一些特殊字符的输入解密一些关键性字段注入一些参数在请求方法的时候返回参数统一处理,如果后台返回空,统一返回成功信息身份证等特殊字符统一做 * 号处理等code主要就是用到了 RequestBodyAdvice 和 ResponseBodyAdvice 两个接口和一个注解 @ControllerAdvice 请求参数去空格package com.sanri.test.testmvc.config;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.serializer.SerializerFeature;import lombok.extern.slf4j.Slf4j;import org.apache.commons.io.IOUtils;import org.apache.commons.lang3.ObjectUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.core.MethodParameter;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpInputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Type;import java.util.Iterator;import java.util.Map;/** * 去掉前后空格和特殊字符 */@Slf4j@ControllerAdvicepublic class CustomRequestBodyAdvice implements RequestBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return true; } @Override public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException { return new CustomHttpInputMessage(httpInputMessage); } @Override public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } class CustomHttpInputMessage implements HttpInputMessage{ private HttpInputMessage origin; public CustomHttpInputMessage(HttpInputMessage httpInputMessage) { this.origin = httpInputMessage; } @Override public InputStream getBody() throws IOException { HttpHeaders headers = origin.getHeaders(); InputStream body = origin.getBody(); // 空参,get 请求,流为空,非 application/json 请求,不处理参数 MediaType contentType = headers.getContentType(); if(contentType == null){return body;} if(!contentType.isCompatibleWith(MediaType.APPLICATION_JSON)){return body;} if(body == null){return body;} String params = IOUtils.toString(body, "utf-8"); if(StringUtils.isBlank(params)){return body;} // 正式过滤 json 参数 Object parse = JSON.parse(params); if (parse instanceof JSONArray) { JSONArray jsonArray = (JSONArray) parse; trimJsonArray(jsonArray); } else if (parse instanceof JSONObject) { trimJsonObject((JSONObject) parse); } else { log.error("参数不支持去空格:" + parse+ " contentType:"+contentType); } return IOUtils.toInputStream(JSON.toJSONString(parse, SerializerFeature.WriteMapNullValue), "UTF-8"); } private void trimJsonObject(JSONObject jsonObject) { Iterator<Map.Entry<String, Object>> iterator = jsonObject.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Object> next = iterator.next(); String key = next.getKey(); Object value = next.getValue(); if (value instanceof JSONArray) { trimJsonArray((JSONArray) value); }else if(value instanceof JSONObject){ trimJsonObject((JSONObject) value); }else if(value instanceof String){ String trimValue = StringUtils.trim(ObjectUtils.toString(value)); next.setValue(filterDangerString(trimValue)); } } } private void trimJsonArray(JSONArray jsonArray) { for (int i = 0; i < jsonArray.size(); i++) { Object object = jsonArray.get(i); if(object instanceof JSONObject){ JSONObject jsonObject = jsonArray.getJSONObject(i); trimJsonObject(jsonObject); }else if(object instanceof String){ String trimValue = StringUtils.trim(ObjectUtils.toString(object)); jsonArray.set(i,trimValue); } } } @Override public HttpHeaders getHeaders() { return origin.getHeaders(); } private String filterDangerString(String value) { if(StringUtils.isBlank(value))return value; value = value.replaceAll(";", ";"); value = value.replaceAll("'", "‘"); value = value.replaceAll("<", "《"); value = value.replaceAll(">", "》"); value = value.replaceAll("\\(", "("); value = value.replaceAll("\\)", ")"); value = value.replaceAll("\\?", "?"); return value; } }}使用 ResponseBodyAdvice 处理返回空返回package com.sanri.test.testmvc.config;import com.alibaba.fastjson.JSONObject;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.lang.reflect.AnnotatedType;import java.lang.reflect.Executable;import java.lang.reflect.Type;/** * 可以定义空返回的时候返回正确的信息,如成功信息 */@RestControllerAdvicepublic class CustomResponseBodyAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { Executable executable = returnType.getExecutable(); AnnotatedType annotatedReturnType = executable.getAnnotatedReturnType(); Type type = annotatedReturnType.getType(); return JSONObject.parseObject("{\"result\":0}"); }}项目代码我弄了一个例子代码,关于 java 中每个工具的使用,如 rabbitmq,mysql,mybatis,springboot,springmvc 可以方便初学者,更方便我自己随时取用,github 地址https://gitee.com/sanri/example ...

September 8, 2019 · 2 min · jiezi

springmvcspringboot-全局异常处理和自定义异常

前言异常处理其实一直都是项目开发中的大头,但关注异常处理的人一直都特别少。经常是简单的 try/catch 所有异常,然后简单的 printStackTrace ,最多使用 logger 来打印下日志或者重新抛出异常,还有的已经有自定义异常了,但是还是在 controller 捕获异常,需要 catch(异常1 )catch(异常2) 特别繁琐,而且容易漏。 其实 springmvc 在 ControllerAdvice 已经提供了一种全局处理异常的方式,并且我们还可以使用 aop 来统一处理异常,这样在任何地方我们都只需要关注自己的业务,而不用关注异常处理,而且抛出异常还可以利用 spring 的事务,它只有在检测到异常才会事务回滚。 重要说明 下面的相关代码用到了 lombok ,不知道的可以百度下 lombok 的用途使用建造者模式统一异常处理这里使用 springmvc 的 ControllerAdvice 来做统一异常处理 import com.sanri.test.testmvc.dto.ResultEntity;import com.sanri.test.testmvc.exception.BusinessException;import com.sanri.test.testmvc.exception.RemoteException;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.ArrayUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.ArrayList;import java.util.List;@RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler { @Value("${project.package.prefix:com.sanri.test}") protected String packagePrefix; /** * 处理业务异常 * @param e * @return */ @ExceptionHandler(BusinessException.class) public ResultEntity businessException(BusinessException e){ printLocalStackTrack(e); return e.getResultEntity(); } @ExceptionHandler(RemoteException.class) public ResultEntity remoteException(RemoteException e){ ResultEntity parentResult = e.getParent().getResultEntity(); ResultEntity resultEntity = e.getResultEntity(); //返回给前端的是业务错误,但是需要在控制台把远程调用异常给打印出来 log.error(parentResult.getReturnCode()+":"+parentResult.getMessage() +" \n -| "+resultEntity.getReturnCode()+":"+resultEntity.getMessage()); printLocalStackTrack(e); //合并两个结果集返回 ResultEntity merge = ResultEntity.err(parentResult.getReturnCode()) .message(parentResult.getMessage()+" \n |- "+resultEntity.getReturnCode()+":"+resultEntity.getMessage()); return merge; } /** * 打印只涉及到项目类调用的异常堆栈 * @param e */ private void printLocalStackTrack(BusinessException e) { StackTraceElement[] stackTrace = e.getStackTrace(); List<StackTraceElement> localStackTrack = new ArrayList<>(); StringBuffer showMessage = new StringBuffer(); if (ArrayUtils.isNotEmpty(stackTrace)) { for (StackTraceElement stackTraceElement : stackTrace) { String className = stackTraceElement.getClassName(); int lineNumber = stackTraceElement.getLineNumber(); if (className.startsWith(packagePrefix)) { localStackTrack.add(stackTraceElement); showMessage.append(className + "(" + lineNumber + ")\n"); } } log.error("业务异常:" + e.getMessage() + "\n" + showMessage); } else { log.error("业务异常,没有调用栈 " + e.getMessage()); } } /** * 异常处理,可以绑定多个 * @return */ @ExceptionHandler(Exception.class) public ResultEntity result(Exception e){ e.printStackTrace(); return ResultEntity.err(e.getMessage()); }}统一返回值一般我们都会定义统一的返回,这样前端好做返回值的解析,像这样package com.sanri.test.testmvc.dto;import lombok.Data;import lombok.ToString;import java.io.Serializable;/** * 普通消息返回 * @param <T> */@Data@ToStringpublic class ResultEntity<T> implements Serializable { private String returnCode = "0"; private String message; private T data; public ResultEntity() { this.message = "ok"; } public ResultEntity(T data) { this(); this.data = data; } public static ResultEntity ok() { return new ResultEntity(); } public static ResultEntity err(String returnCode) { ResultEntity resultEntity = new ResultEntity(); resultEntity.returnCode = returnCode; resultEntity.message = "fail"; return resultEntity; } public static ResultEntity err() { return err("-1"); } public ResultEntity message(String msg) { this.message = msg; return this; } public ResultEntity data(T data) { this.data = data; return this; }}自定义异常自定义异常,就我目前的工作经历来看的话,异常一般就三种 。 ...

September 8, 2019 · 4 min · jiezi

spring-boot-使用Security-进行用户访问控制

前言spring security 可以对网站进行用户访问控制(验证|authentication)和用户授权(authorization)。两者也在springboot 手册中明说到: authentication (who are you?) and authorization (what are you allowed to do?)。用户授权结合OAuth进行api或者第三方接入控制授权(授权),本文使用security进行用户登录,验证用户合法性(验证)。 参考:1、官网,网站安全控制;2、博客,用户登录验证a;用户登录验证b 1、创建不受访问限制的项目1.1、初始化项目在Spring Initializr 或者编辑器中生成空白项目,maven依赖添加web和thymeleaf就可以了,如下图: 1.2、创建两个展示界面src/main/resources/templates/home.html<!doctype html><html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><head> <meta charset="UTF-8" /> <title>home</title></head><body> <h1>Home Page</h1> <p>click <a th:href="@{/hello}">here</a> to see a greeting.</p></body></html>希望通过上面这个界面跳转到 /hello,hello界面内容如下: src/main/resources/templates/hello.html<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Hello World!</title> </head> <body> <h1>Hello world!</h1> </body></html>页面创建完成后,配置路由控制界面跳转,通过配置文件方式添加路由: package com.noel.handbook.accesscontroll.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class MvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // TODO Auto-generated method stub registry.addViewController("/home").setViewName("/home"); registry.addViewController("/").setViewName("/home"); registry.addViewController("/login").setViewName("/login"); registry.addViewController("/hello").setViewName("/hello"); }}通过重写WebMvcConfigurer 的addViewControllers方法,添加四个路由,login界面在下面创建。到现在,可以运行项目,浏览器输入地址ip:端口/ 或者 ip:端口/home查看界面输出,界面之间和url地址之间可以随意跳转。 ...

September 8, 2019 · 3 min · jiezi

Spring-Boot整合JSP遇到的问题解决思路

虽然Spring Boot默认已经不支持JSP了,但是本着学习至上的原则,在多方查资料的情况下,进行了一番整合操作。 1 Maven依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- JSP 渲染引擎 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <!-- 如果是外部tomcat部署的话,provided属性需要开启 --> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>2 Controller代码@Controllerpublic class JspOnSpringBootController { // 从 application.yml 中读取配置,如取不到默认值为Hello Jsp @Value("${application.hello:Hello Jsp}") private String hello = "Hello Jsp"; /** * 默认页<br/> * @RequestMapping("/") 和 @RequestMapping 是有区别的 * 如果不写参数,则为全局默认页,加入输入404页面,也会自动访问到这个页面。 * 如果加了参数“/”,则只认为是根页面。 * 可以通过localhost:7070或者localhost:7070/index访问该方法 */ @RequestMapping(value = {"/","/index"}) public String index(Model model) { // 直接返回字符串,框架默认会去 spring.view.prefix 目录下的 (index拼接spring.view.suffix)页面 // 本例为 /WEB-INF/jsp/index.jsp model.addAttribute("name", "Landy"); model.addAttribute("hello", hello); return "index"; }}3 application.properties配置#service端口(Dev)server.port=7070spring.mvc.view.prefix = /WEB-INF/jsp/spring.mvc.view.suffix = .jspapplication.hello = hello jsp on spring boot4 Jsp页面代码<html><head> <title>JSP on Spring Boot</title></head><body> <h1 style="color: red">${name}, ${hello}</h1></body></html>5 运行按理说,以上完成后就可以运行顺利看到页面结果了,但是如果直接运行SpringApplication的main方法是不能看到结果的,报404. ...

September 7, 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

Spring-Boot-配置文件详解

SpringBoot是为了简化Spring应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程。一、准备前提为了让SpringBoot更好的生成数据,我们需要添加如下依赖(该依赖可以不添加,但是在 IDEA 不会有属性提示),该依赖只会在编译时调用,所以不用担心会对生产造成影响… <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency>二、自定义属性配置SpringBoot 主配置文件默认为application.yml或者application.properties,我更喜欢用前者,所以文章中基本以第一种为例读取自定义属性2.1 application.yml 自定义属性server: port: 8081original: config: title: OriginalConfig description: 主配置文件中属性2.2 新建OriginalConfig配置类该配置类用来映射我们在application.yml中的内容,这样一来我们就可以通过操作对象的方式来获得配置文件的内容了。@Data@Componentpublic class OriginalConfig { /** * 注入 application.yml 配置 * */ @Value("${original.config.title}") private String title; @Value("${original.config.description}") private String description; }这里,可以采用@ConfigurationProperties简化@Value注入值的写法,详见文末Github源码写法。2.3 ConfigTest.java测试2.3.1 测试方法testOriginalConfig @Autowired OriginalConfig originalConfig; /** * 读取自定义属性 */ @Test public void testOriginalConfig() { System.out.println("开始读取 application.yml 的自定义属性:"); System.out.println("读取配置信息:"); System.out.println("title: " + originalConfig.getTitle()); System.out.println("desc: " + originalConfig.getDescription()); System.out.println("application.yml 文件的属性读取完毕!"); }2.3.2 控制台打印如下:开始读取 application.yml 的自定义属性:读取配置信息:title: OriginalConfigdesc: 主配置文件中属性application.yml 文件的属性读取完毕!三、自定义文件配置3.1 定义一个名为myConfig.properties的自定义文件自定义配置文件的命名不强制application开头customize.config.title=CustomizeConfigcustomize.config.description=自定义配置文件中属性3.2 新建CustomizeConfig配置类该配置类用来映射我们在myConfig.properties中的内容,这样一来我们就可以通过操作对象的方式来获得配置文件的内容了。@Data@Component@PropertySource(value = "classpath:myConfig.properties", encoding = "utf-8")public class CustomizeConfig { /** * 注入 myConfig.properties 配置 * */ @Value("${customize.config.title}") private String title; @Value("${customize.config.description}") private String description;}@PropertySource :配置文件路径和编码格式2.3 单元测试 @Autowired CustomizeConfig customizeConfig; /** * 从 myConfig.properties 获取配置 */ @Test public void testCustomizeConfig() { System.out.println("开始读取 myConfig.properties 文件的属性:"); System.out.println("title: " + customizeConfig.getTitle()); System.out.println("desc: " + customizeConfig.getDescription()); System.out.println("myConfig.properties 文件的属性读取完毕!"); }控制台打印如下:开始读取 myConfig.properties 文件的属性:title: CustomizeConfigdesc: 自定义配置文件中属性myConfig.properties 文件的属性读取完毕!三、多环境化配置真实的应用中,常常会有多个环境(如:本地,开发,测试,正式),不同的环境数据库连接都不一样,这个时候就需要用到spring.profile.active,它的格式为application-{profile}.yml,这里的application为前缀不能改,{profile}是我们自己定义的。3.1 指定不同环境的路径通过server.servlet.context-path将不同环境指定不同的路径。application-local.ymltitle: local配置文件server: servlet: context-path: /localapplication-daily.ymltitle: daily配置文件server: servlet: context-path: /dailyapplication-gray.ymltitle: gray配置文件server: servlet: context-path: /grayapplication-production.ymltitle: production配置文件server: servlet: context-path: /production3.2 新增配置类EnvConfig.java@Data@Configurationpublic class EnvConfig { @Value("${title}") private String title;}3.3 测试方法 @Autowired EnvConfig envConfig; /** * 从 不同环境配置读取 */ @Test public void testEnvConfig() { System.out.println("开始读取 不同环境 文件的属性:"); System.out.println("title: " + envConfig.getTitle()); System.out.println("myConfig.properties 文件的属性读取完毕!"); }3.4 激活某个环境配置在application.yml配置文件中指定环境,例如指定gray环境: ...

August 21, 2019 · 1 min · jiezi

第一个SpringBoot项目

第一个SpringBoot项目SpringBoot为我们提供了一系列的依赖包,所以需要构建工具的支持:Maven或Gradle。博主更习惯使用Maven,暂时学习教程基本采用所Maven与IntelliJ IDEA;新公司使用的是Gradle,所以后续可能会使用Gradle。框架基于目前最新的SpringBoot 2.1.1。一、创建项目第一个项目,先做一个简单的demo,能跑起来即可。 1.点击 File -> Project;2.选择Spring Initializr;第一次用,建议选择的是Spring Initializr(官方的构建插件,需要联网),还有Maven或Gradle可选,后续可选用。选择SDK版本,本项目用的是JDK 1.8,然后点击Next。 3.填写项目基本信息 Group:组织ID,一般分为两段,第一段为域,第二段为公司名称。域又分为org、com、cn等等,其中org为非营利组织,com为商业组织;Artifact:唯一标识符,一般是项目名称;Description:项目描述。4.选择包 选择最新的SpringBoot版本,最新的为2.1.1,即图中version;Spring Initializr为我们提供了很多的选项,不同的选项有不同的作用,第一个项目只需要依赖Web -> Web就可以了,选择好依赖包之后点击Next->Finish 。5.目录结果- src -main -java -package #主函数,启动类,运行它如果运行了 Tomcat、Jetty、Undertow 等容器 -SpringbootApplication -resouces #存放静态资源 js/css/images 等 - statics #存放 html 模板文件 - templates #主要的配置文件,SpringBoot启动时候会自动加载application.yml/application.properties - application.yml #测试文件存放目录 -test # pom.xml 文件是Maven构建的基础,里面包含了我们所依赖JAR和Plugin的信息- pom6.pom.xml 依赖注意的是版本要选择RELEASE,稳定版本BUG少<?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"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.1.1.RELEASE</version> </parent> <modelVersion>4.0.0</modelVersion> <!--Group:组织ID,一般分为两段,第一段为域,第二段为公司名称。域又分为org、com、cn等等,其中org为非营利组织,com为商业组织;--> <!--Artifact:唯一标识符,一般是项目名称;--> <!--Description:项目描述。--> <groupId>cn.van</groupId> <artifactId>springboot-demo</artifactId> <version>1.0-SNAPSHOT</version> <description>第一个SpringBoot项目示例</description> <dependencies> <!-- 测试包,当我们使用 mvn package 的时候该包并不会被打入,因为它的生命周期只在 test 之内--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- web 包,默认就内嵌了Tomcat 容器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <!-- 编译插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>7.主函数入口一个项目中千万不要出现多个main函数,否在在打包的时候spring-boot-maven-plugin将找不到主函数(主动指定打包主函数入口除外…) ...

August 21, 2019 · 1 min · jiezi

springbootplus-V121-发布-文件上传下载和静态资源访问

[V1.2.1-RELEASE] 2019.08.21⭐️ New Features文件上传保存到服务器指定目录文件下载访问上传的图片等资源启用项目静态资源访问,可访问static/templates目录下资源⚡️ Optimizationstatic资源访问:http://localhost:8888/static/welcome.htmltemplates资源访问:http://localhost:8888/templates/springbootplus.html上传swagger:http://localhost:8888/swagger-ui.html#!/upload-controller/uploadUsingPOST上传后,图片文件访问:http://localhost:8888//resource/201908210134467.png图片自定义控制访问:http://localhost:8888/image/201908210134467.png???? Added/ModifiedAdd UploadController 上传控制器Add DownloadController 下载控制器Add ImageController 图片访问控制器Add ResourceInterceptor 资源拦截器Add welcome.html 在static目录下Add springbootplus.html 在templates目录下Add ContentTypeUtil 文件类型工具Add mime-type.properties 文件类型自定义拓展配置Add UploadUtil 上传工具类,UploadFileNameHandle 文件名称回调接口,DefaultUploadFileNameHandleImpl 默认文件名称实现类Add DownloadUtil 下载工具类Modify WebMvcConfig 注册资源拦截器,项目静态资源访问配置Modify SpringBootPlusConfig 创建 ResourceInterceptor 资源拦截器Modify SpringBootPlusInterceptorConfig 添加 resourceConfig 资源拦截器配置Modify SpringBootPlusProperties 添加 uploadPath,resourceAccessPath,resourceAccessPatterns,resourceAccessUrl属性Modify application.yml, application-local.yml 添加文件上传/下载配置Modify mysql_spring_boot_plus.sql 添加创建数据库语句,如果不存在,则创建???? Bug Fixes拦截器exclude-path,include-path字符串配置问题,已修改为数组接收String[] excludePath,String[] includePath???? Documentationmime-type大全???? Dependency UpgradesUpgrade to springboot 2.1.7.RELEASE

August 21, 2019 · 1 min · jiezi

专访宜信梁鑫回归架构本质重新理解微服务

本期专访宜信开发平台(SIA)负责人梁鑫,与大家一起聊聊微服务架构及其在企业落地应用的策略。 第一部分:微服务的诞生、演变以及应用策略 记者:近几年来,微服务架构设计方式被提出并在越来越多的企业中得以实践和落地,但对于刚开始接触微服务的人来说,还是不知道要从哪些方面开始了解。您能否结合软件架构的发展历史,聊聊微服务的发展与特征。 梁鑫:微服务本质上是一种架构的风格,如果要了解微服务,我认为需要先了解整个架构的发展脉络。 软件架构,总是在不断的演进中。如果把时间退回到二十年前,当时企业级领域研发主要推崇的还是C/S模式,PB、Delphi这样的开发软件是企业应用开发的主流。 随着时间的推移,我们发现标准化的客户端存在一些弊病,比如我有一千个终端,升级版本需要每一台终端都升级,这是非常麻烦的。然后,企业应用研发开始向互联网学习,把浏览器作为客户端来使用,就可以避免这个问题。因此,基于浏览器的B/S架构开始渐渐流行起来。 刚开始的时候是ASP,之后又出现了JSP,因为Java的预编译模式,让性能有了非常大的提升,随后基于Java语言的J2EE架构就变得越来越流行。至此,架构经历了从传统的C/S模式到B/S模式的转变。 B/S架构初期基本都是单体架构,各个系统比较独立,他们之间往往不需要进行交互,即使存在一些交互,也大多是数据层面的。那个阶段ETL工具发展得很快,就是为了解决这样的数据孤岛问题。 随着企业应用越来越多,系统之间相互的关系也越来越密切,系统之间实时交互访问的需求也越来越多。这个时候工程师们发现,不管是什么语言开发的软件,基本都支持一种叫做XML的语言,于是提出一种实时交互的技术解决方案:通过XML语言来进行企业应用系统之间的远程调用。由此,SOA的概念被提了出来,WebService开始流行。 当第二波互联网浪潮来临后,很多公司为了适应更加灵活的业务发展,用基于HTTP协议和Restful的架构风格替代了原先笨重的WebService,简洁清晰的JSON替代了XML。同时SOA架构中常常采用服务总线技术,无疑是给系统架构增加了一个异常麻烦的瓶颈。如果使用注册和发现的机制,让服务进程之间直接进行调用,更适合企业应用的发展。这就是微服务架构从技术方面来说的历史脉络。 在《微服务设计》中界定微服务有两个基本原则:松耦合&高内聚。即“把因相同因素变化的事情聚集在一起,把因不同因素变化的事情区隔开来。”至于微服务大小的划分并没有统一的标准,通俗地说,就是你觉得它的大小差不多,同时符合“松耦合&高内聚”的原则就可以。 微服务有很多的好处,大致列举一些。 异构:微服务可以帮助我们轻松采用不同的技术,并且理解这些新技术的好处。尝试新技术通常伴随着风险,但如果把服务切得很小了,总会存在一些点让你可以选择一个风险最小的服务采用新技术,并降低风险。弹性:很明显,微服务可以很好地处理服务不可用和功能降级的问题,因为它可以形成很多个节点。隔离:微服务架构将系统分解为独立运行单元,给系统带来更好的隔离性,独立的微服务在发生异常时更容易定位和隔离问题,隔离性也是服务扩展性的基础。扩展:庞大的单体服务只能作为一个整体进行扩展,即使系统中只有一小部分模块存在性能问题,也需要对整个系统进行扩展。而微服务架构可以根据性能需要对不同的模块进行水平扩展。部署简单:在微服务架构中,各个服务的部署是独立的,这样就可以更快地对特定部分的代码进行部署。服务出现问题也更容易快速回滚,同时敏捷的交付和部署带来了更好的业务需求响应体验。灵活:在微服务架构中,系统会开放很多接口供外部使用,当情况发生改变时,可以使用不同的方式构建应用。把单体应用分解成多个微服务,可以达到可复用、可组合的目的。记者:据悉,您之前发表过一篇文章“公司为什么需要建立一套统一的开发框架?”,您认为公司建立统一开发框架是为了解决什么问题? 梁鑫:这是一个仁者见仁智者见智的问题,每个人的出发点都不一样,有的人可能主张需要统一,有的人则可能排斥统一,结合我的经历和实践来看,我认为公司是需要建立统一开发框架的。 近十年,互联网的发展颠覆了很多传统行业,很多新兴公司如雨后春笋般的冒了出来,它们的业务增长非常快,公司规模也越来越大。这得益于中国经济的高速增长和互联网的快速发展。但这种高速的发展过程中伴随而来的是不可忽视的弊端: 弊端一:自我繁衍在公司快速的发展过程中,往往会出现这样一个链条。新增一块业务 —> 招聘一位高级技术人员 —> 围绕这位同事组建一支技术团队 —> 该业务基本由这只团队负责,然后就形成了一个闭环。当需要跟其他业务进行交互时,经常是技术负责人之间自行决定。这就形成了自我繁衍的状态。 弊端二:管控壁垒随着业务规模的快速发展,这个团队很快形成了一个部门,团队决策者通常会从自身利益考量,希望尽量减少对外部门的依赖,无论是技术选型、规范建立、组件选取、运行环境都能够自行掌控。 弊端三:断崖效应当这样的技术氛围一旦形成,单个员工对单个项目的影响就会变的非常巨大。一个产品经常会因为一两个核心员工的离职难以为继,最后不得不重新开发新的产品。 弊端四:资源浪费当每个团队都在试图构建自己完整的研发流程时。中间的技术研究、产品研发、运维管理就会出现非常多的资源浪费。 弊端五:难以考核怎么衡量一个川菜厨师和一个鲁菜厨师谁更优秀?当每个团队都是一个闭环,采用不同技术栈、不同的技术组件、不同的维护方式和规范时,已经无法从产出效率来判断一个团队的绩效,KPI 指标也就非常难以设立。 建立一套公司级的统一的开发平台可以有效解决上述问题。从技术层面来讲,如果可以形成公司级别的统一开发平台,会在实际的生产过程中带来非常大的收益。 首先,避免了重复性技术研究,节约了人力成本。在项目组之下构建一个基础的开发架构平台,把技术共性问题提炼出来,交给一个专门团队负责处理,让项目组把精力投入到业务中。其次,标准化了技术规范,提升了产品项目质量。做工程要千人一面,而不要千人千面。采用统一的开发平台后,在技术栈、技术组件、技术实现方案,甚至在代码规范上,就能形成标准化的技术输出模式,标准化带来的效果不仅仅是开发效率的快速提升,还有产品质量的大幅提升。再次,可以进行技术沉淀,提升公司整体的技术能力,避免陷入一个人的能力决定一个项目。技术的进步来源于不断的技术积累和沉淀,建立公司级别的统一开发框架(平台),项目团队基于该平台进行自身项目的研发,不再需要关注于底层技术实现,只需要关注业务即可。而且,专注于平台的同事为了更好地满足项目组的技术需求,对平台进行不断的改进,从而达到技术积累和沉淀的目标。最后,可以对研发的投入和产出进行衡量,对研发团队进行有效管理和考核。当基于同一开发平台的标准化技术规范建立起来后,对业务功能的代码实现就可以进行相对有效的评估和考量,可以避免因为技术实现差异而出现的种种问题。这对KPI的制定和考核是一个巨大的帮助。我从前年提出这样的一个思想,通过一年多的努力,已经在公司有了一定的成果。我们的统一开发平台定位于技术层面,其主要目的是为统一公司内相关产品研发和项目实施使用的技术架构和开发工具,有效提高统一技术支持力度,形成持续的技术积累手段,提升技术人员的利用率并降低对人员的依赖性,最终提升软件的规模化、流水线式的生产能力。 记者:最近“Spring Boot”、“Spring Cloud”等词总被提及,这些新的框架集合方案与传统的微服务框架相比有哪些优势?结合您的经验来看,您认为微服务未来的发展走向可能是什么? 梁鑫:我是公司内部较早研究Spring Cloud 技术栈的人,也是Spring Cloud中国社区的成员。Spring Cloud是在2017年一跃成为最流行的微服务开发框架。但是,这里有一个需要辩证看待的问题。“不是说使用了Spring Cloud框架就实现了微服务架构,具备了微服务架构的优势”,正确的理解应该是“使用Spring Cloud框架开发微服务架构系统,使系统具备微服务架构的优势。”Spring Cloud之所以能从其他框架中脱颖而出成为最火的框架,得益于其本身体系的完整性。这一点通过下图Spring Cloud、Dubbo和ServiceComb的对比可以比较直观地了解到。 另外,Spring在中国有广泛的群众基础,我也比较推崇这种“约定大于配置”的研发思想,不需要完全依赖标准化的东西。 我不敢妄谈微服务架构的未来走向。立足当下,我认为目前Spring Cloud+Docker容器化的技术是用于微服务架构的一个比较好的选择。我比较认可一个很有趣的说法是“基因架构”,意思是:架构从诞生之初就是为了改变的,所以你的架构越容易改变就越好。我觉得架构的未来会向这条路发展。 我们的统一开发平台的建设就是基于Spring Cloud技术栈。 记者:今年在软件研发行业比较热门的话题是“中台”,在架构层面也有人提出来要做微服务中台,对此您怎么看? 梁鑫:去年一个综艺节目带火了一句话,“盘它”。节目里有一句 “干干巴巴的,麻麻赖赖的,一点都不圆润,盘他!”。 后来说到什么就盘什么,也不管是什么东西,能不能握在手中,反正盘就是了。听起来是不是特别有魔性,然后就有了“万物皆可盘”这个段子。这本身其实只是一种调侃的讲法,也并不会真的有人看到什么就盘什么。有意思的是任何事情都可以再认真往深处想一想,你会不会也犯一些看似荒唐的错误呢?今年技术圈最火的一个名词就是“中台”,套用到这儿就变成了“万物皆可中台”,一个名词到处套,我认为很多公司应该避免盲目跟风,让“中台”成为名词陷阱。 面对一个新的技术或趋势,我们要先了解其来源和根本。中台的来源需要回溯到阿里。2015年阿里巴巴集团启动了中台战略,目标是要构建符合互联网大数据时代的,具有创新性、灵活性的‘大中台、小前台’的机制,即作为前台的一线业务会更敏捷、更快速地适用瞬息万变的市场,而中台将集合整个集团的运营数据能力、产品技术能力,对各前台业务形成强有力的支撑. 那阿里集团为什么要建立一个‘大中台、小前台’的架构呢?《企业IT架构转型之道——阿里巴巴中台战略思考与架构实战》一书对此有详细介绍。从阿里共享业务事业部的发展史说起,起初,阿里只有一个淘宝事业部,后来成立了天猫事业部,此时淘宝的技术团队同时支撑着这两个事业部。当时的淘宝和天猫的电商系统像我们很多大型企业的一样是分为两套独立的烟囱式体系,两套体系中都包含的有商品、交易、支付、评价、物流等功能。因为上述原因,阿里集团又成立了共享业务事业部,其成员主要来自之前的淘宝技术团队,同时将两套电商业务做了梳理和沉淀,将两个平台中公共的、通用的业务功能沉淀到共享事业部,避免重复建设和维护。 作为一个拥有10多年编程经验的老兵,我经常思考的一个问题就是系统发展的规律,透过其形领会其意,回顾架构的发展,我认为可以总结出一点:“快”。当然这个快是有前提的,比如正确率、资源限制,要在稳定、尽量减少资源消耗的情况下求快。 “快”可以再次分解,从开发的角度来看,编写代码要快、开发要快、功能测试要快、环境部署要快、服务启停要快;从生产的角度来看,程序运行的速度要快、高并发之下还是要快等。 微服务架构之所以流行,因为把服务拆小了,可以高度复用,不用经常编写和修改代码,节省了非常多的时间;容器化技术之所以流行,因为容器化技术可以使得生产环境和测试环境一致,节省了大量的环境部署时间、减少了出错的可能性,还可以随意增加容器节点,增强业务处理能力,保证高并发下的快速响应。分布式架构也是如此,微服务架构其实就是分布式架构的一种演化。万变不离其宗,都是追求“快”。 回到“中台”这个话题,建设中台的目标是避免重复建设和维护,快速响应需求。后台和平台的系统比较稳定,一般不轻易发生变化,而且从稳定性考虑,应该尽量减少后台和平台系统更新的次数,前端系统因为要适用用户的需求而不断变化,这样前台和后台在对接时就产生了一个求变一个求不变的矛盾,这时我们希望在两者之间建立一个中间平台,把前端后台可重复利用的东西集中到这个中间平台来,重新封装组合对外提供服务,这是符合“快”的思想的。 这是中台的来源和根本,企业在建设中台之前,一定要先了解这些,看所要建设的中台是否符合“避免重复建设和维护”的思想,是否符合“快”的原则。 第二部分:微服务在业务中的应用需要解决的关键问题 记者:宜信在今年开源了微服务任务调度平台SIA-TASK,这个平台在宜信技术团队内部有广泛的应用,开源后也得到了很多开发者的支持。您能否介绍一下这个平台的设计思路以及核心功能?(设计开发这个平台想要解决什么问题) 梁鑫:前面谈到中台,其实我认为“中台”只是一个名称而已,只要符合“避免重复建设和维护”和“快”两个原则,叫什么都可以,比如我们的微服务调度平台SIA-TASK,就是一个很像中台的系统。 介绍SIA-TASK的设计思想之前,我先介绍一下它的背景。无论是互联网应用或者企业级应用,都充斥着大量的批处理任务。常常需要一些任务调度系统帮助开发者解决问题。随着微服务化架构的逐步演进,单体架构逐渐演变为分布式、微服务架构。很多原先的任务调度平台已经不能满足业务系统的需求,于是出现了一些基于分布式的任务调度平台。这些平台各有其特点,也各有不足之处,比如不支持任务编排、与业务高耦合、不支持跨平台等问题,不符合新一代微服务架构的需求,因此我们开发了微服务任务调度平台(SIA-TASK)。 SIA-TASK 使用 SpringBoot 体系作为架构选型,基于Quartz及Zookeeper进行二次开发,支持相应的特性功能,SIA-TASK 的逻辑架构图如下图所示: ...

August 20, 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

Spring-cloud-一步步实现广告系统-20-系统运行测试

系统运行经过长时间的编码实现,我们的主体模块已经大致完成,因为之前我们都是零散的对各个微服务自行测试,接下来,我们需要将所有的服务模块进行联调测试,Let's do it. 清除测试数据&测试文件我们在实现各个服务的过程中,添加了不少的测试文件和测试数据,为了不影响我们最终的展示效果,我们先将之前的历史数据清理掉。 drop database advertisement;依然使用flyway 添加我们的测试数据: INSERT INTO `ad_user` VALUES (10,'Isaac','B2E56F2420D73FEC125D2D51641C5713',1,'2019-08-14 20:29:01','2019-08-14 20:29:01');INSERT INTO `ad_creative` VALUES (10,'第一个创意',1,1,720,1080,1024,0,1,10,'https://www.life-runner.com','2019-08-14 21:31:31','2019-08-14 21:31:31');INSERT INTO `ad_plan` VALUES (10,10,'推广计划名称',1,'2019-11-28 00:00:00','2019-11-20 00:00:00','2019-11-19 20:42:27','2019-08-14 20:57:12');INSERT INTO `ad_unit` VALUES (10,10,'第一个推广单元',1,1,10000000,'2019-11-20 11:43:26','2019-11-20 11:43:26'),(12,10,'第二个推广单元',1,1,15000000,'2019-01-01 00:00:00','2019-01-01 00:00:00');INSERT INTO `ad_unit_district` VALUES (10,10,'陕西省','西安市'),(11,10,'陕西省','西安市'),(12,10,'陕西省','西安市'),(14,10,'山西省','阳泉市');INSERT INTO `ad_unit_hobby` VALUES (10,10,'爬山'),(11,10,'读书'),(12,10,'写代码');INSERT INTO `ad_unit_keyword` VALUES (10,10,'汽车'),(11,10,'火车'),(12,10,'飞机');INSERT INTO `relationship_creative_unit` VALUES (10,10,10);导出测试索引文件可参考 全量索引传送门 ,或者下载源码github传送门 / gitee传送门 ,运行mscx-ad-db项目,然后执行 http://localhost:7002/ad-db/export/plan。 开发自测 Unit Test一个合格的开发人员是绝对不能容忍自己的代码存在傻~ bug 存在的,但是个人总会有犯错的时候,那么我们要怎么避免此类非业务发展导致的基础问题呢,这时候,开发的UT就显得非常Important了。 广告投放系统测试我们来编写投放系统的单元测试,如下图:单元测试模块的目录结构与我们的正式项目结构保持一致,如果你需要给单元测试编写特例化配置,把我们的application.yml配置文件copy到UT中就可以了,这里就不做赘述。 ...

August 19, 2019 · 2 min · jiezi

springboot-217-mysql56-弃用-Calendar类型字段

原因:在使用Calendar做为字段类型时,每进行一次findById()操作返回的数据的值都比实际值要大一点。更新后再调用查询,还会再大一点。也就是说:如果我们用Calendar做为字段类型,那么该字段会在程序运行时会静悄悄的增大。 代码准备示例代码很简单:我们只需要准备两个类,一个实体,一个数据访问对象。 Student.java package com.example.demo;import org.hibernate.annotations.CreationTimestamp;import javax.persistence.*;import java.sql.Timestamp;import java.util.Calendar;@Entitypublic class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @CreationTimestamp private Calendar applyTime; @CreationTimestamp private Timestamp applyTimestamp; // 省略构造函数及setter/getter}为了测试代码更简单,我们为applyTime及applyTime加入CreationTimestamp注解。让其自动生成。 删除自动时间戳并不会影响测试结果package com.example.demo;import org.springframework.data.repository.CrudRepository;public interface StudentRepository extends CrudRepository<Student, Long> {}测试代码package com.example.demo;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest@RunWith(SpringRunner.class)public class StudentRepositoryTest { private static final Logger logger = LoggerFactory.getLogger(StudentRepositoryTest.class); @Autowired StudentRepository studentRepository; @Test public void test() { Student student = new Student(); studentRepository.save(student); Student student1 = studentRepository.findById(student.getId()).get(); System.out.println(student1.getApplyTime().getTimeInMillis()); System.out.println(student1.getApplyTimestamp().getTime()); }}结果如下: ...

August 18, 2019 · 1 min · jiezi

分布式SpringBootSpringCloudEureka

环境Java version 1.8SpringBoot version 2.1.7搭建注册中心 Eureka-serverpom.xml 依赖如下所示:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency></dependencies>配置 Eureka application.properties# 应用启动端口server.port=8090# 注册中心管理中的 应用名称spring.application.name=eureka-server# 登陆注册管理中的的账号密码spring.security.user.roles=SUPERUSERspring.security.user.name=eurekaspring.security.user.password=123456# 是否把自己注册到注册中心eureka.client.register-with-eureka=true# 是否从eureka上来获取服务的注册信息eureka.client.fetch-registry=falseeureka.instance.hostname=localhosteureka.client.serviceUrl.defaultZone=http://eureka:123456@localhost:8090/eureka启动注册中心 启动后访问(http://127.0.0.1:8090) 登陆界面 2. 输入设置的账号密码(user:eureka pwd:123456)3. 进入注册中心页面 需要注意的是 需要处理下CSRF不然服务提供者注册不进来 启动入口文件如下EurekaServiceApplication.java@EnableEurekaServer@SpringBootApplicationpublic class EurekaServiceApplication { public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); } /** * 忽略 uri /eureka/** 的CSRF检测 */ @EnableWebSecurity static class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/**"); super.configure(http); } }}简单的注册中心已经搭建完成搭建服务提供者 Provider-service首先还是配置文件 application.properties服务端口server.port=8081# 服务应用名称spring.application.name=provider-ticket# 是否允许使用ip连接eureka.instance.ip-address=true# 注意 http://用户名:密码@主机:端口/eureka 需要与服务中心里配置的一样eureka.client.serviceUrl.defaultZone=http://eureka:123456@localhost:8090/eureka启动 项目后会自动把服务注册到配置的服务中心以下是我做的测试代码 ...

August 17, 2019 · 2 min · jiezi

There-is-no-PasswordEncoder-mapped-for-the-id-null

spring-boot 1.5.3 升级到 2.1.7 出现上述错误,查看MAVEN引用信息,引用的spring security版本为5.1.16,其官方文档地址为:https://docs.spring.io/spring... 原理猜想报错的代码在这: package org.springframework.security.crypto.password;public class DelegatingPasswordEncoder implements PasswordEncoder { @Override public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) { String id = extractId(prefixEncodedPassword); throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\""); }}根据异常排查,大概的思想是这样: 获取密码获取默认加密、密码匹配对象1.1 获取获取的加密类型(加密前缀)1.2 根据类型找算法1.3 没找到算法,则调用默认算法1.4 默认算法代码如上,抛出异常: 第1.1步我给几个例子,帮助学习: 由密码{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG获取到的加密类型为bcrypt.由密码{noop}password获取到加密类开地为noop由密码{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0中获取到的加密类型为sha256。具体的出错的逻辑是这样的: 获取到了密码,比如为123456.获取默认加密、密码匹配对象:DelegatingPasswordEncoder1.1 spring尝试从123456中,获取一个加密前缀的东西。但获取的值为null。1.2 没有找到算法,则调用默认算法,此时默认对象为:UnmappedIdPasswordEncoder1.3 运行对象UnmappedIdPasswordEncoder的matches算法1.4 抛出异常。 解决问题spring security支持的列表如下: String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder()); encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); encoders.put("scrypt", new SCryptPasswordEncoder()); encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1")); encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());使用了预制算法可以将数据库中密码更新为{算法前缀}原密码,来进行升级。新增数据时,密码字段也要加入前缀。 ...

August 17, 2019 · 2 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

Spring-Boot-2x十三你不知道的PageHelper

PageHelper说起PageHelper,使用过Mybatis的朋友可能不是很陌生,作为一款国人开发的分页插件,它基本上满足了我们的日常需求。但是,我想去官方文档看看这个东西配合Spring Boot进行使用的时候,发现了这个: 所以花了一个晚上的时间,研究了一下合理的怎么玩这个。 快速入门如果你想在一个Spring Boot项目中快速进行一次分页操作,只需要两步即可: 导入Maven这里我导入的是官方最新的: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version></dependency><dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version></dependency><dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.10</version></dependency>使用 // 只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页!!!! PageHelper.startPage(1, 10); return PageInfo.of(userService.findAll());没错,只需要两句代码即可达到我们想要的分页效果 进阶玩法如果,你仅仅是想简单的使用分页功能,那么这篇文章到这里对你而言来说就已经结束了,但是作为一个程序员,你会仅仅满足于初级的玩儿法吗?不!你不会!所以,接着往下看~ 从文档中,我们可以看出,作者给我们提供了很多的参数供我们配置: helperDialect,offsetAsPageNum,rowBoundsWithCount,pageSizeZero,reasonable,params,supportMethodsArguments,autoRuntimeDialect,closeConn等等,我们可以通过配置这些属性来获得更为强大的效果~ 这里需要说明一点,网上的教程大部分是让我们在xml或者代码中配置,其实如果你使用的是springboot,干嘛要舍近求远呢,我们可以直接在Spring boot 的配置文件application.yml中进行配置: pagehelper: # dialect: ① # 分页插件会自动检测当前的数据库链接,自动选择合适的分页方式(可以不设置) helper-dialect: mysql # 上面数据库设置后,下面的设置为true不会改变上面的结果(默认为true) auto-dialect: true page-size-zero: false # ② reasonable: true # ③ # 默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。(一般用不着) offset-as-page-num: false # 默认值为 false,RowBounds是否进行count查询(一般用不着) row-bounds-with-count: false #params: ④ #support-methods-arguments: 和params配合使用,具体可以看下面的讲解 # 默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 auto-runtime-dialect: false # ⑤ # 与auto-runtime-dialect配合使用 close-conn: true # 用于控制默认不带 count 查询的方法中,是否执行 count 查询,这里设置为true后,total会为-1 default-count: false #dialect-alias: ⑥①:默认情况下会使用 PageHelper 方式进行分页,如果想要实现自己的分页逻辑,可以实现 Dialect(com.github.pagehelper.Dialect) 接口,然后配置该属性为实现类的全限定名称。(这里不推荐这样玩,毕竟你用了别人的插件,干嘛还要多此一举呢?) ...

August 8, 2019 · 2 min · jiezi

Spring-Boot-2x十二Swagger2的正确玩法

Swagger2简介简单的来说,Swagger2的诞生就是为了解决前后端开发人员进行交流的时候API文档难以维护的痛点,它可以和我们的Java程序完美的结合在一起,并且可以与我们的另一开发利器Spring Boot来配合使用。 开始使用第一步:导入POM文件 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- 这里使用 swagger-bootstrap-ui 替代了原有丑陋的ui,拯救处女座~ --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.0</version> </dependency>#### 第二步:添加配置类 我们需要新增一个Swagger2Config 的配置类: /** * Swagger2 配置类 * @author vi * @since 2019/3/6 8:31 PM */@Configurationpublic class Swagger2Config { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("indi.viyoung.viboot.*")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("viboot-swagger2") //标题 .description("Restful-API-Doc") //描述 .termsOfServiceUrl("https://www.cnblogs.com/viyoung") //这里配置的是服务网站,我写的是我的博客园站点~欢迎关注~ .contact(new Contact("Vi的技术博客", "https://www.cnblogs.com/viyoung", "18530069930@163.com")) // 三个参数依次是姓名,个人网站,邮箱 .version("1.0") //版本 .build(); }}第三步:在启动类中添加配置注意一定要记得添加@EnableSwagger2注解 ...

August 7, 2019 · 2 min · jiezi

使用SpringDataJPA的Query注解完成动态条件分页查询

写作原因之前在学校都是做前端,但是最后找了个Java后端的工作,框架什么的基本没用过,所以工作中遇到了很多问题,所以决定记录下来工作中遇到的问题,记录成长的点滴。正文公司使用的是现在流行的SpringBoot,数据库方面使用的是SpringData+JPA+Hibernate。这几天用的最多的就是用JPA进行查询了,简单的查询很简单,网上查一查就有一堆方案,直到遇到分页查询的时候出了问题。 网上查到的大多都是使用EntityManager通过人工拼接sql来查询的,但是今天从导师那里学到了一手,在Repository中使用@Query注解即可。代码如下 public interface StudentRepository extends JpaRepository<Student,Integer> { @Query( value = "SELECT * FROM student" + " WHERE (age = ?1 OR ?1 IS NULL)" + " WHERE (id = ?2 OR ?2 IS NULL)" + " WHERE (first_name = ?3 OR ?3 IS NULL)" + " ORDER BY ?#{#pageable}", countQuery = "SELECT COUNT(1) FROM " + "SELECT * FROM student" + " WHERE (age = ?1 OR ?1 IS NULL)" + " WHERE (id = ?2 OR ?2 IS NULL)" + " WHERE (first_name = ?3 OR ?3 IS NULL)" + " ORDER BY ?#{#pageable}", nativeQuery = true ) Page<Student> findByAgeAndIdAndFirstName(Integer age, Integer id, String firstName, Pageable pageable);}最后的Pageable可以使用PageRequest来创建。 ...

July 17, 2019 · 1 min · jiezi

Spring-Boot-和-Thymeleaf-演示上传文件

注意事项:1、配置最大支持文件大小2、前端from表单post请求添加属性enctype="multipart/form-data"3、前端使用注解@RequestParam("file") MultipartFile file 使用postman github地址

July 17, 2019 · 1 min · jiezi

基于小米即时消息云服务MIMC的Web-IM

michat一个基于小米即时消息云服务(MIMC)的Web IM。 源码地址github和gitee同步。 截图展示 如何使用请先双击目录“需要安装的jars”的install.bat,安装自定义的jars。直接运行类MichatApplication,启动项目。访问http://localhost:8081/login,登录账号。默认配置了以下账号做测试:用户名 密码user 123456admin 123456jack 123456rose 123456simon 123456ddd 123456如何配置自己的MIMC登录https://dev.mi.com/console/appservice/mimc.html,注册并创建应用,修改chatIndex.js的mimc_appId,mimc_appSecret,mimc_appKey为你自己的值。

July 16, 2019 · 1 min · jiezi

Online开发初体验JeecgBoot-在线配置表单

Online开发——初体验(在线配置表单) 01 单表配置02 一对多配置03 树表单配置 单表配置 一对多配置 树表单配置

July 16, 2019 · 1 min · jiezi

Java秒杀系统实战系列整体业务流程介绍与数据库设计

摘要: 本篇博文是“Java秒杀系统实战系列文章”的第三篇,本篇博文将主要介绍秒杀系统的整体业务流程,并根据相应的业务流程进行数据库设计,最终采用Mybatis逆向工程生成相应的实体类Entity、操作Sql的接口Mapper以及写动态Sql的配置文件Mapper.xml。 内容: 对于该秒杀系统的整体业务流程,相信机灵的小伙伴在看完第二篇博文的时候,就已经知道个大概了!因为在提供的源码数据库下载的链接中,Debug已经跟各位小伙伴介绍了该秒杀系统整体的业务流程,而且还以视频形式给各位小伙伴进行了展示!该源码数据库的下载链接如下:https://gitee.com/steadyjack/...  在本篇博文中Debug将继续花一点篇幅介绍介绍! 一图以概之,如下图所示为该秒杀系统整体的业务流程: 从该业务流程图中,可以看出,后端接口在接收前端的秒杀请求时,其核心处理逻辑为: (1)首先判断当前用户是否已经抢购过该商品了,如果否,则代表用户没有抢购过该商品,可以进入下一步的处理逻辑 (2)判断该商品可抢的剩余数量,即库存是否充足(即是否大于0),如果是,则进入下一步的处理逻辑 (3)扣减库存,并更新数据库的中对应抢购记录的库存(一般是减一操作),判断更新库存的数据库操作是否成功了,如果是,则创建用户秒杀成功的订单,并异步发送短信或者邮件通知信息通知用户 (4)以上的操作逻辑如果有任何一步是不满足条件的,则直接结束整个秒杀的流程,即秒杀失败! 如下图所示为后端处理“秒杀请求”时的核心处理逻辑: 综合这两个业务流程,下面进入“秒杀系统”的数据库设计环节,其中,主要包含以下几个表:商品信息表item、待秒杀信息表item_kill、秒杀成功记录表item_kill_success以及用户信息表user;当然,在实际的大型网站中,其所包含的数据库表远远不止于此!本系统暂且浓缩出其中核心的几张表! 如下图所示为该“秒杀系统”的数据库设计模型: 紧接着,是采用Mybatis的逆向工程生成这几个数据库表对应的实体类Entity、操作Sql的接口Mapper以及写动态Sql的配置文件Mapper.xml。如下图所示: 下面,贴出其中一个实体类以及相对应的Mapper接口和Mapper.xml代码,其他的,各位小伙伴可以点击链接:https://gitee.com/steadyjack/... 前往下载查看!首先是实体类ItemKill的源代码: import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Data;import java.util.Date;@Datapublic class ItemKill { private Integer id; private Integer itemId; private Integer total; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date startTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date endTime; private Byte isActive; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createTime; private String itemName; //采用服务器时间控制是否可以进行抢购 private Integer canKill;}然后是ItemKillMapper接口的源代码: ...

July 16, 2019 · 2 min · jiezi

Java秒杀系统实战系列构建SpringBoot多模块项目

摘要:本篇博文是“Java秒杀系统实战系列文章”的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项目,即“秒杀系统”!。 内容:传统的基于IDEA构建SpringBoot的项目,是直接借助Spring Initializr插件进行构建,但是这种方式在大部分情况下,只能充当“单模块”的项目,并不能很好的做到“分工明确、职责清晰”的分层原则! 故而为了能更好的管理项目代码以及尽量做到“模块如名”,快速定位给定的类文件或者其他文件的位置,下面我们将基于IDEA、借助Maven构建多模块的项目,其中,其构建的思路如下图所示: ![图片上传中...] 详细的构建过程在本文就不赘述了!文末有提供源码的地址以及构建过程的视频教程!下面重点介绍一下跟“Java秒杀系统”相关的构建步骤。 (1)如下图所示为最终构建成功的项目的整体目录结构: 从该目录结构中可以看出,该项目为一个“聚合型项目”,其中,model模块依赖api模块,server模块依赖model模块,层层依赖!最终在server模块实现“大汇总”,即server模块为整个项目的核心关键所在,像什么“配置文件”、“入口启动类”啥的都在这个模块中! 而且,各个模块的职责是不一样的,分工也很明确,就像model模块,一般人看了就知道这里放的东西应该是跟mybatis或者跟数据库mysql相关的类文件与配置文件等等。 构建好相应的模块之后,就需要往相应的模块添加依赖,即只需要在pom.xml中加入相应的依赖即可,在这里就不贴出来了!(2)在这里主要贴一下server模块入口启动类MainApplication的代码,如下所示: @SpringBootApplication@ImportResource(value = {"classpath:spring/spring-jdbc.xml"})@MapperScan(basePackages = "com.debug.kill.model.mapper")@EnableSchedulingpublic class MainApplication extends SpringBootServletInitializer{ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(MainApplication.class); } public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); }}其中,该启动类将加载配置文件spring-jdbc.xml(数据库链接信息的配置文件)! 构建完成之后,可以将整个项目采用外置的Tomcat跑起来,运行过程中,观察控制台Console的输出信息,如果没有报错信息,则代表整个项目的搭建是没有问题的!如果出现了问题,建议自己先研究一番并尝试去解决掉!如果仍旧不能解决,可以加文末提供的联系方式进行解决! (4)除此之外,为了让整个项目在前后端分离开发的情况下,前后端的接口交互更加规范(比如响应信息的规范等等),在这里我们采用了通用的一个状态码枚举类StatusCode 跟 一个通用的响应结果类BaseResponse,用于后端在返回响应信息给到前端时进行统一封装。 状态码枚举类StatusCode的源代码如下所示: public enum StatusCode { Success(0,"成功"), Fail(-1,"失败"), InvalidParams(201,"非法的参数!"), UserNotLogin(202,"用户没登录"), ; private Integer code; //状态码code private String msg; //状态码描述信息msg StatusCode(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; }}响应结果类BaseResponse的源代码如下所示: ...

July 16, 2019 · 2 min · jiezi

SpringCloud使用Fegin进行HTTP接口调用

Fegin简介Fegin是声明式、模块化的Http客户端,可以帮助我们快捷优雅的调用HTTP接口。在SpringCloud中可以很方便的创建一个Feign客户端,只需声明一个接口,并加上对应的注解就能完成对HTTP接口的调用。 本文不集成注册中心也就不使用Fegin的负载均衡,所以可以理解为一个更简便,高可复用的Http客户端。以示例讲解SpringBoot版本:2.1.1.RELEASESpringCloud版本:Finchley.SR2必要POM引入<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> </parent> <dependencyManagement> <dependencies> <!--spring-cloud --> <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> <dependencies> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>Fegin客户端定义1、@FeignClient 定义接口为Http客户端,调用指定方法,并将接口注入Spring上下文中参数url:所请求http服务的url、参数config:指定fegin的配置类2、@PostMapping 为@RequestMapping的组合注解,默认为Post请求调用,注解不多介绍,主要表示所需要调用的http接口的具体路径,可在url中拼接参数,也可以指定入参的ContentType3、http接口的返回值格式如果和返回对象属性一致,会反序列化为对应对象。package com.external.feign;import java.util.List;import java.util.Map;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PostMapping;import com.external.config.FormFeignConfig;import com.external.dto.response.BaseBispDto;import com.external.dto.response.NiftyRes;import com.external.dto.response.ReportAnalyzesRes;import com.external.dto.response.ReportSummaryRes;import com.external.feign.rpc.BISPResponse;@FeignClient(name="fegin-name", url="${http-url}" , configuration = FormFeignConfig.class)public interface BispClient { /** * @Description * @author zengzp */ @PostMapping(value="/intf?method=XXXX", consumes = {"application/x-www-form-urlencoded"}) public XXXXResponse<BaseBispDto> saveSampleInfo(Map<String, ?> params); /** * @Description * @author zengzp */ @PostMapping(value="/intf?method=XXXX", consumes = {"application/x-www-form-urlencoded"}) public XXXXResponse<NiftyRes> getNiftyDetectionResultReplenish(Map<String, ?> params); Fegin配置代码配置日志输出策略与指定对应ContentType的消息的编码与解码package com.external.config;import org.springframework.beans.factory.ObjectFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.http.HttpMessageConverters;import org.springframework.cloud.openfeign.support.SpringDecoder;import org.springframework.cloud.openfeign.support.SpringEncoder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Scope;import feign.Logger;import feign.codec.Decoder;import feign.codec.Encoder;import feign.form.FormEncoder;@Configurationpublic class FormFeignConfig { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Bean Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } @Bean @Scope("prototype") Decoder decoder() { return new AllinpayDecoder(new SpringDecoder(messageConverters)); } @Bean @Scope("prototype") Encoder encoder(){ return new FormEncoder(new SpringEncoder(this.messageConverters)); }}统一响应DTOpackage com.external.feign.rpc;import org.springframework.http.HttpStatus;import com.external.common.dto.BaseDto;public class XXXXResponse<T> extends BaseDto { /** * */ private static final long serialVersionUID = 1L; private String code; private String msg; private Long total; private T rows;}启动服务时务必在配置类上增加@EnableFeignClients

July 15, 2019 · 1 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

SpringBoot二配置文件

二、配置文件1、配置文件SpringBoot使用一个全局的配置文件,配置文件名是固定的; •application.properties •application.yml 配置文件的作用:修改SpringBoot自动配置的默认值;SpringBoot在底层都给我们自动配置好; YAML(YAML Ain't Markup Language) YAML A Markup Language:是一个标记语言 YAML isn't Markup Language:不是一个标记语言; 标记语言: 以前的配置文件;大多都使用的是 xxxx.xml文件; YAML:以数据为中心,比json、xml等更适合做配置文件; YAML:配置例子 server: port: 8081XML:<server> <port>8081</port></server>2、YAML语法:1、基本语法 k:(空格)v:表示一对键值对(空格必须有); 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的 server: port: 8081 path: /hello属性和值也是大小写敏感; 2、值的写法 字面量:普通的值(数字,字符串,布尔) k: v:字面直接来写; 字符串默认不用加上单引号或者双引号; "":双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思 name: "zhangsan n lisi":输出;zhangsan 换行 lisi '':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据 name: ‘zhangsan n lisi’:输出;zhangsan n lisi 对象、Map(属性和值)(键值对): k: v:在下一行来写对象的属性和值的关系;注意缩进 对象还是k: v的方式 friends: lastName: zhangsan age: 20行内写法: friends: {lastName: zhangsan,age: 18}数组(List、Set): 用- 值表示数组中的一个元素 ...

July 15, 2019 · 3 min · jiezi

安全访问框架spring-security

如果没有修改它的配置,Spring Security 默认自动给所有访问请求做了登录保护。

July 15, 2019 · 1 min · jiezi

SpringBoot三日志

三、日志1、日志框架小张;开发一个大型系统; 1、System.out.println("");将关键数据打印在控制台;去掉?写在一个文件? 2、框架来记录系统的一些运行时信息;日志框架 ; zhanglogging.jar; 3、高大上的几个功能?异步模式?自动归档?xxxx? zhanglogging-good.jar? 4、将以前框架卸下来?换上新的框架,重新修改之前相关的API;zhanglogging-prefect.jar; 5、JDBC---数据库驱动; 写了一个统一的接口层;日志门面(日志的一个抽象层);logging-abstract.jar; 给项目中导入具体的日志实现就行了;我们之前的日志框架都是实现的抽象层; 市面上的日志框架; JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j.... 日志门面 (日志的抽象层)日志实现JCL(Jakarta Commons Logging) SLF4j(Simple Logging Facade for Java) jboss-loggingLog4j JUL(java.util.logging) Log4j2 Logback左边选一个门面(抽象层)、右边来选一个实现; 日志门面: SLF4J; 日志实现:Logback; SpringBoot:底层是Spring框架,Spring框架默认是用JCL;‘ ==SpringBoot选用 SLF4j和logback;== 2、SLF4j使用1、如何在系统中使用SLF4j https://www.slf4j.org 以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法; 给系统里面导入slf4j的jar和 logback的实现jar import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); }}图示; 每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件; 2、遗留问题 a(slf4j+logback): Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx 统一日志记录,即使是别的框架和我一起统一使用slf4j进行输出? 如何让系统中所有的日志都统一到slf4j; ==1、将系统中其他日志框架先排除出去;== ==2、用中间包来替换原有的日志框架;== ...

July 15, 2019 · 2 min · jiezi

使用Jenkins构建Docker镜像

准备1.安装Centos7虚拟机2.安装JDK3.安装Git4.安装Maven5.安装Docker (1)使用yum命令进行安装: yum install -y docker -y 表示不询问 使用默认配置进行安装(2)查看是否安装成功 yum list installed | grep docker(3)启动docker systemctl start docker(4)查看是否启动成功 systemctl status docker(5)修改docker国内镜像源 vi /etc/docker/daemon.json修改为下面这样 { "registry-mirrors": ["http://hub-mirror.c.163.com"] }(6)重启docker systemctl restart docker(7)设置docker远程访问 vi /lib/systemd/system/docker.service 添加下面这行 ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock安装/配置Jenkins下载Jenkins Jenkins安装有3种方式,下载jenkins.war放在Tomcat的webapp目录下运行、docker安装Jenkins、yum在线安装。使用jenkins.war安装 (1)下载jenkins.war、Tomcat通过xshell上传到虚拟机definesys目录 (2)把jenkins.war复制到tomcat的webapp目录下 (3)java -jar jenkins.war启动Jenkins (4)cat /var/lib/jenkins/secrets/initialAdminPassword查看初始登录密码 (5)登录进去后会一直卡住不动,在$JENKINS_HOME/hudson.model.UpdateCenter.xml文件 中,默认内容如下 <?xml version='1.0' encoding='UTF-8'?> <sites> <site> <id>default</id> <url>http://updates.jenkins-ci.org/update-center.json</url> </site></sites这个地址在外国的服务器,因为墙的原因,下载初始化界面所需插件不了,就一直处于等待状态 把url改为http://mirror.xmission.com/je... (6)登录进去后创建一个用户 (7)提示安装的插件安一安 配置Jenkins 点击系统管理-》系统设置maven配置Gitee配置Docker配置,其中docker host url就是虚拟机的IP,端口就是安装docker时配置docker远程访问的端口。 点击系统管理-》全局工具配置JDK配置 Git配置Maven配置使用创建一个项目 创建一个springboot项目 在项目根目录下创建dockerfile ...

July 15, 2019 · 1 min · jiezi

SpringBoot-实战-二十-整合-Redis

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。 前言两个月没更新原创了,实在惭愧。没有借口,就是因为自己懒了。最近看了「刻意学习」,这本书谈的是学习与行动的关系,书中提到了「持续行动」 这个概念,意思就是:我们要去实实在在地去做一些事情,而且是每天都做,才能称之为「持续行动」。看完这本书以后,我意识到我必须要做些什么,那就是写作。 Redis 简介Redis 是一个开源的,基于内存的键值数据存储,用作数据库,缓存和消息代理。在实现方面,Key-Value 存储代表 NoSQL 空间中最大和最老的成员之一。Redis 支持数据结构,如字符串,散列,列表,集和带范围查询的有序集。在 spring data redis 的框架,可以很容易地编写,通过提供一个抽象的数据存储使用 Redis 的键值存储的 Spring 应用程序。非关系型数据库,基于内存,存取数据的速度不是关系型数据库所能比拟的redis 是键值对 (key-value) 的数据库数据类型字符串类型 string散列类型 hash列表类型 list集合类型 set有序集合类型 zset其中,因为SpringBoot 约定大于配置的特点,只要我们加入了 spring-data-redis 依赖包并配置 Redis 数据库,SpringBoot 就会帮我们自动配置一个 RedisTemplate ,利用它我们就可以按照以下方式操作对应的数据类型,在下面实战中我将会对这五种数据进行操作。 redisTemplate.opsForValue(); //操作字符串redisTemplate.opsForHash(); //操作hashredisTemplate.opsForList(); //操作listredisTemplate.opsForSet(); //操作setredisTemplate.opsForZSet(); //操作有序set开发环境SpringBoot 2.1.6 RELEASEspring-data-redis 2.1.9 RELEASERedis 3.2IDEAJDK8mysql关于如何安装 Redis 这里不再赘述,请自行搜索引擎搜索解决。 pom 依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>配置文件spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: root password: 123456 jpa: hibernate: ddl-auto: update #ddl-auto:设为 create 表示每次都重新建表 show-sql: true redis: host: localhost port: 6379 # Redis数据库索引(默认为0) database: 1 jedis: pool: #连接池最大连接数 max-active: 8 #最小空闲连接 min-idle: 0 #最大阻塞等待时间,负值表示没有限制 max-wait: -1ms #最大空闲连接 max-idle: 8 #连接超时时间(毫秒) timeout: 20ms # 无密码可不写 # password:为什么乱码? /** * 添加字符串 */ @Test public void setString(){ redisTemplate.opsForValue().set(USERKEY,"nasus"); redisTemplate.opsForValue().set(AGEKEY,24); redisTemplate.opsForValue().set(CITYKEY,"清远"); }首先是添加字符串类型的数据。它的运行结果如下: ...

July 14, 2019 · 3 min · jiezi

手撕面试官系列三-微服务架构面试题DubboSpring-BootSpring-Cloud

直接进入主题Dubbo(面试题+答案领取方式见个人主页) Dubbo 中 中 zookeeper 做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?dubbo 服务负载均衡策略?Dubbo 在安全机制方面是如何解决的dubbo 连接注册中心和直连的区别dubbo 服务集群配置(集群容错模式)dubbo 通信协议 dubbo 协议为什么要消费者比提供者个数多dubbo 通信协议 dubbo 协议为什么不能传大包dubbo 通信协议 dubbo 协议为什么采用异步单一长连接dubbo 通信协议 dubbo 协议适用范围和适用场景RMI 协议Hessian 协议httpWebserviceThrifSpring Boot 什么是 Spring Boot?Spring Boot 有哪些优点?什么是 JavaConfig?如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?Spring Boot 中的监视器是什么?如何在 Spring Boot 中禁用 Actuator 端点安全性?如何在自定义端口上运行 Spring Boot 应用程序?什么是 YAML?如何实现 Spring Boot 应用程序的安全性?如何集成 Spring Boot 和 ActiveMQ?如何使用 Spring Boot 实现分页和排序?什么是 Swagger?你用 Spring Boot 实现了它吗?什么是 Spring Profiles?什么是 Spring Batch?什么是 FreeMarker 模板?如何使用 Spring Boot 实现异常处理?您使用了哪些 starter maven 依赖项?什么是 CSRF 攻击?什么是 WebSockets?什么是 AOP?什么是 Apache Kafka?我们如何监视所有 Spring Boot 微服务?Spring Cloud ...

July 12, 2019 · 1 min · jiezi

IDEA关于springboot-Thymeleaf-热部署修改java文件无需手动编译重启

一、前言每次修改完java文件或者thmeleaf模版文件后,需要重新编译 rebuild 才能生效,这就显得很麻烦了。列表项目

July 12, 2019 · 1 min · jiezi

Spring-Boot-支持-HTTPS-如此简单So-easy

这里讲的是 Spring Boot 内嵌式 Server 打 jar 包运行的方式,打 WAR 包部署的就不存在要 Spring Boot 支持 HTTPS 了,需要去外部对应的 Server 配置。 你所需具备的基础什么是 Spring Boot?Spring Boot 核心配置文件详解Spring Boot 开启的 2 种方式Spring Boot 自动配置原理、实战Spring Boot 2.x 启动全过程源码分析更多请在Java技术栈微信公众号后台回复关键字:boot。 支持 HTTPSSpring Boot 配置 SSL 很简单,只需要通过一系列的 server.ssl.* 参数即可完成配置,如下所示。 application.properties 配置文件参考配置: server.port=8443server.ssl.protocol=TLSserver.ssl.key-store=classpath:javastack.keystoreserver.ssl.key-store-password=javastackserver.ssl.key-store-type=JKS如何在本地测试创建证书请参考Java技术栈微信公众号的这篇文章《一分钟开启Tomcat https支持》,把生成完的证书复制到 Spring Boot 项目中的 resources 目录即可。 这边只是提供了一个 SSL 单向验证的演示,更多 SSL 参数配置如下。 server.ssl.ciphers= # Supported SSL ciphers.server.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store.server.ssl.enabled= # Enable SSL support.server.ssl.enabled-protocols= # Enabled SSL protocols.server.ssl.key-alias= # Alias that identifies the key in the key store.server.ssl.key-password= # Password used to access the key in the key store.server.ssl.key-store= # Path to the key store that holds the SSL certificate (typically a jks file).server.ssl.key-store-password= # Password used to access the key store.server.ssl.key-store-provider= # Provider for the key store.server.ssl.key-store-type= # Type of the key store.server.ssl.protocol=TLS # SSL protocol to use.server.ssl.trust-store= # Trust store that holds SSL certificates.server.ssl.trust-store-password= # Password used to access the trust store.server.ssl.trust-store-provider= # Provider for the trust store.server.ssl.trust-store-type= # Type of the trust store.参数对应的类:org.springframework.boot.web.server.Ssl上面的例子配置后就能开启 HTTPS 了,默认的 HTTP 协议就不再支持了,Spring Boot 不支持以配置文件配置的方式同时支持 HTTP 和 HTTPS。 ...

July 12, 2019 · 2 min · jiezi

SpringBoot20-基础案例06引入JdbcTemplate和多数据源配置

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、JdbcTemplate对象1、JdbcTemplate简介在Spring Boot2.0框架下配置数据源和通过JdbcTemplate访问数据库的案例。SpringBoot对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。 2、JdbcTemplate核心方法1)execute方法:可以用于执行任何SQL语句;2)update方法batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;3)query方法及queryFor方法:用于执行查询相关语句;4)call方法:用于执行存储过程、函数相关语句。二、SpringBoot2中用法1、导入Jar包<!-- 数据库依赖 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version></dependency><!-- JDBC 依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency>2、配置数据源信息spring: application: # 应用名称 name: node06-boot-jdbc datasource: # 数据源一:data_one 库 primary: # 2.0开始的版本必须这样配置 jdbc-url: jdbc:mysql://localhost:3306/data_one #url: jdbc:mysql://localhost:3306/data_one username: root password: 123 driver-class-name: com.mysql.jdbc.Driver # 数据源二:data_two 库 secondary: # 2.0开始的版本必须这样配置 jdbc-url: jdbc:mysql://localhost:3306/data_two #url: jdbc:mysql://localhost:3306/data_two username: root password: 123 driver-class-name: com.mysql.jdbc.Driver3、数据源代码配置1)数据源一的配置@Primary 注解表示该数据源作为默认的主数据库。 /** * 数据源一配置 */@Configurationpublic class DataOneConfig { @Primary // 主数据库 @Bean(name = "primaryDataSource") @Qualifier("primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource (){ return DataSourceBuilder.create().build() ; } @Bean(name = "primaryJdbcTemplate") public JdbcTemplate primaryJdbcTemplate ( @Qualifier("primaryDataSource") DataSource dataSource){ return new JdbcTemplate(dataSource); }}2)数据源二配置 ...

July 12, 2019 · 1 min · jiezi

SpringBoot20-基础案例07集成Druid连接池配置监控界面

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、Druid连接池1、druid简介Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。Druid连接池是阿里巴巴内部唯一使用的连接池,在内部数据库相关中间件TDDL/DRDS 都内置使用强依赖了Druid连接池,经过阿里内部数千上万的系统大规模验证,经过历年双十一超大规模并发验证。 2、druid特点1)稳定性特性,阿里巴巴的业务验证2)完备的监控信息,快速诊断系统的瓶颈3)内置了WallFilter 提供防SQL注入功能二、整合SpringBoot2.0框架1、引入核心依赖<!-- 数据库依赖 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.13</version></dependency><!-- JDBC 依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency>2、数据源配置文件spring: application: # 应用名称 name: node07-boot-druid datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/data_one?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false username: root password: 123 initial-size: 10 max-active: 100 min-idle: 10 max-wait: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 max-evictable-idle-time-millis: 60000 validation-query: SELECT 1 FROM DUAL # validation-query-timeout: 5000 test-on-borrow: false test-on-return: false test-while-idle: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #filters: #配置多个英文逗号分隔(统计,sql注入,log4j过滤) filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/*3、核心配置类import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.support.http.StatViewServlet;import com.alibaba.druid.support.http.WebStatFilter;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.core.JdbcTemplate;/** * Druid数据库连接池配置文件 */@Configurationpublic class DruidConfig { private static final Logger logger = LoggerFactory.getLogger(DruidConfig.class); @Value("${spring.datasource.druid.url}") private String dbUrl; @Value("${spring.datasource.druid.username}") private String username; @Value("${spring.datasource.druid.password}") private String password; @Value("${spring.datasource.druid.driverClassName}") private String driverClassName; @Value("${spring.datasource.druid.initial-size}") private int initialSize; @Value("${spring.datasource.druid.max-active}") private int maxActive; @Value("${spring.datasource.druid.min-idle}") private int minIdle; @Value("${spring.datasource.druid.max-wait}") private int maxWait; @Value("${spring.datasource.druid.pool-prepared-statements}") private boolean poolPreparedStatements; @Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}") private int maxPoolPreparedStatementPerConnectionSize; @Value("${spring.datasource.druid.time-between-eviction-runs-millis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.min-evictable-idle-time-millis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.max-evictable-idle-time-millis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validation-query}") private String validationQuery; @Value("${spring.datasource.druid.test-while-idle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.test-on-borrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.test-on-return}") private boolean testOnReturn; @Value("${spring.datasource.druid.filters}") private String filters; @Value("{spring.datasource.druid.connection-properties}") private String connectionProperties; /** * Druid 连接池配置 */ @Bean //声明其为Bean实例 public DruidDataSource dataSource() { DruidDataSource datasource = new DruidDataSource(); datasource.setUrl(dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setDriverClassName(driverClassName); datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); datasource.setPoolPreparedStatements(poolPreparedStatements); datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try { datasource.setFilters(filters); } catch (Exception e) { logger.error("druid configuration initialization filter", e); } datasource.setConnectionProperties(connectionProperties); return datasource; } /** * JDBC操作配置 */ @Bean(name = "dataOneTemplate") public JdbcTemplate jdbcTemplate (@Autowired DruidDataSource dataSource){ return new JdbcTemplate(dataSource) ; } /** * 配置 Druid 监控界面 */ @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean srb = new ServletRegistrationBean(new StatViewServlet(),"/druid/*"); //设置控制台管理用户 srb.addInitParameter("loginUsername","root"); srb.addInitParameter("loginPassword","root"); //是否可以重置数据 srb.addInitParameter("resetEnable","false"); return srb; } @Bean public FilterRegistrationBean statFilter(){ //创建过滤器 FilterRegistrationBean frb = new FilterRegistrationBean(new WebStatFilter()); //设置过滤器过滤路径 frb.addUrlPatterns("/*"); //忽略过滤的形式 frb.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return frb; }}4、简单测试类@RestControllerpublic class DruidController { private static final Logger LOG = LoggerFactory.getLogger(DruidController.class); @Resource private JdbcTemplate jdbcTemplate ; @RequestMapping("/druidData") public String druidData (){ String sql = "SELECT COUNT(1) FROM d_phone" ; Integer countOne = jdbcTemplate.queryForObject(sql,Integer.class) ; // countOne==2 LOG.info("countOne=="+countOne); return "success" ; }}三、测试效果完成一次数据请求后,访问如下链接。 ...

July 12, 2019 · 2 min · jiezi

SpringBoot20-基础案例08集成Redis数据库实现缓存管理

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、Redis简介Spring Boot中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多NoSQL数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, Elasticsearch。这些案例整理好后,陆续都会上传Git。SpringBoot2 版本,支持的组件越来越丰富,对Redis的支持不仅仅是扩展了API,更是替换掉底层Jedis的依赖,换成Lettuce。本案例需要本地安装一台Redis数据库。 二、Spring2.0集成Redis1、核心依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>2、配置文件# 端口server: port: 8008spring: application: # 应用名称 name: node08-boot-redis # redis 配置 redis: host: 127.0.0.1 #超时连接 timeout: 1000ms jedis: pool: #最大连接数据库连接数,设 0 为没有限制 max-active: 8 #最大等待连接中的数量,设 0 为没有限制 max-idle: 8 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。 max-wait: -1ms #最小等待连接中的数量,设 0 为没有限制 min-idle: 0这样Redis的环境就配置成功了,已经可以直接使用封装好的API了。 3、简单测试案例import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.concurrent.TimeUnit;@RestControllerpublic class RedisController { @Resource private StringRedisTemplate stringRedisTemplate ; @RequestMapping("/setGet") public String setGet (){ stringRedisTemplate.opsForValue().set("cicada","smile"); return stringRedisTemplate.opsForValue().get("cicada") ; } @Resource private RedisTemplate redisTemplate ; /** * 设置 Key 的有效期 10 秒 */ @RequestMapping("/setKeyTime") public String setKeyTime (){ redisTemplate.opsForValue().set("timeKey","timeValue",10, TimeUnit.SECONDS); return "success" ; } @RequestMapping("/getTimeKey") public String getTimeKey (){ // 这里 Key 过期后,返回的是字符串 'null' return String.valueOf(redisTemplate.opsForValue().get("timeKey")) ; }}4、自定义序列化配置import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.io.Serializable;/** * Redis 配置 */@Configurationpublic class RedisConfig { private static final Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class) ; /** * 序列化配置 */ @Bean public RedisTemplate<String, Serializable> redisTemplate (LettuceConnectionFactory redisConnectionFactory) { LOGGER.info("RedisConfig == >> redisTemplate "); RedisTemplate<String, Serializable> template = new RedisTemplate<>(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; }}5、序列化测试import com.boot.redis.entity.User;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.ArrayList;import java.util.List;@RestControllerpublic class SerializeController { @Resource private RedisTemplate redisTemplate ; @RequestMapping("/setUser") public String setUser (){ User user = new User() ; user.setName("cicada"); user.setAge(22); List<String> list = new ArrayList<>() ; list.add("小学"); list.add("初中"); list.add("高中"); list.add("大学"); user.setEducation(list); redisTemplate.opsForValue().set("userInfo",user); return "success" ; } @RequestMapping("/getUser") public User getUser (){ return (User)redisTemplate.opsForValue().get("userInfo") ; }}三、源代码地址GitHub地址:知了一笑https://github.com/cicadasmile/spring-boot-base码云地址:知了一笑https://gitee.com/cicadasmile/spring-boot-base ...

July 12, 2019 · 2 min · jiezi

SpringBoot20-基础案例10整合Mybatis框架集成分页助手插件

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、Mybatis框架1、mybatis简介MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 2、mybatis特点1)sql语句与代码分离,存放于xml配置文件中,方便管理2)用逻辑标签控制动态SQL的拼接,灵活方便3)查询的结果集与java对象自动映射4)编写原生态SQL,接近JDBC5)简单的持久化框架,框架不臃肿简单易学3、适用场景MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。对性能的要求很高,或者需求变化较多的项目,MyBatis将是不错的选择。 二、与SpringBoot2.0整合1、项目结构图采用druid连接池,该连接池。 2、核心依赖<!-- mybatis依赖 --><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version></dependency><!-- mybatis的分页插件 --><dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version></dependency>3、核心配置mybatis: # mybatis配置文件所在路径 config-location: classpath:mybatis.cfg.xml type-aliases-package: com.boot.mybatis.entity # mapper映射文件 mapper-locations: classpath:mapper/*.xml4、逆向工程生成的文件这里就不贴代码了。 5、编写基础测试接口// 增加int insert(ImgInfo record);// 组合查询List<ImgInfo> selectByExample(ImgInfoExample example);// 修改int updateByPrimaryKeySelective(ImgInfo record);// 删除int deleteByPrimaryKey(Integer imgId);6、编写接口实现@Servicepublic class ImgInfoServiceImpl implements ImgInfoService { @Resource private ImgInfoMapper imgInfoMapper ; @Override public int insert(ImgInfo record) { return imgInfoMapper.insert(record); } @Override public List<ImgInfo> selectByExample(ImgInfoExample example) { return imgInfoMapper.selectByExample(example); } @Override public int updateByPrimaryKeySelective(ImgInfo record) { return imgInfoMapper.updateByPrimaryKeySelective(record); } @Override public int deleteByPrimaryKey(Integer imgId) { return imgInfoMapper.deleteByPrimaryKey(imgId); }}7、控制层测试类@RestControllerpublic class ImgInfoController { @Resource private ImgInfoService imgInfoService ; // 增加 @RequestMapping("/insert") public int insert(){ ImgInfo record = new ImgInfo() ; record.setUploadUserId("A123"); record.setImgTitle("博文图片"); record.setSystemType(1) ; record.setImgType(2); record.setImgUrl("https://avatars0.githubusercontent.com/u/50793885?s=460&v=4"); record.setLinkUrl("https://avatars0.githubusercontent.com/u/50793885?s=460&v=4"); record.setShowState(1); record.setCreateDate(new Date()); record.setUpdateDate(record.getCreateDate()); record.setRemark("知了"); record.setbEnable("1"); return imgInfoService.insert(record) ; } // 组合查询 @RequestMapping("/selectByExample") public List<ImgInfo> selectByExample(){ ImgInfoExample example = new ImgInfoExample() ; example.createCriteria().andRemarkEqualTo("知了") ; return imgInfoService.selectByExample(example); } // 修改 @RequestMapping("/updateByPrimaryKeySelective") public int updateByPrimaryKeySelective(){ ImgInfo record = new ImgInfo() ; record.setImgId(11); record.setRemark("知了一笑"); return imgInfoService.updateByPrimaryKeySelective(record); } // 删除 @RequestMapping("/deleteByPrimaryKey") public int deleteByPrimaryKey() { Integer imgId = 11 ; return imgInfoService.deleteByPrimaryKey(imgId); }}8、测试顺序http://localhost:8010/inserthttp://localhost:8010/selectByExamplehttp://localhost:8010/updateByPrimaryKeySelectivehttp://localhost:8010/deleteByPrimaryKey三、集成分页插件1、mybatis配置文件<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <plugins> <!--mybatis分页插件--> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> </plugins></configuration>2、分页实现代码@Overridepublic PageInfo<ImgInfo> queryPage(int page,int pageSize) { PageHelper.startPage(page,pageSize) ; ImgInfoExample example = new ImgInfoExample() ; // 查询条件 example.createCriteria().andBEnableEqualTo("1").andShowStateEqualTo(1); // 排序条件 example.setOrderByClause("create_date DESC,img_id ASC"); List<ImgInfo> imgInfoList = imgInfoMapper.selectByExample(example) ; PageInfo<ImgInfo> pageInfo = new PageInfo<>(imgInfoList) ; return pageInfo ;}3、测试接口http://localhost:8010/queryPage四、源代码地址GitHub地址:知了一笑https://github.com/cicadasmile/spring-boot-base码云地址:知了一笑https://gitee.com/cicadasmile/spring-boot-base ...

July 12, 2019 · 2 min · jiezi

SpringBoot20-基础案例09集成JPA持久层框架简化数据库操作

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、JAP框架简介JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范。主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。 二、与SpringBoot2.0整合1、核心依赖<!-- JPA框架 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>2、配置文件spring: application: name: node09-boot-jpa datasource: url: jdbc:mysql://localhost:3306/data_jpa?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true username: root password: root driver-class-name: com.mysql.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: trueddl-auto几种配置说明1)create每次加载hibernate时都删除上一次的生成的表,然后根据bean类重新来生成新表,容易导致数据丢失,(建议首次创建时使用)。2)create-drop每次加载hibernate时根据bean类生成表,但是sessionFactory一关闭,表就自动删除。3)update第一次加载hibernate时根据bean类会自动建立起表的结构,以后加载hibernate时根据bean类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。4)validate每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 3、实体类对象就是根据这个对象生成的表结构。 @Table(name = "t_user")@Entitypublic class User { @Id @GeneratedValue private Integer id; @Column private String name; @Column private Integer age; // 省略 GET SET}4、JPA框架的用法定义对象的操作的接口,继承JpaRepository核心接口。 import com.boot.jpa.entity.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;import org.springframework.stereotype.Repository;@Repositorypublic interface UserRepository extends JpaRepository<User,Integer> { // 但条件查询 User findByAge(Integer age); // 多条件查询 User findByNameAndAge(String name, Integer age); // 自定义查询 @Query("from User u where u.name=:name") User findSql(@Param("name") String name);}5、封装一个服务层逻辑import com.boot.jpa.entity.User;import com.boot.jpa.repository.UserRepository;import org.springframework.stereotype.Service;import javax.annotation.Resource;@Servicepublic class UserService { @Resource private UserRepository userRepository ; // 保存 public void addUser (User user){ userRepository.save(user) ; } // 根据年龄查询 public User findByAge (Integer age){ return userRepository.findByAge(age) ; } // 多条件查询 public User findByNameAndAge (String name, Integer age){ return userRepository.findByNameAndAge(name,age) ; } // 自定义SQL查询 public User findSql (String name){ return userRepository.findSql(name) ; } // 根据ID修改 public void update (User user){ userRepository.save(user) ; } //根据id删除一条数据 public void deleteStudentById(Integer id){ userRepository.deleteById(id); }}三、测试代码块import com.boot.jpa.JpaApplication;import com.boot.jpa.entity.User;import com.boot.jpa.service.UserService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import javax.annotation.Resource;@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = JpaApplication.class)public class UserJpaTest { @Resource private UserService userService ; @Test public void addUser (){ User user = new User() ; user.setName("知了一笑"); user.setAge(22); userService.addUser(user); User user1 = new User() ; user1.setName("cicada"); user1.setAge(23); userService.addUser(user1); } @Test public void findByAge (){ Integer age = 22 ; // User{id=3, name='知了一笑', age=22} System.out.println(userService.findByAge(age)); } @Test public void findByNameAndAge (){ System.out.println(userService.findByNameAndAge("cicada",23)); } @Test public void findSql (){ // User{id=4, name='cicada', age=23} System.out.println(userService.findSql("cicada")); } @Test public void update (){ User user = new User() ; // 如果这个主键不存在,会以主键自增的方式新增入库 user.setId(3); user.setName("哈哈一笑"); user.setAge(25); userService.update(user) ; } @Test public void deleteStudentById (){ userService.deleteStudentById(5) ; }}四、源代码地址GitHub地址:知了一笑https://github.com/cicadasmile/spring-boot-base码云地址:知了一笑https://gitee.com/cicadasmile/spring-boot-base ...

July 12, 2019 · 2 min · jiezi

SpringBoot-动态代理反射注解四-动态代理对象注入到Spring容器

上一篇:SpringBoot 动态代理|反射|注解|AOP 优化代码(三)-注解 本篇我们将实现通过代理生成的对象注入到spring容器中。首先需要实现BeanDefinitionRegistryPostProcessor, ApplicationContextAware两个接口,作用分别为:ApplicationContextAware:可以获得ApplicationContext对象,然后获取Spring容器中的对象BeanDefinitionRegistryPostProcessor:可以将我们自定义的bean注入到spring容器 @Slf4j@Componentpublic class HandlerBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware { private ApplicationContext applicationContext; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { /** * 获取AutoImpl注解的接口,这些接口就需要通过动态代理提供默认实现 */ Set<Class<?>> classes = getAutoImplClasses(); for (Class<?> clazz : classes) { /** * 获取继承自HandlerRouter的接口的泛型的类型typeName,传入到DynamicProxyBeanFactory * 以便传入到DynamicProxyBeanFactory扫描typeName的实现类,然后按照feign和url两种实现 * 方式分类 */ Type[] types = clazz.getGenericInterfaces(); ParameterizedType type = (ParameterizedType) types[0]; String typeName = type.getActualTypeArguments()[0].getTypeName(); /** * 通过FactoryBean注入到spring容器,HandlerInterfaceFactoryBean实现以下功能: * 1.调用动态代理DynamicProxyBeanFactory提供HandlerRouter子接口的默认实现 * 2.将第一步的默认实现,注入到spring容器 */ HandlerRouterAutoImpl handlerRouterAutoImpl = clazz.getAnnotation(HandlerRouterAutoImpl.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); definition.getPropertyValues().add("interfaceClass", clazz); definition.getPropertyValues().add("typeName", typeName); definition.getPropertyValues().add("context", applicationContext); definition.setBeanClass(HandlerInterfaceFactoryBean.class); definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); beanDefinitionRegistry.registerBeanDefinition(handlerRouterAutoImpl.name(), definition); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { log.info("------------------------>postProcessBeanFactory"); } /** * 通过反射扫描出所有使用HandlerRouterAutoImpl的类 * @return */ private Set<Class<?>> getAutoImplClasses() { Reflections reflections = new Reflections( "io.ubt.iot.devicemanager.impl.handler.*", new TypeAnnotationsScanner(), new SubTypesScanner() ); return reflections.getTypesAnnotatedWith(HandlerRouterAutoImpl.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; log.info("------------------->setApplicationContext"); } /** * 通过class获取所有该类型的bean * * @param clazz * @return */ private Map<String, T> getBeans(Class<T> clazz) { return applicationContext.getBeansOfType(clazz); } private String getYmlProperty(String propery) { return applicationContext.getEnvironment().getProperty(propery); }}HandlerInterfaceFactoryBean 通过动态代理创建默认实现类 ...

July 11, 2019 · 2 min · jiezi

SpringBoot-动态代理反射注解AOP-优化代码三注解

上一篇SpringBoot 动态代理|反射|注解|AOP 优化代码(二)-反射 我们实现了通过反射完善找到目标类,然后通过动态代理提供默认实现,本篇我们将使用自定义注解来继续优化。 创建注解1.创建枚举 ClientType,用来标明Handler的实现方式 public enum ClientType { FEIGN,URL}2.创建注解ApiClient,用来标明Handler的实现方式 @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface ApiClient { ClientType type();}3.创建HandlerRouterAutoImpl注解,来标记该HandlerRouter是否通过代理提供默认实现 @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface HandlerRouterAutoImpl { /** * 在spring容器中对应的名称 * @return */ String name();}4.DeviceHandlerRouter添加注解,以动态代理提供默认实现 @HandlerRouterAutoImpl(name = "deviceHandlerRouter")public interface DeviceHandlerRouter extends HandlerRouter<DeviceHandler> {}5.DeviceHandlerFeignImpl、DeviceHandlerUrlImpl 添加注解标明具体的实现方式 @ApiClient(type = ClientType.FEIGN)@Component@Slf4jpublic class DeviceHandlerFeignImpl implements DeviceHandler { @Autowired private DeviceFeignClient deviceFeignClient; @Override public void remoteAddBatch(RemoteAddDeviceParam remoteAddDeviceParam, Integer envValue) { RestResult restResult = deviceFeignClient.create(remoteAddDeviceParam); ... } @Override public void remoteDeleteBatch(Integer envValue, List<String> snsList) { RestResult restResult = deviceFeignClient.deleteBySnList(snsList); ... } }@ApiClient(type = ClientType.URL)@Component@Slf4jpublic class DeviceHandlerUrlImpl implements DeviceHandler { @Override public void remoteAddBatch(RemoteAddDeviceParam remoteAddDeviceParam, Integer envValue) { String url = getAddUrlByEnvValue(envValue); String response = OkHttpUtils.httpPostSyn(url, JSON.toJSONString(snsList), false); RestResult restResult = JSON.parseObject(response, RestResult.class); ... } @Override public void remoteDeleteBatch(Integer envValue, List<String> snsList) { String url = getDelUrlByEnvValue(envValue); String response = OkHttpUtils.httpPostSyn(url, JSON.toJSONString(snsList), false); RestResult restResult = JSON.parseObject(response, RestResult.class); ... }}6.通过注解扫描目标类 ...

July 11, 2019 · 2 min · jiezi

SpringBoot-动态代理反射注解AOP-优化代码二反射

SpringBoot 动态代理|反射|注解|AOP 优化代码(一)-动态代理提供接口默认实现 我们抛出问题,并且提出解决问题的第一步的方法。下面我们继续深入,动态代理和反射继续解决我们的问题。 改动代码结构新增一个HandlerRougter接口,其目的就是替代上一篇的DeviceHandlerRouter public interface HandlerRouter<T> { T getHandler(Integer env,Object... args);}其中T是具体的业务接口。下面实现DeviceHandler的HandlerRouter: public interface DeviceHandlerRouter extends HandlerRouter<DeviceHandler> {}那么上层代码的调用方式将会类似下面的代码: DeviceHandlerRouter deviceHandlerRouter = ...deviceHandlerRouter.getHandler(...). remoteAddBatch(...)反射+动态代理前面说过,每增加一种接口调用,就需要重新实现xxxHandlerRouter,那么下面我们通过动态代理和反射提供DeviceHandler的默认实现。 1.通过反射获取HandlerRouter<T>的子接口和泛型对应的类 首先加入下面的依赖 <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency>/** * 通过反射扫描出所有HandlerRouter的子类 * @return */private Set<Class<?>> getHandlerRouterClasses() { Reflections reflections = new Reflections( "package.name.*", new TypeAnnotationsScanner(),//注解扫描,本节用不到 new SubTypesScanner() ); return reflections.getSubTypesOf(HandlerRouter.class);}Set<Class<?>> classes = getHandlerRouterClasses();//获取HandlerRouter的子接口的泛型Class 例如:DeviceHandlerRouter接口的DeviceHandlerfor (Class<?> clazz : classes) { //clazz 对应DeviceHandlerRouter.class Type[] types = clazz.getGenericInterfaces(); ParameterizedType type = (ParameterizedType) types[0]; //typeName对应DeviceHandlerRouter extends HandlerRouter<DeviceHandler> 中的DeviceHandler.class String typeName = type.getActualTypeArguments()[0].getTypeName();}2.SpringBoot ApplicationContext 获取注入的bean ...

July 11, 2019 · 2 min · jiezi

SpringBoot-动态代理反射注解AOP-优化代码一动态代理提供接口默认实现

一、背景在项目中需要调用外部接口,由于需要调用不同环境(生产、测试、开发)的相同接口(例如:向生、测试、开发环境的设备下发同一个APP)。 1.生产环境由SpringCloud注册中心,通过Feign调用,2.其它环境直接通过OKHttp直接通过Url调用。因此需要根据传入的环境调选择不同的调用方式。 优化前代码结构下面以添加和删除设备接口为例(一切从简,不代表真正业务代码): public interface DeviceHandler { void remoteAddBatch(RemoteAddDeviceParam remoteAddDeviceParam, Integer envValue); void remoteDeleteBatch(Integer envValue, List<String> snsList);}Feign方式实现: @Component@Slf4jpublic class DeviceHandlerFeignImpl implements DeviceHandler { @Autowired private DeviceFeignClient deviceFeignClient; @Override public void remoteAddBatch(RemoteAddDeviceParam remoteAddDeviceParam, Integer envValue) { RestResult restResult = deviceFeignClient.create(remoteAddDeviceParam); ... } @Override public void remoteDeleteBatch(Integer envValue, List<String> snsList) { RestResult restResult = deviceFeignClient.deleteBySnList(snsList); ... } }Url方式实现 @Component@Slf4jpublic class DeviceHandlerUrlImpl implements DeviceHandler { @Override public void remoteAddBatch(RemoteAddDeviceParam remoteAddDeviceParam, Integer envValue) { String url = getAddUrlByEnvValue(envValue); String response = OkHttpUtils.httpPostSyn(url, JSON.toJSONString(snsList), false); RestResult restResult = JSON.parseObject(response, RestResult.class); ... } @Override public void remoteDeleteBatch(Integer envValue, List<String> snsList) { String url = getDelUrlByEnvValue(envValue); String response = OkHttpUtils.httpPostSyn(url, JSON.toJSONString(snsList), false); RestResult restResult = JSON.parseObject(response, RestResult.class); ... }}起到路由作用的DeviceHandlerRouter(其实类似代理),选择具体调用哪种实现,对上传服务暴露的是DeviceHandlerRouter。 ...

July 11, 2019 · 2 min · jiezi

聊聊spring-boot的WebFluxTagsProvider

序本文主要研究一下webflux的WebFluxTagsProvider WebFluxTagsProviderspring-boot-actuator-2.1.5.RELEASE-sources.jar!/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTagsProvider.java @FunctionalInterfacepublic interface WebFluxTagsProvider { /** * Provides tags to be associated with metrics for the given {@code exchange}. * @param exchange the exchange * @param ex the current exception (may be {@code null}) * @return tags to associate with metrics for the request and response exchange */ Iterable<Tag> httpRequestTags(ServerWebExchange exchange, Throwable ex);}WebFluxTagsProvider接口定义了httpRequestTags方法DefaultWebFluxTagsProviderspring-boot-actuator-2.1.5.RELEASE-sources.jar!/org/springframework/boot/actuate/metrics/web/reactive/server/DefaultWebFluxTagsProvider.java public class DefaultWebFluxTagsProvider implements WebFluxTagsProvider { @Override public Iterable<Tag> httpRequestTags(ServerWebExchange exchange, Throwable exception) { return Arrays.asList(WebFluxTags.method(exchange), WebFluxTags.uri(exchange), WebFluxTags.exception(exception), WebFluxTags.status(exchange), WebFluxTags.outcome(exchange)); }}DefaultWebFluxTagsProvider实现了WebFluxTagsProvider接口,它返回了method、uri、exception、status、outcome这几个tagWebFluxTagsspring-boot-actuator-2.1.5.RELEASE-sources.jar!/org/springframework/boot/actuate/metrics/web/reactive/server/WebFluxTags.java ...

July 11, 2019 · 2 min · jiezi

SpringBoot20-基础案例05多个拦截器配置和使用场景

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、拦截器简介1、拦截器定义拦截器,请求的接口被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。 拦截器主要用来按照指定规则拒绝请求。 2、拦截器中应用Token令牌验证请求数据校验用户权限校验放行指定接口二、SpringBoot2.0拦截器用法1、编写两个拦截器自定义类实现HandlerInterceptor接口1)OneInterceptor 拦截器 import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * 拦截器一 */public class OneInterceptor implements HandlerInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(OneInterceptor.class.getName()); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception { String url =String.valueOf(request.getRequestURL()) ; LOGGER.info("1、url=="+url); // 放开拦截 return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { LOGGER.info("1、postHandle"); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { LOGGER.info("1、afterCompletion"); }}2)TwoInterceptor 拦截器 ...

July 10, 2019 · 1 min · jiezi

SpringBoot20-基础案例03配置系统全局异常映射处理

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、异常分类这里的异常分类从系统处理异常的角度看,主要分类两类:业务异常和系统异常。 1、业务异常业务异常主要是一些可预见性异常,处理业务异常,用来提示用户的操作,提高系统的可操作性。常见的业务异常提示:1)请输入xxx2)xxx不能为空3)xxx重复,请更换 2、系统异常系统异常主要是一些不可预见性异常,处理系统异常,可以让展示出一个友好的用户界面,不易给用户造成反感。如果是一个金融类系统,在用户界面出现一个系统异常的崩溃界面,很有可能直接导致用户流失。常见的系统异常提示:1)页面丢失4042)服务器异常500 二、解决应用启动后404界面 1、引入页面Jar包<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>2、自定义首页接口import org.springframework.stereotype.Controller;import org.springframework.ui.ModelMap;import org.springframework.web.bind.annotation.RequestMapping;@Controllerpublic class IndexController { @RequestMapping("/") public String index(ModelMap modelMap) { modelMap.addAttribute("name","知了一笑") ; return "index"; }}3、首页界面<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8" /> <title></title></head><body><h1 th:text="${name}"></h1></body></html>4、运行效果 三、SpringBoot2.0中异常处理1、项目结构图 2、自定义业务异常类public class ServiceException extends Exception { public ServiceException (String msg){ super(msg); }}3、自定义异常描述对象public class ReturnException { // 响应码 private Integer code; // 异常描述 private String msg; // 请求的Url private String url; // 省略 get set 方法}4、统一异常处理格式1)两个基础注解@ControllerAdvice 定义统一的异常处理类@ExceptionHandler 定义异常类型对应的处理方式2)代码实现 ...

July 10, 2019 · 1 min · jiezi

SpringBoot20-基础案例04定时任务和异步任务的使用方式

一、定时任务1、基本概念按照指定时间执行的程序。 2、使用场景数据分析数据清理系统服务监控二、同步和异步1、基本概念同步调用程序按照代码顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用顺序执行时,不等待异步调用的代码块返回结果就执行后面的程序。 2、使用场景短信通知邮件发送批量数据入缓存三、SpringBoot2.0使用定时器1、定时器执行规则注解@Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行@Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行@Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次@Scheduled(cron="/5") :通过cron表达式定义规则2、定义时间打印定时器import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;import java.util.Date;/** * 时间定时任务 */@Componentpublic class TimeTask { Logger LOG = LoggerFactory.getLogger(TimeTask.class.getName()) ; private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ; /** * 每3秒打印一次系统时间 */ @Scheduled(fixedDelay = 3000) public void systemDate (){ LOG.info("当前时间::::"+format.format(new Date())); }}3、启动类开启定时器注解@EnableScheduling // 启用定时任务@SpringBootApplicationpublic class TaskApplication { public static void main(String[] args) { SpringApplication.run(TaskApplication.class,args) ; }}四、SpringBoot2.0使用异步任务1、编写异步任务类import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;@Componentpublic class AsyncTask { private static final Logger LOGGER = LoggerFactory.getLogger(AsyncTask.class) ; /* * [ asyncTask1-2] com.boot.task.config.AsyncTask : ======异步任务结束1====== * [ asyncTask1-1] com.boot.task.config.AsyncTask : ======异步任务结束0====== */ // 只配置了一个 asyncExecutor1 不指定也会默认使用 @Async public void asyncTask0 () { try{ Thread.sleep(5000); }catch (Exception e){ e.printStackTrace(); } LOGGER.info("======异步任务结束0======"); } @Async("asyncExecutor1") public void asyncTask1 () { try{ Thread.sleep(5000); }catch (Exception e){ e.printStackTrace(); } LOGGER.info("======异步任务结束1======"); }}2、指定异步任务执行的线程池这里可以不指定,指定执行的线城池,可以更加方便的监控和管理异步任务的执行。 ...

July 10, 2019 · 2 min · jiezi

SpringBoot20-基础案例02配置Log4j2实现不同环境日志打印

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、Log4j2日志简介日志打印是了解Web项目运行的最直接方式,所以在项目开发中是需要首先搭建好的环境。 1、Log4j2特点1)核心特点相比与其他的日志系统,log4j2丢数据这种情况少;disruptor技术,在多线程环境下,性能高;并发的特性,减少了死锁的发生。 2)性能测试 2、日志打印之外观模式每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,增加应用程序代码和日志框架的耦合性。《阿里巴巴Java开发手册》,其中有一条规范做了『强制』要求:SLF4JJava简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。 二、配置日志打印1、项目结构 2、不同环境的日志配置使用最直接的方式,不同环境加载不同的日志配置。1)开发环境配置 logging: config: classpath:log4j2-boot-dev.xml2)生产环境配置 logging: config: classpath:log4j2-boot-pro.xml3、Log4j2的配置文件<?xml version="1.0" encoding="UTF-8"?><!--monitorInterval:Log4j2 自动检测修改配置文件和重新配置本身,设置间隔秒数--><configuration monitorInterval="5"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出: %date表示日期,%thread表示线程名, %-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <!-- 定义日志存储的路径,不要配置相对路径 --> <property name="FILE_PATH" value="E:/logs/dev" /> <property name="FILE_NAME" value="boot-log4j2" /> </Properties> <appenders> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!--文件会打印出所有信息--> <File name="Filelog" fileName="${FILE_PATH}/log4j2.log" append="true"> <PatternLayout pattern="${LOG_PATTERN}"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy同一文件夹下15个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Console"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> <root level="info"> <appender-ref ref="Console"/> <appender-ref ref="Filelog"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers></configuration>三、测试日志打印1、简单的测试程序import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class Log4j2Controller { private static final Logger LOGGER = LoggerFactory.getLogger(Log4j2Controller.class); /** * 日志级别 * OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL */ @RequestMapping("/printLog") public String printLog (){ LOGGER.error("ERROR 级别日志"); LOGGER.warn("WARN 级别日志"); LOGGER.info("INFO 级别日志"); LOGGER.debug("DEBUG 级别日志"); LOGGER.trace("TRACE 级别日志"); return "success" ; }}2、测试效果图 ...

July 10, 2019 · 2 min · jiezi

新版druid监控页面SQL不显示问题

新版druid数据源驱动的SQL监控如果用以前的老版本配置是无法监控到SQL的: application.yml spring: datasource: druid: filters: - stat - wall - log4j启动应用之后访问druid监控页面,除了SQL相关的页面都正常工作,但是访问SQL监控页面时没有看到SQL记录。查看监控页面 数据源 菜单发现 filter类名 显示的是空,估计是filter配置有问题导致。 查阅官方文档发现filter配置有变更,改成以下形式即可统计SQL,同时在数据源页面 filter类名 会显示正常。 application.yml spring: datasource: druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 5000 # 状态监控 filter: stat: enabled: true db-type: mysql log-slow-sql: true slow-sql-millis: 2000 # 监控过滤器 web-stat-filter: enabled: true exclusions: - "*.js" - "*.gif" - "*.jpg" - "*.png" - "*.css" - "*.ico" - "/druid/*" # druid 监控页面 stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: false login-username: root login-password: root数据源filter类名:com.alibaba.druid.filter.stat.StatFilter ...

July 10, 2019 · 1 min · jiezi

公司倒闭-1-年了而我当年的项目上了-GitHub-热榜

公司倒闭 1 年多了,而我在公司倒闭时候做的开源项目,最近却上了 GitHub Trending,看着这个数据,真是不胜唏嘘。 <!--more--> 缘起2017 年 11 月份的时候,松哥所在的公司因为经营不善要关门了,关门的是深圳分公司,北京总部还在正常运转。 然后就是北京那边来人,和深圳的员工挨个谈话,谈裁员和赔偿,公司制度还算完善,都按照劳动合同法走,有的同事担心公司最后不按劳动合同法走,因此觉得先拿钱先走比价划算。我当时主要考虑到两个原因,并不着急走: 公司毕竟是香港上市公司,跑的了和尚跑不了庙,深圳关门了,北京那边还在运转,所以我不太担心公司赖账的事。年底工作不好找,11 月拿赔偿走人,还有俩月才过年,这个时候不太容易拿到满意的 offer,很多公司年底都关闭 HC 了。基于上面两点考虑,我当时并不急着走人,当公司说还需要有人留下来善后一直到 2018 年 1 月 31 号的时候,我就争取了下,然后就给留下来了。 留下来后并没有太多事情要做。划水划了一周,同事在楼下叫我:“老王下来聊天”,于是下楼跟他们吹吹牛,虽然吹牛,不过大多数时候还是在筹划来年找工作的事,不过我觉得这样没什么用,与其天天规划,不如来点实实在在的东西,为来年找工作积累一点筹码。 第一次尝试心里想着手上就开始行动了,技术栈就选择当时最流行的 Spring Boot + Vue 前后端分离,业务就打算先做一个简单的博客试试水,博客的业务比较简单,做起来快,于是,V部落项目就诞生了: V 部落 一个简单的博客后台管理,集成了博客编辑、发表、排版引入了 md 编辑器,博客的分类展示等,记得不到一周时间就弄完了,毕竟还是非常容易的。 V 部落项目发布后,我认认真真的写了一个介绍的 README,README 和我以前的开源项目一样,就是展示了一下项目的效果图,然后说了下要如何部署运行就完了。虽然自我感觉良好,但是并没有引起太多人关注。 在为数不多的几个关注中,我发现小伙伴在运行项目时候总是会遇到各种各样的问题,很多人多前后端分离的这种开发方式非常陌生,很多后端工程师甚至不懂,没听说过前端工程化,很多小伙伴在 GitHub 上提了很多非常简单的 issue,他们在部署V 部落项目时老是出错。 另一方面,由于博客项目比较简单,Vue 中很多高级功能没用上,例如状态管理,还有前后端分离时的动态权限管理,这些都没有体现出来。再加上当时才是 12 月,离过年还早着,我心想着再做一个业务复杂点的,然后把这些之前没用到的技能点都给用上。于是就有了微人事项目,这也是我们今天的主角,上了 6 月份 GitHub Trending。 微人事微人事项目,我就吸取 V 部落的经验,没有等项目完全发布后再上传到 GitHub 上,而是边做变更新,每做完一个功能,就写一个文档,把实现的思路,代码的原理等都记录下来,然后在打一个 tag ,发布到 GitHub 上,这样,即使是一些新手,跟着文档,也能完全做出来。 这是当时的一些提交记录: 基本上每隔一两天就能完成一个新功能,然后就提交一次,这样的更新频率一直持续到 2018 年 1 月 20 之前,1 月 21 号女票从昆士兰大学访学回来,陪她在深圳玩了几天,然后把女票送回家,耽搁了好几天没更新。 ...

July 10, 2019 · 1 min · jiezi

聊聊spring-boot的ErrorWebFluxAutoConfiguration

序本文主要研究一下spring boot的ErrorWebFluxAutoConfiguration ErrorWebFluxAutoConfigurationspring-boot-autoconfigure-2.1.5.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java @Configuration@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)@ConditionalOnClass(WebFluxConfigurer.class)@AutoConfigureBefore(WebFluxAutoConfiguration.class)@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })public class ErrorWebFluxAutoConfiguration { private final ServerProperties serverProperties; private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer; public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<ViewResolver> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { this.serverProperties = serverProperties; this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.orderedStream() .collect(Collectors.toList()); this.serverCodecConfigurer = serverCodecConfigurer; } @Bean @ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT) @Order(-1) public ErrorWebExceptionHandler errorWebExceptionHandler( ErrorAttributes errorAttributes) { DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler( errorAttributes, this.resourceProperties, this.serverProperties.getError(), this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders()); return exceptionHandler; } @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes( this.serverProperties.getError().isIncludeException()); }}ErrorWebFluxAutoConfiguration注册了DefaultErrorAttributes、ErrorWebExceptionHandlerErrorAttributesspring-boot-2.1.5.RELEASE-sources.jar!/org/springframework/boot/web/reactive/error/ErrorAttributes.java ...

July 9, 2019 · 7 min · jiezi

Spring-Boot-打包成的可执行-jar-为什么不能被其他项目依赖

前两天被人问到这样一个问题: “松哥,为什么我的 Spring Boot 项目打包成的 jar ,被其他项目依赖之后,总是报找不到类的错误?” <!--more--> 大伙有这样的疑问,就是因为还没搞清楚可执行 jar 和普通 jar 到底有什么区别?今天松哥就和大家来聊一聊这个问题。 多了一个插件Spring Boot 中默认打包成的 jar 叫做 可执行 jar,这种 jar 不同于普通的 jar,普通的 jar 不可以通过 java -jar xxx.jar 命令执行,普通的 jar 主要是被其他应用依赖,Spring Boot 打成的 jar 可以执行,但是不可以被其他的应用所依赖,即使强制依赖,也无法获取里边的类。但是可执行 jar 并不是 Spring Boot 独有的,Java 工程本身就可以打包成可执行 jar 。 有的小伙伴可能就有疑问了,既然同样是执行 mvn package 命令进行项目打包,为什么 Spring Boot 项目就打成了可执行 jar ,而普通项目则打包成了不可执行 jar 呢? 这我们就不得不提 Spring Boot 项目中一个默认的插件配置 spring-boot-maven-plugin ,这个打包插件存在 5 个方面的功能,从插件命令就可以看出: 五个功能分别是: build-info:生成项目的构建信息文件 build-info.propertiesrepackage:这个是默认 goal,在 mvn package 执行之后,这个命令再次打包生成可执行的 jar,同时将 mvn package 生成的 jar 重命名为 *.originrun:这个可以用来运行 Spring Boot 应用start:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理stop:这个在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理这里功能,默认情况下使用就是 repackage 功能,其他功能要使用,则需要开发者显式配置。 ...

July 9, 2019 · 2 min · jiezi

springboot-使用yml格式进行配置的方法

一、前言springboot配置文件的格式,有两种方式,一种是properties,就是application.properties,另一种就是yml格式,application.yml两种方式是差不多的,默认是properties,yml格式看起来会很有层次感如图,下边两种形式的效果是相同的:二、application.yml注意事项不同等级用冒号隔开次等级的前面是空格,不能使用制表符(tab)冒号之后如果有值,那么冒号和值之间至少有一个空格(实际上紧贴着也不影响)要么用application.properties,要么用application.yml,不要同时存在,否则的话yml中书写的时候,idea编辑器可能没有补全功能了三、参考链接http://how2j.cn/k/springboot/...

July 9, 2019 · 1 min · jiezi

IDEA创建springboot项目图文配置

一、项目创建之前的准备在创建项目之前,首先要下载好jdk、IDEA编辑器以及配置好maven工具。IDEA编辑器和jdk安装很简单,从网上先下载好IDEA的软件包直接安装就行。springboot由于内部已经集成了tomcat,所以不需要单独下载tomcat。maven的简单认识和相关的配置: maven构建java项目工具介绍IDEA配置好了,就可以用IDEA进行编辑代码和创建项目,maven工具配置好后,可以直接配合IDEA来通过maven自动引入需要的jar包。二、IDEA创建springboot项目首先打开IDEA,点击create new project按照下图选择spring initializr初始化springboot,点击next修改artifact,就相当于项目名称,最好小写字母选择依赖,web依赖然后一直next,finished,这样就创建好springboot初始化的一个项目了创建好后,按照下图点击enable auto-import,代表maven自动下载pom.xml中的依赖

July 9, 2019 · 1 min · jiezi

cron表达式基础讲解

初识 corncorn 表达式是一个字符串,分为 6 或 7 个域,每个域都会代表一个含义。 语法格式6 个域:second minute hour day month week7 个域:second minute hour day month week year由上可见,7 个域与 6 个域的语法只差了 year,一般情况下,我们使用 6 个域的结构。 corn表达式结构corn 从左到右(中间用空格隔开):秒 分 小时 月份中的日 月份 星期中的日期 年份 各个字段的含义 位置时间域名允许值允许的特殊字符1秒0-59, - * /2分0-59, - * /3小时0-23, - * /4日1-31, - * ? / L W C5月0-12, - * / 6星期1-7, - * ? / L C #7年(可选)1970-2099, - * /代码示例声明:本次的 cron 讲解结合了 springboot 框架。由于只是讲解 cron 表达式,所以就不在介绍 springboot 中如何使用 @Scheduled 注解的相关配置了秒的位置允许值为 0-59,如果写 60,将会报错 ...

July 7, 2019 · 2 min · jiezi

好好面试你必须要懂的SpringAop

【干货点】此处是【好好面试】系列文的第10篇文章。看完该篇文章,你就可以了解Spring中Aop的相关使用和原理,并且能够轻松解答Aop相关的面试问题。 在实际研发中,Spring是我们经常会使用的框架,毕竟它们太火了,也因此Spring相关的知识点也是面试必问点,今天我们就大话Aop。特地在周末推文,因为该篇文章阅读起来还是比较轻松诙谐的,当然了,更主要的是周末的我也在充电学习,希望有追求的朋友们也尽量不要放过周末时间,适当充电,为了走上人生巅峰,迎娶白富美。【话说有没有白富美介绍(o≖◡≖)】接下来,直接进入正文。 为什么要有aop我们都知道Java是一种面向对象编程【也就是OOP】的语言,不得不说面向对象编程是一种及其优秀的设计,但是任何语言都无法十全十美,对于OOP语言来说,当需要为部分对象引入公共部分的时候,OOP就会引入大量的重复代码【这些代码我们可以称之为横切代码】。而这也是Aop出现的原因,没错,Aop就是被设计出来弥补OOP短板的。Aop便是将这些横切代码封装到一个可重用模块中,继而降低模块间的耦合度,这样也有利于后面维护。 Aop是什么东西学过Spring的都知道,Spring内比较核心的功能便是Ioc和Aop,Ioc的主要作用是应用对象之间的解耦,而Aop则可以实现横切代码【如权限、日志等】与他们绑定的对象之间的解耦,举个浅显易懂的小栗子,在用户调用很多接口的地方,我们都需要做权限认证,判断用户是否有调用该接口的权限,如果每个接口都要自己去做类似的处理,未免有点sb了,也不够装x,因此Aop就可以派上用场了,将这些处理的代码放到切片中,定义一下切片、连接点和通知,刷刷刷跑起来就ojbk了。 想要了解Aop,就要先理解以下几个术语,如PointCut、Advice、JoinPoint。接下来尽量用白话文描述下。 PointCut【切点】其实切点的概念很好理解,你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution( com.nuofankj.springdemo.aop.Service.*(..))可以看出来,格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点,简单明了。 AdviceAdvice行内很多人都定义成了通知,但是我总觉得有点勉强。所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉,何时调用其实也不过以下几种: Before 在方法被调用之前调用After 在方法完成之后调用After-returning 在方法成功执行之后调用After-throwing 在方法抛出异常之后调用Around 在被通知的方法调用之前和调用之后调用JoinPoint【连接点】JoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 给出一个Aop在实际中的应用场景先给出两个业务内的接口,一个是聊天,一个是购买东西接下来该给出说了那么久的切片了可以从中看到PointCut【切点】是 execution( com.nuofankj.springdemo.aop.Service.*(..))Advice是 BeforeJoinPoint【连接点】是 MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();代码浅显易懂,其实就是将ChatService和BuyService里边给userId做权限校验的逻辑抽出来做成切片。 那么如何拿到具体业务方法内的具体参数呢?这里是定义了一个新的注解作用可以直接看注释,使用地方如下可以看到对应接口使用了AuthPermission的注解,而取出的地方在于是的,这样便可以取出来对应的接口传递的userId具体是什么了,而校验逻辑可以自己处理。 送佛送到西,不对,撸码撸整套,接下来给出运行的主类可以看到,上面有一个接口传递的userId是1,另一个是123,而上面权限认证只有1才说通过,否则会抛出异常。 运行结果如下运行结果可想而知,1的通过验证,123的失败。 Aop的原理解析关于原理解析,由于大家都不喜欢看篇幅太长的文章,因此打算拆分成两篇进行,下篇文章会对Aop的原理和设计思想进行解析,有兴趣的朋友可以关注我一波。 公众号主营:服务端编程相关技术解说,具体可以看历史文章。 公众号副业:各种陪聊吹水,包括技术、就业、人生经历、大学生活、内推等等。 欢迎关注,一起侃大山

July 7, 2019 · 1 min · jiezi

Spring-Boot系列文章

最近学习Spring Boot写了几篇文章。写文章的过程也是对自己学习成果的总结,更能促进自己的思考。在这里汇总一下,希望能和大家多交流。后续有新的Spring Boot相关文章会在这里持续更新。 《Spring Boot Hello World》。对Spring Boot最入门的介绍,不借助任何额外的工具,从无到有创建一个Spring Boot的web项目。目的是先对Spring Boot有个大概的认识。《Spring Boot自动装配详解》。在我刚接触Spring Boot的时候,最好奇的就是它的自动装配是如何实现的。本篇文章介绍了自动装配实现的原理。《深入理解SpringApplication》。在main函数中通过SpringApplication类启动Spring Boot应用,本篇文章结合SpringApplication类的源码分析了Spring Boot应用的启动过程。《Spring Boot外部化配置》。同一份代码要能够正常运行在多个不同的环境,例如PROD、SIT、TEST、DEV等环境。本篇文章介绍了Spring Boot在这方便的支持。《自定义Spring Boot Starter》。通过分析mybatis-spring-boot-starter的源码来介绍如何构建自定义的Spring Boot Starter。参考资料:《Spring Framework Documentation》《Spring Boot Reference Guide》

July 7, 2019 · 1 min · jiezi

自定义Spring-Boot-Starter

在实际开发中,对于一些通用业务和公共组件,我们可能想将其做成一个Spring Boot Starter便于所有系统使用,这就需要我们定义自己的Spring Boot Starter。本文不会编写一个真正的Spring Boot Starter,而是选择借用mybatis-spring-boot-starter来描述starter的制作方式。之所以选择mybatis的spring-boot-starter进行讲解,一是考虑到mybatis比较重要,大家都很熟悉。二是mybatis的spring-boot-starter不是spring官方提供的,是由mybatis自己实现的。先来思考一下,一个Spring Boot Starter都需要具备哪些能力: 提供了统一的dependency版本管理。仅需要导入对应的Starter依赖,相关的library,甚至是中间件,都一次性被引入了,而且要保证各dependency之间是不冲突的。例如当我们引入mybatis-spring-boot-starter依赖,mybatis和mybatis-spring等相关依赖也顺带被导入了。提供自动装配的能力。Starter可以自动的向Spring容器中注入需要的Bean,并且完成对应的配置。对外暴露恰当的properties。Starter不可能提前知道全部的配置信息,有些配置信息只有在应用集成这个Starter的时候才能明确。例如对于mybatis,configLocation、mapperLocation这些参数在每个项目中都可能不同,所以只有应用自己知道这些参数的值该是什么。mybatis-spring-boot-starter对外暴露了一组properties,例如如果我们想指定mapper文件的存放位置,只需要在application.properties中添加mybatis.mapperLocations=classpath:mapping/*.xml即可。下面我们带着这几个问题,来看mybatis-spring-boot-starter是如何实现的。 统一的dependency管理这一点比较好理解,就是利用maven的间接依赖特性,在Starter的maven pom.xml中声明所有需要的dependency,这样在项目工程导入这个Starter时,相关的依赖就都被一起导入了。下面是mybatis-spring-boot-starter的pom.xml,可以看到与集成mybatis相关的dependency都已经声明了。 <project ...> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot</artifactId> <version>2.0.1</version> </parent> <artifactId>mybatis-spring-boot-starter</artifactId> <name>mybatis-spring-boot-starter</name> <properties> <module.name>org.mybatis.spring.boot.starter</module.name> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> </dependencies></project>对外暴露propertiesmybatis-spring-boot-autoconfigure模块中的MybatisProperties类支持向外暴露相关的properties,它是通过@ConfigurationProperties实现的,并指定所有properties都以”mybatis“为前缀。关于@ConfigurationProperties的具体用法可参考《Spring Boot外部化配置》下面是MybatisProperties类的部分源码: @ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)public class MybatisProperties { public static final String MYBATIS_PREFIX = "mybatis"; private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); /** * Location of MyBatis xml config file. */ private String configLocation; /** * Locations of MyBatis mapper files. */ private String[] mapperLocations; /** * Packages to search type aliases. (Package delimiters are ",; \t\n") */ private String typeAliasesPackage; /** * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that * searched from typeAliasesPackage. */ private Class<?> typeAliasesSuperType;可以看到,我们使用mybatis的所有属性都可以通过properties的形式在application.properties中配置。 ...

July 7, 2019 · 1 min · jiezi

SpringBoot2X整合Mybaties和事务

开发小经验SpringBoot可以自动识别jdbc驱动,下面的配置项可以省略不写spring.datasource.driver-class-name =com.mysql.jdbc.Driver如果没有配置下面啊里的druid数据源,则默认使用数据源 (com.zaxxer.hikari.HikariDataSource)spring.datasource.type =com.alibaba.druid.pool.DruidDataSource

July 6, 2019 · 1 min · jiezi

一个非常好的springboot学习框架

今天分享一个非常好的springboot学习框架,注释全网最全,自动生成controller、model、dao、html、sql文件,集成一个shiro 权限框架,非常方便的脚手架,开发、接私活利器。 △链接:https://gitee.com/bdj/SpringB...

July 6, 2019 · 1 min · jiezi

使用jsr303规范验证数据

我们有时需要对前端传过来的数据做校验,就可以使用spring validation。他可以使我们不用在每个Controller编写校验代码,可以达到解耦的功能。本文环境为jdk8,框架使用springboot 2.1.0.RELEASE。 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId></dependency>需要被校验的实体类 @Datapublic class LoginVo { @Size(min=6,max = 12,message = "用户名不符合规范") String username; @NotBlank(message = "密码不能为空") String password;}Controller层,在参数Login前加上@Validated注解,表明需要spring对其进行校验 public JSONResult registerOrLogin(@Validated LoginVo loginVo)JSR提供的校验注解:@Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Size(max=, min=) 被注释的元素的大小必须在指定的范围内 @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 @Past 被注释的元素必须是一个过去的日期 @Future 被注释的元素必须是一个将来的日期 @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式 Hibernate Validator提供的校验注解: @NotBlank(message =) 验证字符串非null,且长度必须大于0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内 4.如果前端传来的数值不符合标准,后端会报错,所以我们可以编写一个全局异常类来捕获这个参数绑定异常,从而给前端返回提示消息 ...

July 5, 2019 · 1 min · jiezi

Spring-Boot-整合-Freemarker50-多行配置是怎么省略掉的

Spring Boot2 系列教程接近完工,最近进入修修补补阶段。Freemarker 整合貌似还没和大家聊过,因此今天把这个补充上。 <!--more--> 已经完工的 Spring Boot2 教程,大家可以参考这里: 干货|最新版 Spring Boot2.1.5 教程+案例合集Freemarker 简介这是一个相当老牌的开源的免费的模版引擎。通过 Freemarker 模版,我们可以将数据渲染成 HTML 网页、电子邮件、配置文件以及源代码等。Freemarker 不是面向最终用户的,而是一个 Java 类库,我们可以将之作为一个普通的组件嵌入到我们的产品中。 来看一张来自 Freemarker 官网的图片: 可以看到,Freemarker 可以将模版和数据渲染成 HTML 。 Freemarker 模版后缀为 .ftl(FreeMarker Template Language)。FTL 是一种简单的、专用的语言,它不是像 Java 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。 好了,这是一个简单的介绍,接下来我们来看看 Freemarker 和 Spring Boot 的一个整合操作。 实践在 SSM 中整合 Freemarker ,所有的配置文件加起来,前前后后大约在 50 行左右,Spring Boot 中要几行配置呢? 0 行! 1.创建工程首先创建一个 Spring Boot 工程,引入 Freemarker 依赖,如下图: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>工程创建完成后,在 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration 类中,可以看到关于 Freemarker 的自动化配置: ...

July 5, 2019 · 2 min · jiezi

SpringBoot系列教程JPA之delete使用姿势详解

原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿势,通过JPA可以怎样删除数据 一般来讲是不建议物理删除(直接从表中删除记录)数据的,在如今数据就是钱的时代,更常见的做法是在表中添加一个表示状态的字段,然后通过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么做的原因如下 物理删除,如果出问题恢复比较麻烦无法保证代码一定准确,在出问题的时候,删错了数据,那就gg了删除数据,会导致重建索引Innodb数据库对于已经删除的数据只是标记为删除,并不真正释放所占用的磁盘空间,这就导致InnoDB数据库文件不断增长,也会导致表碎片逻辑删除,保留数据,方便后续针对数据的挖掘或者分析<!-- more --> I. 环境准备在开始之前,当然得先准备好基础环境,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建下面简单的看一下演示添加记录的过程中,需要的配置 1. 表准备沿用前一篇的表,结构如下 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 项目配置配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明 ...

July 4, 2019 · 3 min · jiezi

基于SpringBootAntDesign的快速开发平台JeecgBoot-202-版本发布

项目介绍Jeecg-Boot 是一款基于SpringBoot+代码生成器的快速开发平台!采用前后端分离架构:SpringBoot,Ant-Design-Vue,Mybatis,Shiro,JWT。强大的代码生成器让前端和后台代码一键生成,不需要写任何代码,保持jeecg一贯的强大,绝对是全栈开发福音!! JeecgBoot在提高UI能力的同时,降低了前后分离的开发成本,JeecgBoot还独创在线开发模式(No代码概念),一系列在线智能开发:在线配置表单、在线配置报表等等。源码下载https://github.com/zhangdaisc...https://gitee.com/jeecg/jeecg...演示地址:http://boot.jeecg.org技术文档:http://jeecg-boot.mydoc.io快速入门:http://jeecg-boot.mydoc.io/?t...系统模块├─系统管理│ ├─用户管理│ ├─角色管理│ ├─菜单管理│ ├─权限设置(支持按钮权限、数据权限)│ ├─表单权限(控制字段禁用、隐藏)│ ├─部门管理│ └─字典管理├─智能化功能│ ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)│ ├─代码生成器模板(提供4套模板,分别支持单表和一对多模型,不同风格选择)│ ├─代码生成器模板(生成代码,自带excel导入导出)│ ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)│ ├─高级查询器(弹窗自动组合查询条件)│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)│ ├─平台移动自适应支持├─系统监控│ ├─性能扫描监控│ │ ├─监控 Redis│ │ ├─Tomcat│ │ ├─jvm│ │ ├─服务器信息│ │ ├─请求追踪│ │ ├─磁盘监控│ ├─定时任务│ ├─系统日志│ ├─消息中心(支持短信、邮件、微信推送等等)│ ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)│ ├─系统通知│ ├─SQL监控│ ├─swagger-ui(在线接口文档)│─报表示例│ ├─曲线图│ └─饼状图│ └─柱状图│ └─折线图│ └─面积图│ └─雷达图│ └─仪表图│ └─进度条│ └─排名列表│ └─等等│─常用示例│ ├─单表模型例子│ └─一对多模型例子│ └─打印例子│ └─一对多TAB例子│ └─内嵌table例子│ └─常用选择组件│ └─异步树table│ └─接口模拟测试│ └─一对多JEditable│ └─图片拖拽排序│ └─图片翻页│ └─图片预览│ └─PDF预览│ └─分屏功能│─封装通用组件 │ ├─行编辑表格JEditableTable│ └─省略显示组件│ └─时间控件│ └─高级查询│ └─通用选择用户组件│ └─通过组织机构选择用户组件│ └─报表组件封装│ └─字典组件│ └─下拉多选组件│ └─选人组件│ └─选部门组件│ └─通过部门选人组件│ └─封装曲线、柱状图、饼状图、折线图等等报表的组件(经过封装,使用简单)│ └─在线code编辑器│ └─上传文件组件│ └─等等│─更多页面模板│ ├─各种高级表单│ ├─各种列表效果│ └─结果页面│ └─异常页面│ └─个人页面├─Online在线开发(下个版本发布)│ ├─Online在线表单│ ├─Online在线图表│ ├─Online图表模板配置│ ├─Online在线报表│ ├─高级表单设计器└─其他模块 └─更多功能开发中。。系统特点采用最新主流前后分离框架(Springboot+Antd+Vue+Mybatis)强大的代码生成器,单表、一对多一键生成(包括前后端)简易Excel导入导出,支持单表导出和一对多表模式导出强大的权限机制,支持数据权限、表单按钮权限封装各种常用组件、报表组件,及其简单的生成图形报表支持菜单动态路由、支持多数据源查询过滤器:查询功能根据配置自动生成,不需要编码常用共通封装,各种工具类(定时任务,短信接口,邮件发送,Excel导入导出等)浏览器兼容性好,页面支持PC,Pad和移动端提供各种系统监控,实时跟踪系统运行情况(监控 Redis、Tomcat、jvm、服务器信息、请求追踪、SQL监控)提供简单易用的打印插件,支持谷歌、IE浏览器等各种浏览器示例代码丰富,提供很多案例学习升级日志修复功能我的部门录入用户bug处理 issues#202请求监控列表,ms单位问题修复 issues#132一对多示例功能,表单添加多张图片只显示一张问题处理 issues#103用户管理,改成逻辑删除首页统计报表优化菜单页面优化,菜单管理添加子菜单、顺序验证修复、菜单路由、前端组件为必选公告页面优化,postgres数据库兼容问题修正公告功能查询为空时,需要加判断,不然拼接sql会报错 issues#254系统管理-角色管理-添加/编辑加入校验,防止输入超过数据库限定字符长度信息,对用户友好提示部门管理、角色维护编辑时以及添加时问题修复部门管理,添加子部门按钮放出来,减少误解部门管理、角色维护编辑时以及添加时问题修复字典组件SQL注入风险处理启动项目,邮箱报错彻底解决 issues#225单表生成时,如果表字段过少,vue页面会出错处理 issues#234表格列表,字典字段排序问题处理 issues#244sys_log中request_param的字段过短问题 issues#214del_flag代码不规范问题 issues#169聚合路由问题修复,提供使用文档 issues#150登陆安全问题 issues#195解决继承实体无法翻译字典文本问题RedisConfig keyGenerator问题 issues#75权限类规范及ngalin菜单优化代码常量引用不规范的,重构统一常量文件引用导出功能excel导出未带登录人名字修复新功能前端项目升级依赖版本号:antv/data-set、ant-design-vue、vue、eslint、less、vue-template-compiler【新功能】用户注册功能实现【新功能】用户重置密码功能实现【新功能】用户手机号登录实现【权限升级】支持数据表格列权控制,及支持自定义列选择显示字典翻译注解@ Dict,支持多值翻译菜单升级,路由支持是否缓存配置提供国际化改造方案 issues#210动态数据源版本升级升级mybatis-plus版本3.1.2,支持逻辑删除注解@TableLogic增加工具 hutool代码生成器模板规范,进一步规范精简代码,导入支持批量插入数据库示例demo主键改成ID_WORKER_STR,后续系统ID规则全部切换为ID_WORKER_STR阿里规约检查扫描调整部分代码自定义组件新增corn表达式生成组件JMultiSelectTag组件升级,父组件动态改变dictOptions值时,子组件更新下拉列表的值JEditableTable功能增强,支持hidden类型、默认值显示错误字典组件支持表字典带条件新增JTreeSelect树形下拉框组件 (异步加载)新增JTreeDict 分类字典树形下拉组件新增异步加载树TABLE组件 JTreeTable新增表单禁用专用组件 JFormContainer新增图形验证码组件 JGraphicCode系统截图PC端 ...

July 4, 2019 · 1 min · jiezi

Spring-Boot-面试一个问题就干趴下了下

前些天栈长在Java技术栈微信公众号分享一篇文章:Spring Boot 面试,一个问题就干趴下了!,看到大家的留言很精彩,特别是说"约定大于配置"的这两个玩家。 哈哈,上墙的朋友开不开森? 不错,约定优(大)于配置确实是 Spring Boot 整个框架的核心思想。 那么怎么理解约定优于配置呢? 百度百科定义: 约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。总结就是两点: 1、约定一些推荐的默认配置; 2、开发人员只需要规定不符约定的部分; 这样做的好处就是,如果约定的默认配置符合我们的要求,省略即可,反之,再进行额外配置。 从 Spring Boot 中提供的默认的配置文件(application.properties/yml),再到默认值自动配置,都可以看出约定带来的便利,以及节省大量的配置。 来看下 Spring Boot 中一个自动配置的源码实例吧: @Configuration@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)@ConditionalOnWebApplication(type = Type.SERVLET)@EnableConfigurationProperties(MultipartProperties.class)public class MultipartAutoConfiguration { private final MultipartProperties multipartProperties; public MultipartAutoConfiguration(MultipartProperties multipartProperties) { this.multipartProperties = multipartProperties; } @Bean @ConditionalOnMissingBean public MultipartConfigElement multipartConfigElement() { return this.multipartProperties.createMultipartConfig(); } @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) @ConditionalOnMissingBean(MultipartResolver.class) public StandardServletMultipartResolver multipartResolver() { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); return multipartResolver; }}@ConfigurationProperties(prefix = "spring.servlet.multipart", ignoreUnknownFields = false)public class MultipartProperties { /** * Whether to enable support of multipart uploads. */ private boolean enabled = true; /** * Intermediate location of uploaded files. */ private String location; /** * Max file size. Values can use the suffixes "MB" or "KB" to indicate megabytes or * kilobytes, respectively. */ private String maxFileSize = "1MB"; /** * Max request size. Values can use the suffixes "MB" or "KB" to indicate megabytes or * kilobytes, respectively. */ private String maxRequestSize = "10MB"; /** * Threshold after which files are written to disk. Values can use the suffixes "MB" * or "KB" to indicate megabytes or kilobytes, respectively. */ private String fileSizeThreshold = "0"; /** * Whether to resolve the multipart request lazily at the time of file or parameter * access. */ private boolean resolveLazily = false; // get/set/etc..}这是一个文件上传的自动配置类,约定了: ...

July 4, 2019 · 2 min · jiezi

从SpringBoot整合Mybatis分析自动配置

前言SpringBoot凭借"约定大于配置"的理念,已经成为最流行的web开发框架,所以有必须对其进行深入的了解;本文通过整合Mybatis类来分析SpringBoot提供的自动配置(AutoConfigure)功能,在此之前首先看一个整合Mybatis的实例。 SpringBoot整合Mybatis提供SpringBoot整合Mybatis的实例,通过Mybatis实现简单的增删改查功能; 1.表数据CREATE TABLE `role` ( `note` varchar(255) CHARACTER SET utf8 DEFAULT NULL, `role_name` varchar(255) DEFAULT NULL, `id` bigint(20) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8提供创建role表相关的sql,对表进行增删改查操作; 2.整合Mybatis的依赖主要是mybatis-spring-boot-starter和使用的mysql驱动: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.29</version></dependency>3.配置application.properties提供连接mysql相关的信息:url,驱动,用户名,密码; spring.datasource.url=jdbc:mysql://localhost/mybatisspring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.jdbc.Driver4.提供bean和Dao分别提供表对应的bean类和操作数据库的dao类; public class Role { private long id; private String roleName; private String note; //省略get/set方法}@Mapperpublic interface RoleDao { @Select("SELECT id,role_name as roleName,note FROM role WHERE id = #{id}") Role findRoleById(@Param("id") long id);}5.提供Service和Controllerpublic interface RoleService { public Role findRoleById(long roleId);}@Servicepublic class RoleServiceImpl implements RoleService { @Autowired private RoleDao roleDao; @Override public Role findRoleById(long roleId) { return roleDao.findRoleById(roleId); }}@RestControllerpublic class RoleController { @Autowired private RoleService roleService; @RequestMapping("/role") public String getRole(long id) { return roleService.findRoleById(id).toString(); }}启动服务,进行简单的测试:http://localhost:8888/role?id=111结果如下: ...

July 2, 2019 · 3 min · jiezi

前后端分离时代Java-程序员的变与不变

事情的起因是这样的,有个星球的小伙伴向邀请松哥在知乎上回答一个问题,原题是: <!--more--> 前后端分离的时代,Java后台程序员的技术建议?松哥认真看了下这个问题,感觉对于初次接触前后端分离的小伙伴来说,可能都会存在这样的疑问,于是决定通过这篇文章和大家聊一聊这个话题。 我这里还是尽量从一个 Java 程序员的角度来说说这个问题,这样大家可能更好理解。 从一个题外话开始很多小伙伴可能知道,松哥本科是经管学院的,亚当•斯密的《国富论》多多少少还是了解一点。书中提到人类社会的本质就是分工协作,亚当•斯密认为人类之间的专业分工可以极大的提高生产力、创造财富,专业分工也是工业革命的基础。人类社会的发展过程就是一个专业分工不断细化、不断深化的过程,从最早的农牧分家到手工业农业分家再到商人的出现,其实都是专业分工不断细化深化的体现。 我们的开发世界也是一个小宇宙,专业分工不断细化也是一个趋势,从这个角度来说,前后端分离,都是值得积极拥抱的。 前后端分离的开发方式在最近几年突然火起来,松哥认为有两方面的原因: 前端的发展。前端经过近几年的发展,已经不再是我们传统所说的HTML+画图了,各种概念层出不穷,webpack、RxJs、Node、Redux、ssr、NuxtJs等,前端已经可以胜任很多事情,也能够完成更加丰富的用户交互。移动互联网的发展。前两年移动互联网的火爆,很多公司的产品都要在多个平台上线,Android、iOS、小程序、公众号、PC 等等各个平台都要展示,不可能针对不同的设备开发一套后端,应该是多个前端共用同一个后端,这是就不能采用传统的前后端不分的方式来开发后端程序了。正是这样的业务需求,促进了前后端分离的发展。变与不变程序员之间的分工协作方式有所变化,开发方式当然也会随着一起变化。但是这种变化其实是非常细微的,很容易上手的。 变工作内容变老实说,前后端分离之后,对 Java 程序员的要求变低了,以前大家大家出去面试 Java 工程师,如果是前后端不分的话,前端基本上也是必问的,常见的问题就是各种元素选择器,这也很好理解,因为在前后端不分的开发方式中,后端工程师多多少少是要写一点前端代码的,你很难完完全全的只写 Java 代码。但是在这种情况下,你要写的前端代码其实都是很简单的,不会是特别难的。 前后端分离之后,Java 程序员只需要专注于后台业务逻辑,对外接收前台传来的参数,根据参数给出不同的响应即可,基本上不需要写前端代码。因为这个时候的前端不同于前后端不分时候的前端,前后端分离之后,前端还是有一定的难度,较为常见的是 SPA 应用,涉及到 NodeJS、Webpack 等,此时如果还要让后端工程师写前端代码,对后端工程师的技术要求就会比较高。 不过话说回来,前后端分离后,如果你还能即写前端又写后端,那可以让老板加薪了。 接口变前后端不分的时候,很少会涉及到接口设计,以 SpringMVC 为例,你可能返回的始终是 ModelAndView 一类的东西,前后端分离之后,我们基本上不需要返回页面了,后端主要是返回 JSON 数据,所以关键是设计好各种接口。 一个比较好的实践方案是设计满足 RESTful 规范的接口,语义明确,简洁明了,看到 URL 就知道你想干嘛! 开发流程变化前后端分离之后,前端不可能等后端开发好接口之后再去开发,如果这样,原本两个月做完的项目可能就得 4 个月才能完成。 一般在开发之前,整个项目组需要先设计好一个接口文档,一般可以采用 Swagger 来做接口文档(SpringBoot整合Swagger2,再也不用维护接口文档了!),文档中约定了接口的详细信息,前后端分别按照既定的接口规范去开发,在尚未开发完成时,可以借助 Mock 来进行测试。 前端也是使用模拟数据进行测试,开发完成之后,前后端接口联调,完成测试。 不变其实除了前后端交互方式发生变化之外,其他的地方都是不变的。 前后端分离,一般来说是不会影响后端技术架构的,你使用了 SSM 或者 Spring Boot 或者 Dubbo 或者微服务,无论什么,这些技术架构既可以支撑你前后端不分的项目,也可以支撑你前后端分离的项目。 因此我说后端技术架构不受前后端分离影响。 另一方面,技术的根本不变,例如你做 Java 开发,该会的 SSM/SpringBoot/Redis/Nginx/Dubbo/SpringCloud/MySQL/MyCat/ELK/...等等,都还得会。 所以,还是去老老实实撸代码吧! 结语如果仅仅从一个 Java 程序员的角度来说,前后端分离开发这种方式,其实是解放了 Java 程序员,可以让我们专注于后端的工作,不用再去写前端代码,术业有专攻,可以写出更优质的后端代码。不过话说回来,如果想保持一个良好的竞争力,还是有必要去了解一下目前流行的前端开发方式。 ...

July 1, 2019 · 1 min · jiezi

Spring-Boot外部化配置

通常情况,一个项目从开发到上线需要经历多个环境。例如程序员在自己的开发环境完成Coding,QA在测试环境完成对软件的测试。而最终软件运行在生产环境对外提供服务,产生真正的业务价值。一个正规的系统研发流程会按顺序经历如下多个环境:DEV->TEST->SIT->PRE->PROD。那么问题就来了,各个环境间肯定会存在差异(例如各个环境的数据源配置一定是不同的),如何让我们开发好的应用能正常的运行在不同的环境呢?这就需要外部化配置。简单的说就是提前在代码中定义好配置项,代码中引用的是配置项的key,配置项真正的value放置在应用外部(包括配置文件、命令行参数、系统环境变量等等),这样不同的环境就可以根据自身情况定义不同的value。本文会先介绍Spring Framework Environment的概念,有了基础之后再来看Spring Boot的Externalized Configuration。 EnvironmentSpring的Environment接口用来抽象application正在运行的环境,它有两个核心概念:profile和properties。profile用于限制一组bean的定义,这组bean只有在该profile处于激活状态时才会被注入到Spring容器中。properties就是一组配置项,它可以有多个来源:properties配置文件,JVM参数,系统环境变量等等。下面分别对profile和profiles进行详细介绍 ProfilePropertiesSpringBoot外部化配置

June 30, 2019 · 1 min · jiezi

工作记录给-Spring-boot-Jar-瘦瘦身

写在前面在如今程序员的世界中,spring boot 越来越流行,不管是开发web应用还是构建spring cloud 微服务架构都离不开它, 不同于传统的web应用 需要单独部署容器来发布war包, spring boot 应用可以把整个项目打包成我们熟悉的jar来运行,大大方便了我们的开发部署。 问题凸显上述提到Spring boot将整个应用打成一个Jar来执行,大大提高了我们的效率。 但是同时也给我们带来了烦恼,随着我们项目的不但迭代,也导致Jar不断的肥胖,对于高速迭代的项目上传一个如此肥胖的Jar简直痛不欲生。 那怎么办? 程序员是一个懒人职业,总会想到办法来一次搞定这些问题的。 下面就让我们来看看吧! 解决方案解决上述问题,只需要如下几步就可以搞定了。 通常我们是用spring-boot-maven-plugin 进行打包、通过阅读文档发现可以通过配置使得该插件在打包时忽略特定的依赖,文档:spring-boot-maven-plugin首先备份原先的依赖: 可以用使用 mvn dependency:copy-dependencies 首先将依赖导出。将一些变化不大的 jar copy 到外部文件 lib 文件夹中(和 pom 文件中配置对应 )修改 pom 文件<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layout>ZIP</layout> <!--去除在生产环境中不变的依赖--> <excludeGroupIds> org.springframework.boot, org.springframework, org.springframework.data, org.apache.tomcat.embed </excludeGroupIds> </configuration> </plugin> </plugins></build> 注:layout 必须是 ZIP 、 excludeGroupIds 中时忽略也是就需要打在外部的 jar 、根据自己项目的情况进行配置,exclude的更多用法 请参考文档spring-boot-maven-plugin项目启动 将 项目的 jar 和 刚创建的 lib 放在同级目录下(不是必须的)。启动项目: java -Dloader.path="lib/" -jar xx.jarok! 就这么简单的帮spring boot jar 减肥成功了。 ...

June 30, 2019 · 1 min · jiezi

mongoDB-40-事务

官网:mongoDB中,对单文档的操作是原子性的。例如insertOne,updateOne等操作。因此建议使用嵌入式文档来实现事务需求,而不是规范化的跨文档设计。但是业务上例如三方数据依赖的需求往往使用嵌入式文档不是理想中的那么方便。所以4.0开始提供了对副本集多文档事务的支持,注意是副本集,也就是说单server是不生效的。接下来的测试需要集群环境,赖得搭建,所以使用mongoDB Cloud提供的Altas的免费集群。 创建测试数据user info 创建springboot项目添加依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version></parent><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency></dependencies>连接mongoDB这里涉及到了Write Concern,推荐阅读MongoDB writeConcern原理解析w=majority:数据写入到副本集大多数成员后向客户端发送确认,适用于对数据安全性要求比较高的场景,该选项会降低写入性能w=1:默认的writeConcern,数据写入到Primary就向客户端发送确认Read Concern推荐阅读MongoDB readConcern 原理解析spring.data.mongodb.uri=mongodb+srv://vulgar:761341@cluster0-t16it.mongodb.net/vulgar_test?retryWrites=true&w=majority配置mongoDB事务管理@Configurationpublic class MongoTransactionConfiguration { @Bean MongoTransactionManager mongoTransactionManager(MongoDbFactory factory) { return new MongoTransactionManager(factory); }}创建对应实体类User.class@Data@Document(collection = "user")public class User implements Serializable { private static final long serialVersionUID = -7257487638617643262L; private String username; private String password; private String sex; private Integer age; private String email;}Info.class@Data@Document(collection = "info")public class Info implements Serializable { private static final long serialVersionUID = 4494527542566322152L; private String username; private String description;}创建测试SERVICE@Slf4j@Service("mongoService")public class MongoService { @Autowired private MongoTemplate mongoTemplate; @Transactional(rollbackFor = ArithmeticException.class) public void updateWithTransaction() { Query query = new Query(Criteria.where("username").is("vulgar-cd")); Update update = new Update(); update.set("age", 10); mongoTemplate.updateFirst(query, update, User.class); User user = mongoTemplate.findOne(query, User.class); log.info("user is {}", JSON.toJSON(user)); update = new Update(); update.set("description", "hahahaha"); mongoTemplate.updateFirst(query, update, Info.class); Info info = mongoTemplate.findOne(query, Info.class); log.info("info is {}", JSON.toJSON(info)); //测试事务回滚 int i = 1/0; }}创建测试CONTROLLER@Slf4j@RestControllerpublic class MongoController { @Resource(name = "mongoService") private MongoService mongoService; @GetMapping("/transaction") public void updateWithTransaction() { mongoService.updateWithTransaction(); }}启动引用程序 ...

June 30, 2019 · 1 min · jiezi

Spring-Boot-JDBC-Mybatis-配置多数据源-以及-采用Durid-作为连接池

1 配置文件在配置文件中配置两个数据源配置,以及mybatis xml配置文件路径 # mybatis 多数据源配置mybatis.config-location = classpath:mapper/config/mybatis-config.xml################# mysql 数据源1 #################spring.datasource.one.jdbc-url=jdbc:mysql://localhost:3306/user?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=truespring.datasource.one.username=rootspring.datasource.one.password=root#spring.datasource.one.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.one.driver-class-name=com.mysql.jdbc.Driver################# mysql 数据源1 ################################## mysql 数据源2 ################spring.datasource.second.jdbc-url=jdbc:mysql://xxxxxxxxxx:3306/user?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=truespring.datasource.second.username=rootspring.datasource.second.password=root#spring.datasource.second.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.second.driver-class-name=com.mysql.jdbc.Driver################# mysql 数据源1 #################2 数据库配置代码:1 步骤1 首先加载配置的数据源:手动将数据配置文件信息注入到数据源实例对象中。2 根据创建的数据源,配置数据库实例对象注入到SqlSessionFactory 中,构建对应的 SqlSessionFactory。3 配置数据库事务:将数据源添加到事务中。4 将SqlSessionFactory 注入到SqlSessionTemplate 模板中5 最后将上面创建的 SqlSessionTemplate 注入到对应的 Mapper 包路径下,这样这个包下面的 Mapper 都会使用第一个数据源来进行数据库操作。 basePackages 指明 Mapper 地址。sqlSessionTemplateRef 指定 Mapper 路径下注入的 sqlSessionTemplate。在多数据源的情况下,不需要在启动类添加:@MapperScan("com.xxx.mapper") 的注解。2 项目结构: 3 第一个数据源@Api("SqlSessionTemplate 注入到对应的 Mapper 包路径下")@Configuration@MapperScan(basePackages = "com.example.demo.mapper.one", sqlSessionTemplateRef = "oneSqlSessionTemplate")public class OneDataSourceConfig { //------------------ 1 加载配置的数据源: ------------------------------- @Bean("oneDatasource") @ConfigurationProperties(prefix = "spring.datasource.one") @Primary //默认是这个库 public DataSource DataSource1Config(){ return DataSourceBuilder.create().build(); } //---------------------- 2 创建的数据源 构建对应的 SqlSessionFactory。 ---------------------- @Bean(name = "oneSqlSessionFactory" ) @Primary public SqlSessionFactory oneSqlSessionFactory(@Qualifier("oneDatasource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/one/*.xml")); return bean.getObject(); } //------------------------3 配置事务 -------------------------- @Bean(name = "oneTransactionManager") @Primary public DataSourceTransactionManager oneTransactionManager(@Qualifier("oneDatasource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } //------------------------------- 4 注入 SqlSessionFactory 到 SqlSessionTemplate 中--------------------------------- @Bean(name = "oneSqlSessionTemplate") @Primary public SqlSessionTemplate oneSqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); }}第二个数据源@Api("SqlSessionTemplate 注入到对应的 Mapper 包路径下")@Configuration@MapperScan(basePackages = "com.example.demo.mapper.second", sqlSessionTemplateRef = "secondSqlSessionTemplate")public class SecondDataSourceConfig { //------------------ 加载配置的数据源: ------------------------------- @Bean("secondDatasource") @ConfigurationProperties(prefix = "spring.datasource.second") public DataSource DataSource2Config(){ return DataSourceBuilder.create().build(); } //---------------------- 创建的数据源 构建对应的 SqlSessionFactory。 ---------------------- @Bean(name = "secondSqlSessionFactory") public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDatasource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/second/*.xml")); return bean.getObject(); } //------------------------ 配置事务 -------------------------- @Bean(name = "secondTransactionManager") public DataSourceTransactionManager secondTransactionManager(@Qualifier("secondDatasource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } //------------------------------- 注入 SqlSessionFactory 到 SqlSessionTemplate 中--------------------------------- @Bean(name = "secondSqlSessionTemplate") public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); }}3 xml文件<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration></configuration>4 mapper 类public interface User1Mapper { public void inserts(User user);}public interface User2Mapper { public void inserts(User user);}5 mybatis mapper.xml<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.demo.mapper.one.User1Mapper"> <insert id="inserts" parameterType="com.example.demo.pojo.User" useGeneratedKeys="true" keyProperty="id"> insert into user(`name`,age) VALUE (#{name},#{age}) </insert> </mapper><?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.demo.mapper.one.User2Mapper"> <insert id="inserts" parameterType="com.example.demo.pojo.User" useGeneratedKeys="true" keyProperty="id"> insert into user(`name`,age) VALUE (#{name},#{age}) </insert> </mapper>3 启动成功表示数据源创建成功,这里连接池采用springboot默认的Hikari数据库连接池(不需要配置) ...

June 29, 2019 · 2 min · jiezi

Springboot整合Hibernate拦截器时无法向拦截器注入Bean

开发环境JDK 1.8Springboot 2.1.1.RELEASEpom配置 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>关键代码实体类@Entitypublic class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }}Repositorypublic interface UserRepository extends JpaRepository<User,Integer> {}自定义服务@Servicepublic class MyService { public void print(){ System.out.println(this.getClass().getSimpleName()+" call"); }}拦截器public class SimpleInterceptor extends EmptyInterceptor { @Resource private MyService myService; @Override public String onPrepareStatement(String sql) { myService.print(); System.out.println("sql:"+sql); return super.onPrepareStatement(sql); }}启动类@SpringBootApplicationpublic class BootHibernateInterceptorProblemApplication { public static void main(String[] args) { SpringApplication.run(BootHibernateInterceptorProblemApplication.class, args); }}配置## DataSourcespring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=truespring.datasource.username=rootspring.datasource.password=123456789## hibernatespring.jpa.hibernate.ddl-auto=update## add interceptorspring.jpa.properties.hibernate.ejb.interceptor=com.rjh.interceptor.SimpleInterceptor单元测试类@RunWith(SpringRunner.class)@SpringBootTestpublic class BootHibernateInterceptorProblemApplicationTests { @Resource private UserRepository userRepository; @Test public void contextLoads() { System.out.println(userRepository.findAll()); }}运行结果java.lang.NullPointerException at com.rjh.interceptor.SimpleInterceptor.onPrepareStatement(SimpleInterceptor.java:20) ... ... ...分析根据异常信息,猜测是注入MyService失败 ...

June 28, 2019 · 2 min · jiezi