Skip to the content.

[TOC]

SpringBoot系列

官方文档:https://docs.spring.io/spring-boot/docs/2.0.8.RELEASE/reference/htmlsingle/

SpringBoot开启事务

一、介绍

查询规范

Spring Data JPA提供的一个查询规范,查询语句关键字,简单的SQL可根据方法命名来即可,省略了写sql语句。

关键字                    方法命名                                 sql where字句
And                     findByNameAndPwd                    where name= ? and pwd =?
Or                      findByNameOrSex                     where name= ? or sex=?
Is,Equals               findById,findByIdEquals             where id= ?
Between                 findByIdBetween                     where id between ? and ?
LessThan                findByIdLessThan                    where id < ?
LessThanEquals          findByIdLessThanEquals              where id <= ?
GreaterThan             findByIdGreaterThan                 where id > ?
GreaterThanEquals       findByIdGreaterThanEquals           where id > = ?
After                   findByIdAfter                       where id > ?
Before                  findByIdBefore                      where id < ?
IsNull                  findByNameIsNull                    where name is null
isNotNull,NotNull       findByNameNotNull                   where name is not null
Like                    findByNameLike                      where name like ?
NotLike                 findByNameNotLike                   where name not like ?
StartingWith            findByNameStartingWith              where name like ‘?%’
EndingWith              findByNameEndingWith                where name like ‘%?’
Containing              findByNameContaining                where name like ‘%?%’
OrderBy                 findByIdOrderByXDesc                where id=? order by x desc
Not                     findByNameNot                       where name <> ?
In                      findByIdIn(Collection<?> c)         where id in (?)
NotIn                   findByIdNotIn(Collection<?> c)      where id not in (?)
True                    findByAaaTue                        where aaa = true
False                   findByAaaFalse                      where aaa = false
IgnoreCase              findByNameIgnoreCase                where UPPER(name)=UPPER(?)

二、配置

以SpringDataJpa为例

1、配置jpa生成数据库平台

(注意:mysql的InnoDB支持事务,MyISAM不支持事务)

    jpa:
        generateDdl: false
        properties:
            hibernate:
                show_sql: true
                # 选配,自行在mysql中设置库表引擎为InnoDB,可不需要配置
                database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

2、类方法配置注解

(注意:SpringBoot2.0后,动态代理默认使用Cglib代理,基于类方法的代理,而Jdk代理基于接口,这里先基于类方法代理,下面在具体说明)

@Transactional(rollbackOn = Exception.class)
public LoginInfoEntity register(String loginName, String password, String userName) {
 		...
         // 手动抛异常
         int i = 1 / 0
		 return loginInfoEntity;
}

3、写测试类

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
 * UserServiceTest
 *
 * @author sjp
 * @date 2019/5/4
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

	@Resource
	private UserService userService;

	@Test
	public void register() {
		userService.register("test9", "test9", "test9");
	}
}

注意:别忘记方法类,手动抛异常,int i = 1 / 0;

Tip1:关于代理的proxy-target-class参数设置?

解决:proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用

spring:
    aop:
   	   # SpringBoot2.0后默认是true
        proxy-target-class: true

Tip2:@EnableTransactionManagement 咋回事,要不要在启动类设置?

解决:SpringBoot2.0后不需要,默认是加载事务管理器的

博客https://www.cnblogs.com/alimayun/p/10296340.html

https://blog.csdn.net/equaker/article/details/81534922

三、踩坑

SpringBoot引入AOP

一、介绍

二、配置

博客:https://blog.csdn.net/huang_550/article/details/53672321

https://www.cnblogs.com/onlymate/p/9630788.html

1、配置

三、踩坑

SpringBoot上传文件

一、介绍

二、整合

 @PutMapping("/upload")
 public SearchResponse upload(MultipartFile[] files) {
    SearchResponse response = new SearchResponse();
    return response;
 }

三、采坑

1、上传文件受大小限制

Spring Boot:The field file exceeds its maximum permitted size of 1048576 bytes.

解决:

Spring Boot1.4版本后配置更改为:

spring.http.multipart.maxFileSize = 10Mb  
spring.http.multipart.maxRequestSize=100Mb  

Spring Boot2.0之后的版本配置修改为:

spring.servlet.multipart.max-file-size = 10MB  
spring.servlet.multipart.max-request-size=100MB

2、复杂类型无法映射到对象中去

Controller:

 @PutMapping("/upload")
    public SearchResponse upload(SearchRequest request, MultipartFile[] files) {
        // TODO 为何映射不到复杂类型里 待解决
        SearchResponse response = new SearchResponse();
        request.setFiles(files);
        searchService.upload(request, response);
        return response;
    }

entity

public class SearchRequest extends BaseRequest {
    @NotBlank
    private String key;

    private MultipartFile[] files;

    public MultipartFile[] getFiles() {
        return files;
    }

    public void setFiles(MultipartFile[] files) {
        this.files = files;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

(待解决)

SpringBoot整合shiro

一、介绍

二、整合

1、导入jar

<shiro.version>1.3.2</shiro.version>
<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>

2、配置类

import com.lemon.relam.LoginRelam;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author shijianpeng
 */
@Configuration
public class ShiroConfig {

    /**
     * 使用FilterRegistrationBean管理DelegatingFilterProxy的生命周期,代替spring项目中shiro在web.xml的配置
     *
     * @return FilterRegistrationBean<DelegatingFilterProxy>
     */
	@Bean
	public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
		FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
		DelegatingFilterProxy proxy = new DelegatingFilterProxy();
		filterRegistrationBean.setFilter(proxy);
		// 保留Filter原有的init,destroy方法的调用
		proxy.setTargetFilterLifecycle(true);
		proxy.setTargetBeanName("shiroFilter");
		return filterRegistrationBean;
	}

	/**
	 * 配置shiro filter
	 *
	 * @return ShiroFilterFactoryBean
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shirFilter() {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager());
		shiroFilterFactoryBean.setLoginUrl("/u/index");
		shiroFilterFactoryBean.setSuccessUrl("/u/main");
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/u/video/get", "anon");
		filterChainDefinitionMap.put("/static/**", "anon");
		filterChainDefinitionMap.put("/u/user/login", "anon");
		filterChainDefinitionMap.put("/u/user/logout", "logout");

		filterChainDefinitionMap.put("/**", "authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * 配置securityManager
	 *
	 * @return SecurityManager
	 */
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(loginRealm());
		return securityManager;
	}

	/**
	 * 配置shiroRelam并指定凭证匹配器
	 *
	 * @return LoginRelam
	 */
	@Bean
	public LoginRelam loginRealm() {
		LoginRelam loginRelam = new LoginRelam();
		loginRelam.setCredentialsMatcher(hashedCredentialsMatcher());
		return loginRelam;
	}

	/**
	 * 凭证匹配器
	 * 
	 * @return HashedCredentialsMatcher
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("md5");
		hashedCredentialsMatcher.setHashIterations(5);
		return hashedCredentialsMatcher;
	}

	/// @Bean
	// public ShiroDialect shiroDialect() {
	// return new ShiroDialect();
	// }

	/**
	 * 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
	 *
	 * @return LifecycleBeanPostProcessor
	 */
	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用
	 *
	 * @return DefaultAdvisorAutoProxyCreator
	 */
	@Bean
	@DependsOn("lifecycleBeanPostProcessor")
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
		/// proxyCreator.setProxyTargetClass(true);
		return proxyCreator;
	}

	/**
	 * 开|启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
	 * 
	 * @return AuthorizationAttributeSourceAdvisor
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
		AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager());
		return advisor;
	}

	// @Bean
	// public SessionManager configWebSessionManager(){
	// DefaultWebSessionManager manager = new DefaultWebSessionManager();
	// // 加入缓存管理器
	// //manager.setCacheManager(cacheManager);
	// //manager.setSessionDAO(sessionDao);
	// // 删除过期的session
	// manager.setDeleteInvalidSessions(true);
	// // 设置全局session超时时间
	// manager.setGlobalSessionTimeout(1800000);
	// // 是否定时检查session
	// manager.setSessionValidationSchedulerEnabled(true);
	// manager.setSessionIdCookie(simpleCookie());
	// return manager;
	// }

	// /**
	// * 注入cookie模板
	// * @return
	// */
	// @Bean
	// public SimpleCookie simpleCookie(){
	// SimpleCookie simpleCookie = new SimpleCookie("sid-shrio");
	// simpleCookie.setMaxAge(-1);
	// simpleCookie.setHttpOnly(true);
	// return simpleCookie;
	// }
}

3、常用

A、FormAuthenticationFilter

继承FormAuthenticationFilter实现一些

SpringBoot整合SpringData

三、踩坑

1、save无法获取自增主键

解决方案:


    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "article_id", nullable = false)
    public int getArticleId() {
        return articleId;
    }
 
    public void setArticleId(int articleId) {
        this.articleId = articleId;
    }
 ArticleEntity article=articleRepository.saveAndFlush(articleEntity);
 int id=article.getArticleId();

SpringBoot整合UrlRewrite

一、介绍

如楼主般配置,shiro和Urlrewirte整合,shiro的拦截器首先执行,后续才是urlrewrite的拦截器

验证:urlrewrite拦截器打上断点 org.tuckey.web.filters.urlrewrite.UrlRewriteFilter#doFilter

​ shiro的拦截器打上断点 org.apache.shiro.web.servlet.OncePerRequestFilter#doFilter

二、整合

1、导入jar

<urlrewrite.version>3.2.0</urlrewrite.version>
<!-- urlRewrite -->
<dependency>
    <groupId>org.tuckey</groupId>
    <artifactId>urlrewritefilter</artifactId>
    <version>${urlrewrite.version}</version>
</dependency>

2、配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * @author sjp
 * @date 2019/1/19
 **/
@Configuration
public class UrlRewriteFilterConfig extends UrlRewriteFilter {

    @Value("classpath:/urlRewrite/urlRewrite.xml")
    private Resource resource;

    @Override
    protected void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
        try {
            // lemon-user为系统名
            Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(),
                "lemon-user");
            checkConf(conf);
        } catch (IOException ex) {
            throw new ServletException("Unable to load URL rewrite configuration file from classpath:/urlRewrite/urlRewrite.xml", ex);
        }
    }

}

三、采坑

问题:SpringBoot整合shiro和UrlRewrite,请求接口一直报错。

推荐文章:Spring Boot整合shiro出现UnavailableSecurityManagerException

org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.

解决:

/**
 * 使用FilterRegistrationBean管理DelegatingFilterProxy的生命周期,代替spring项目中shiro在web.xml的配置
 *
 * @return FilterRegistrationBean<DelegatingFilterProxy>
 */
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
    FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
    DelegatingFilterProxy proxy = new DelegatingFilterProxy();
    filterRegistrationBean.setFilter(proxy);
    // 保留Filter原有的init,destroy方法的调用
    proxy.setTargetFilterLifecycle(true);
    proxy.setTargetBeanName("shiroFilter");
    return filterRegistrationBean;
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
    ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
    // .......
    return filterFactoryBean;
}

SpringBoot-Actuator 健康监控

一、介绍

推荐文章:https://www.jianshu.com/p/1aadc4c85f51

二、整合

1、导入jar

<!-- 健康监控 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、配置yml

# actuator 监控
management:
    endpoints:
        web:
            exposure:
                # 开启全部监控请求
                include: "*"
    server:
        # 监控端口
        port: 10111
        servlet:
            context-path: /
        ssl:
            enabled: false
    endpoint:
        health:
            # heath展示全部信息
            show-details: always

info:
    app.name: eureka-provider
    company.name: www.demo.com
    build.artifactId: project.artifactId
    build.version: 0.0.1

注意:

监控本身不具有安全认证,所以一般如果要实际使用,需配合 spring-boot-starter-security 使用,另外,actuator自带监控可能不满足场景业务需求,所以可以集成自己的需要的监控。

SpringBoot-整合UrlRewriter

一、介绍

 Urlrewriter的作用主要是重写url路径,以此来隐藏真实的路径,如:”http://www.xxxx.com/crm_index.do”,而真实访问的是”http://www.xxxx.com/crm/index.jsp”。还有一种就是,在高并发访问的时候,可以更具是否用静态文件来进行路径的重写。

二、整合

第一步 jar包

<dependency>
     <groupId>org.tuckey</groupId>
     <artifactId>urlrewritefilter</artifactId>
     <version>4.0.3</version>
</dependency>

第二步 配置类


import java.io.IOException;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;

@Configuration
public class UrlRewriteFilterConfig extends UrlRewriteFilter {

    @Value("classpath:/urlRewrite.xml")
    private Resource resource;

    @Override
    protected void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
        try {
            Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(),
                "lemon-user");
            checkConf(conf);
        } catch (IOException ex) {
            throw new ServletException("Unable to load URL rewrite configuration file from classpath:/urlRewrite.xml", ex);
        }
    }
}

第三步 配置文件

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 4.0//EN"
    "http://www.tuckey.org/res/dtds/urlrewrite4.0.dtd">

<urlrewrite>
    <rule>
        <from>^/test.html$</from>
        <to type="redirect">/test</to>
    </rule>
</urlrewrite>

到此整合完成,下面我们进行测试,启动项目,访问127.0.0.1:80/test.html,回车后url变为127.0.0.1:80/test,即成功。

小提示:

问:应用拆分,同一个域名,如何映射各个应用?

(总不至于,每个接口的url,前面都加上应用区分的url吧,如“/u/user/login”,”/a/admin/login”,/u 和 /a 仅仅用来区分应用,如果我们在代码中都加上,肯定不是一个好的解决方案)

答:在应用端,使用UrlRewriter帮助我们重写请求,这样可以做到代码层面不加上区分应用的Url,直接配置在UrlRewriter中,重写Url即可。(解决方案不止这一种)

UrlRewriter快速了解文章入口: 关于UrlRewrite的使用

Springboot整合Redis

Springboot整合Redisson

一、介绍

二、整合

1、引入POM

        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>
        <redisson.version>3.10.6</redisson.version>
        <redisson-springboot.version>3.10.6</redisson-springboot.version>
         
         <!-- redisson -->
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-boot-starter</artifactId>
             <version>${redisson-springboot.version}</version>
         </dependency>
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson</artifactId>
             <version>${redisson.version}</version>
         </dependency>

2、配置文件

Tips:新建redisson-config.yml配置文件

# Redisson配置
singleServerConfig:
    address: "redis://127.0.0.1:6379"
    password: null
    clientName: null
    database: 0
    idleConnectionTimeout: 10000
    pingTimeout: 1000
    connectTimeout: 10000
    timeout: 3000
    retryAttempts: 3
    retryInterval: 1500
    reconnectionTimeout: 3000
    failedAttempts: 3
    subscriptionsPerConnection: 5
    subscriptionConnectionMinimumIdleSize: 1
    subscriptionConnectionPoolSize: 50
    connectionMinimumIdleSize: 32
    connectionPoolSize: 64
    dnsMonitoringInterval: 5000
    # dnsMonitoring: false

threads: 0
nettyThreads: 0
codec:
    class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"

3、写配置类

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

/**
 * @author sjp
 * @date 2019/4/30
 **/
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redisson() throws IOException {
        // 本模块使用的是yaml格式的配置文件,读取使用Config.fromYAML,如果是Json文件,则使用Config.fromJSON
        Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
        return Redisson.create(config);
    }

}

4、测试类

import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RBucket;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@RunWith(SpringRunner.class)
@Component
public class RedissonTest {

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private RedisTemplate<String, String> template;

    @Test
    public void set() {
        redisson.getKeys().flushall();
        RBucket<String> rBucket =  redisson.getBucket("key");
        rBucket.set("1231");
        RMap<String, String> m = redisson.getMap("test", StringCodec.INSTANCE);
        m.put("1", "2");
        BoundHashOperations<String, String, String> hash = template.boundHashOps("test");
        String t = hash.get("1");
        assertThat(t).isEqualTo("2");
    }

}

三、踩坑

1、redisson连接池的连接无法释放

Springboot2.1.1 之前的版本在接入redisson的时候,redission无法自动释放redis连接,会导致池中连接都在占用状态,后续的请求将无法从连接池中获取连接,导致请求阻塞。

解决方案,使用 2.1.1及之后的版本即可

// TODO 具体原因待分析。

2、开发阶段应用经常重启,导致redis已经被应用占用的连接无法释放。

// TODO 待解决方案,在系统关闭时,调用redisson.shutdown()