搜索词>>@Transactional 耗时0.0070
  • Spring框架事物管理入门教程@Transactional注解使用

    Spring框架是一个非常智能的框架,本文主要讲解spring框架中事物的处理。使用@Transactional注解来标注事物的处理类型等属性<h2>事务有四个特性:ACID</h2> <ul> <li>原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。</li> <li>一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。</li> <li>隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。</li> <li>持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。</li> </ul> Spring框架事务管理涉及的接口的联系如下:<br /> <img alt="1" class="img-thumbnail" src="/assist/images/blog/6bca1098-a091-45f7-8eee-ce32549a556f.jpg" style="height:413px; width:945px" /><br /> @Transactional的属性<br />   <table border="1" class="table table-bordered table-hover"> <tbody> <tr> <td>属性名 </td> <td>类型 </td> <td>说明 </td> </tr> <tr> <td>isolation </td> <td>枚举org.springframework.transaction.annotation.Isolation的值 </td> <td>事务隔离级别 ,默认值Isolation.DEFAULT</td> </tr> <tr> <td>noRollbackFor </td> <td>Class<? extends Throwable>[] </td> <td>一组异常类,遇到时不回滚。默认为{}</td> </tr> <tr> <td>noRollbackForClassName </td> <td>Stirng[] </td> <td>一组异常类名,遇到时不回滚,默认为{}</td> </tr> <tr> <td>propagation </td> <td>枚举org.springframework.transaction.annotation.Propagation的值 </td> <td>事务传播行为 ,默认值 Propagation.REQUIRED</td> </tr> <tr> <td>readOnly </td> <td>boolean </td> <td>事务读写性 ,默认false</td> </tr> <tr> <td>rollbackFor </td> <td>Class<? extends Throwable>[] </td> <td>一组异常类,遇到时回滚 </td> </tr> <tr> <td>rollbackForClassName </td> <td>Stirng[] </td> <td>一组异常类名,遇到时回滚 </td> </tr> <tr> <td>timeout </td> <td>int </td> <td>超时时间,以秒为单位 ,ransactionDefinition.TIMEOUT_DEFAULT</td> </tr> <tr> <td>value </td> <td>String </td> <td>可选的限定描述符,指定使用的事务管理器 </td> </tr> </tbody> </table> <h2><br /> 事务传播行为类型:</h2> <table border="1" class="table table-bordered table-hover"> <tbody> <tr> <td>事务传播行为类型</td> <td>说明</td> </tr> <tr> <td>PROPAGATION_REQUIRED</td> <td>如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。<span style="color:#ff0000">默认值</span></td> </tr> <tr> <td>PROPAGATION_SUPPORTS</td> <td>支持当前事务,如果当前没有事务,就以非事务方式执行。</td> </tr> <tr> <td>PROPAGATION_MANDATORY</td> <td>使用当前的事务,如果当前没有事务,就抛出异常。</td> </tr> <tr> <td>PROPAGATION_REQUIRES_NEW</td> <td>新建事务,如果当前存在事务,把当前事务挂起。</td> </tr> <tr> <td>PROPAGATION_NOT_SUPPORTED</td> <td>以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。</td> </tr> <tr> <td>PROPAGATION_NEVER</td> <td>以非事务方式执行,如果当前存在事务,则抛出异常。</td> </tr> <tr> <td>PROPAGATION_NESTED</td> <td>如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作。</td> </tr> </tbody> </table> <h1><br /> 事物隔离级别</h1>   <table class="table table-bordered table-hover" border="0" cellpadding="0" cellspacing="0" style="width:888px"> <tbody> <tr> <td>隔离级别          </td> <td>隔离级别的值</td> <td>导致的问题</td> </tr> <tr> <td>Read-Uncommitted</td> <td>0     </td> <td>导致脏读</td> </tr> <tr> <td>Read-Committed   </td> <td>1     </td> <td>避免脏读,允许不可重复读和幻读</td> </tr> <tr> <td>Repeatable-Read   </td> <td>2     </td> <td>避免脏读,不可重复读,允许幻读</td> </tr> <tr> <td>Serializable   </td> <td>3     </td> <td>串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重</td> </tr> </tbody> </table> <br /> <strong>名词解释:</strong> <ul> <li><strong>脏读:</strong>一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。</li> <li><strong>不可重复读</strong>:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。</li> <li><strong>幻读</strong>:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。</li> </ul> <br /> 提示: <ul> <li>隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。</li> <li>大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle</li> <li>少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB</li> </ul> <h3><br /> Spring 中的隔离设置</h3> ISOLATION_DEFAULT     这是个 PlatfromTransactionManager <span style="color:#ff0000">默认的隔离级别</span>,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。<br /> ISOLATION_READ_UNCOMMITTED     这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。<br /> ISOLATION_READ_COMMITTED     保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。<br /> ISOLATION_REPEATABLE_READ     这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。<br /> ISOLATION_SERIALIZABLE     这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。<br /> <br /> <span style="color:#ff0000"><strong>注意:</strong></span><br /> <strong><span style="color:#16a085">1.使用注解事物,必须在spring配置中开启事物<br /> 2.@Transactional注解可以标注在类和方法上,也可以标注在定义的接口和接口方法上。<br /> 如果我们在接口上标注@Transactional注解,会留下这样的隐患:因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务实现类继承。所以可能会出现不启动事务的情况。所以,spring建议我们将@Transaction注解在实现类上。<br /> 在方法上的@Transactional注解会覆盖掉类上的@Transactional。</span></strong>
  • Spring Boot Mybatis Shiro 中出现事务不生效原因及解决办法

    出现@Transactional事务不生效原因shiro 的Realm 中注入了用到事务的service,例如下面的​ /** * 自定义权限认证器 * 自定义实现Realm,实现自定义获取登录信息进行登录鉴权和获取权限信息进行权限鉴权出现@Transactional事务不生效原因shiro 的Realm 中注入了用到事务的service,例如下面的​ /** * 自定义权限认证器 * 自定义实现Realm,实现自定义获取登录信息进行登录鉴权和获取权限信息进行权限鉴权 */ public class UserRealm extends AuthorizingRealm {    //超管账号    @Value("${super.user}")    String su;            @Autowired    SystemUserService systemUserService; ​      @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {       // TODO CODE ...        return info;   } ​    /***     * 登录鉴定(就是鉴定用户是否登录)     * @param authenticationToken     * @return     * @throws AuthenticationException     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {       // TODO CODE ...        return info;   } } ​按上方代码使用,SystemUserService 这个service中所有的事务都将不会生效。原因细节首先我们在项目整合Shiro的时候通过ShiroConfig做了一些配置,其中一项包括Shiro的授权认证器MemberAuthorizingRealm。在UserRealm中我们通过@Autowired注入了本篇的主角SystemUserService。Spring启动的时候,配置相关的都是优先初始化的,在初始化UserRealm的时候发现需要注入一个SystemUserService对象,容器里肯定是没有的,那么就提前将其初始化了。此时如果在MemberService还有通过@Autowired注入的其他依赖,那么会一并初始化,依赖中要是还有依赖会继续递归初始化,这样下来会导致一系列的实例都是没有被代理的。但是这时候Spring中创建代理的处理器是还没有的,导致SystemUserService的BeanPostProcessor中没有AbstractAutoProxyCreator这个对象,后面整个BeanPostProcessor列表执行的时候没有为其创建代理。Spring中的数据库事务都是需要代理支持的,所以MemberService中不能开启事务。解决办法方法一(推荐):在Realm中注入的service上面添加@Lazy注解/** * 自定义权限认证器 * 自定义实现Realm,实现自定义获取登录信息进行登录鉴权和获取权限信息进行权限鉴权 */ public class UserRealm extends AuthorizingRealm {    //超管账号    @Value("${super.user}")    String su;        @Lazy    @Autowired    SystemUserService systemUserService; ​      @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {       // TODO CODE ...        return info;   } ​    /***     * 登录鉴定(就是鉴定用户是否登录)     * @param authenticationToken     * @return     * @throws AuthenticationException     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {       // TODO CODE ...        return info;   } }方法二:单独创建一个service直接和mapper打交道,然后注入到Realm中方法三:通过spring application getBean获取 service
  • Spring Boot 事物回滚实验

    1.开启事物通过注解开启@EnableTransactionManagement 提示: Spring Boot  以Spring 5.0为基础的版本无需注解自动开启事物2.测试事物首先查询数据库中的表​默认表中有一条数据;2.1默认不用事1.开启事物通过注解开启@EnableTransactionManagement 提示: Spring Boot  以Spring 5.0为基础的版本无需注解自动开启事物2.测试事物首先查询数据库中的表​默认表中有一条数据;2.1默认不用事物首先是不开启,创建两个数据代码如下:public void test() { SystemMenu m1=new SystemMenu(); m1.setName("test2"); m1.setTerminal("00"); menuExtMapper.insertSelective(m1); SystemMenu m2=new SystemMenu(); m2.setId(1);//重复ID 数据库会报错 m2.setName("test3"); m2.setTerminal("00"); }执行上面的代码:​通过控制台我们可以看到已经输出了 主键冲突的异常,我们再看看数据库:​通过数据库查询可以看到,test2数据已经存入数据库,test3肯定就是失败了,同时证明了默认没开启事物,遇到错误并不会回滚。2.2通过注解@Transactional开启事物首先我们将数据库还原,删除掉test2数据,如下:​程序代码如下:@Transactional public void test() { SystemMenu m1=new SystemMenu(); m1.setName("test2"); m1.setTerminal("00"); menuExtMapper.insertSelective(m1); SystemMenu m2=new SystemMenu(); m2.setId(1);//重复ID 数据库会报错 m2.setName("test3"); m2.setTerminal("00"); menuExtMapper.insertSelective(m2); }执行上方代码:​从控制台看,同样输出了主键冲突的错误,我们再看看数据库中的数据:​从数据可以看到,并没有数据存入。这里也就证明了事物开启成功,遇到错误就会同一回滚保证了数据的一致性。提示:默认情况下,@Transactional注解只对RuntimeException及其子类异常有效。证明上方的观点,下面以代码示例:@Transactional public void test() throws Exception { SystemMenu m1=new SystemMenu(); m1.setName("test2"); m1.setTerminal("00"); menuExtMapper.insertSelective(m1); if (1==1){ throw new Exception("EEEEEE"); } SystemMenu m2=new SystemMenu(); m2.setId(1);//重复ID 数据库会报错 m2.setName("test3"); m2.setTerminal("00"); menuExtMapper.insertSelective(m2); }上方代码,在处理数据中间主动抛出了Exception异常,执行代码观察控制台以及数据库:​控制台​数据库通过上方我们可以看到,抛出Exception的情况下事物并没有生效,test2已经存入数据库中。提示:可以通过注解@Transactional中的rollbackFor 来指定事物遇到那些异常会启用,同样也可以通过noRollbackFor指定那些异常不会滚例如下方的配置事物遇到所有的Exception都会回滚@Transactional(rollbackFor = {Exception.class})2.3 启用事物的方法内部调用其他方法出现异常会回滚吗首先我们蒋数据库恢复如下:​编写测试代码如下:@Transactional(rollbackFor = {Exception.class}) public void test() throws Exception { SystemMenu m1=new SystemMenu(); m1.setName("test2"); m1.setTerminal("00"); menuExtMapper.insertSelective(m1); oo(); } public void oo(){ SystemMenu m2=new SystemMenu(); m2.setId(1); m2.setName("test2"); m2.setTerminal("00"); menuExtMapper.insertSelective(m2); }执行代码,观察控制台和数据库:​控制台​数据库通过上方实验,我们可以看到事物生效了。所以内部调用其他方法出现异常同样会触发事物提示:调用的方法需没有标注注解@Transactional,如果标注注解会有几种情况,后方说明。2.4 启用事物的方法内部调用其他启用事物的方法编写测试代码1: @Transactional(rollbackFor = {Exception.class}) public void test() throws Exception { oo(); SystemMenu m1=new SystemMenu(); m1.setId(1); m1.setName("test2"); m1.setTerminal("00"); menuExtMapper.insertSelective(m1); } @Transactional public void oo(){ SystemMenu m2=new SystemMenu(); m2.setName("test3"); m2.setTerminal("00"); menuExtMapper.insertSelective(m2); }执行测试代码并观察控制台和数据库:​控制台​数据库通过上方实验,我们可以知道默认情况下,@Transactional注解的方法调用@Transactional注解的方法也是可以事物统一回滚的。注意:调用的方法上指定的Exception范围必须包含被调用方抛出异常的范围,否则可能出现不一致上方还涉及到一个知识点,事物的传播级别,默认情况下事物的传播级别为"REQUIRED",即:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。所以上方两个方法的事物会被整合成一个事物处理。@Transactional注解也可以通过propagation 来指定传播级别,如下:@Transactional(propagation = Propagation.REQUIRED)事物传播的几个级别分别为:REQUIRED(@Transactional注解默认值): 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。SUPPORTS:前方法不需要事务上下文,但是如果存在当前事务的话,那么这个方法会在这个事务中运行。MANDATORY:该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。不会主动开启一个事务。REQUIRES_NEW:前方法必须运行在它自己的事务中。一个新的事务将被启动,如果存在当前事务,在该方法执行期间,当前事务会被挂起(如果一个事务已经存在,则先将这个存在的事务挂起)。如果使用JTATransactionManager的话,则需要访问TransactionManager。NOT_SUPPORTED:该方法不应该运行在事务中,如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager。NEVER:表示当前方法不应该运行在事务上下文中,如果当前正有一个事务在运行,则会抛出异常。NESTED:如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与REQUIRED一样。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。(未完待续)后续继续更新实验....
  • spring boot mybatis 整合_spring boot mybatis3 事物配置

    引言    通过之前spring boot mybatis 整合的讲解: spring boot mybaties整合  (spring boot mybaties 整合 基于Java注解方式写sql,无需任何得mapper xml文件)s引言    通过之前spring boot mybatis 整合的讲解: spring boot mybaties整合  (spring boot mybaties 整合 基于Java注解方式写sql,无需任何得mapper xml文件)spring boot mybatis 整合_spring boot与mybaties的使用  (spring boot mybaties 整合 xml mapper方式,也是实际应用最多得方式) 我们对于spring boot mybaties 整合有了一个基础的认知。这里主要正对上面得两篇文章中spring boot mybaties整合讲解得一个扩展学习,事物的配置,整合到spring 的事物控制中。一.环境准备 本博客讲沿用上面的项目进行进一步讲解二.实战编码2.1 spring boot 核心配置文件application.properties#==================DataSource Config Start================== #默认采用Tomcat-jdbc-pool性能和并发最好,注意查看maven依赖中是否有tomcat-jdbc #name #spring.datasource.name=test #url #spring.datasource.url=jdbc:sqlserver://192.168.xxx.xxx;instanceName=sql_03;DatabaseName=edu;integratedSecurity=false spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8 #DriverClass #spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.tomcat.driver-class-name=com.mysql.jdbc.Driver #DB username spring.datasource.tomcat.username=root #DB password spring.datasource.tomcat.password=root #最大连接数量 spring.datasource.tomcat.max-active=150 #最大闲置连接数量 spring.datasource.tomcat.max-idle=20 #最大等待时间 #spring.datasource.tomcat.max-wait=5000 #==================DataSource Config End================== #==================mybaties Config Start================== #ORM Bean Package mybatis.type-aliases-package=com.example.pojo mybatis.mapper-locations=classpath:/mapper/*.xml #打印mybatiesSql语句 logging.level.com.example.mapper=DEBUG #==================mybaties Config End ================== #模板引擎配置缓存为FALSE。开发调试用 spring.thymeleaf.cache=false 这里注意关注数据连接配置和mybaties的xml mapper文件配置。2.2spring boot mybaties 整合 事物关键配置 MyBatiesConfig.javapackage com.example.config; import javax.sql.DataSource; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * mybaties配置扫描mapper路径 * * @author leftso * */ @Configuration @MapperScan(basePackages = { "com.example.mapper" }) /** 注意,这个注解是扫描mapper接口不是xml文件,使用xml模式必须在配置文件中添加xml的配置 **/ @EnableTransactionManagement /** * 启用事物管理 ,在需要事物管理的service类或者方法上使用注解@Transactional **/ public class MyBatiesConfig { @Autowired private DataSource dataSource; /** * 配合注解完成事物管理 * * @return */ @Bean public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(dataSource); } } 注意必须把当前的数据源配置进入spring的注解事物管理器。否则通过spring框架的注解标签@Transactional是不会有事物作用的。提示:spring boot 2.1.4.RELEASE 版本无需配置PlatformTransactionManager 也能起作用,也就说仅需要一个注解@EnableTransactionManagementSpring boot 2.x (Spring 5.0为基础的情况)无需使用@EnableTransactionManagement注解,spring boot 项目内部已经启用三.事物演示3.1编写测试代码package com.example.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.example.mapper.UserMapper; import com.example.pojo.User; @RunWith(SpringRunner.class) @SpringBootTest public class TransactionalTest { @Autowired private UserMapper userMapper; @Test public void name() { User user=new User("leftso", "男", 1); userMapper.insert(user); int t=1/0; System.out.println(t); } } 执行前查询数据库:​执行测试代码并观察eclipse的控制台和数据库的数据查询结果:​​很明显在报错的情况下,数据还是插入进了数据库。这并不是我们正常业务想要的结果。3.2编辑测试代码,添加spring框架的事物注解package com.example.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import com.example.mapper.UserMapper; import com.example.pojo.User; @RunWith(SpringRunner.class) @SpringBootTest public class TransactionalTest { @Autowired private UserMapper userMapper; @Test @Transactional public void name() { User user=new User("测试哈哈", "女", 2); userMapper.insert(user); int t=1/0; System.out.println(t); } } 执行代码并观察eclipse和数据库:​​这次的操作姿势似乎对了。在报错的情况下数据并没有插入数据库。我们仔细观察spring 控制台输出的日志可以发现事物已经在spring的控制下回滚了。​从上图也可以看到回滚的日志
  • spring boot 入门 整合security jpa角色和权限的实现

    1.概述本文继续使用spring boot 和Spring Security系列进行注册,并着重于如何正确实现角色和权限1.概述本文继续使用spring boot 和Spring Security系列进行注册,并着重于如何正确实现角色和权限。2. 用户 角色和权限首先,让我们从我们的实体开始。我们有三个主要实体:UserRole -这代表在系统中的用户的高级别角色; 每个角色都将拥有一组低级权限Privilege  -表示系统中一个低级别的,细小的特权/权限User.java:@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private String password; private boolean enabled; private boolean tokenExpired; @ManyToMany @JoinTable( name = "users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collection<Role> roles; }如您所见,用户包含角色,但也包含适当注册机制所需的一些其他详细信息。接下来 - 这是Role.java:@Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "roles") private Collection<User> users; @ManyToMany @JoinTable( name = "roles_privileges", joinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "privilege_id", referencedColumnName = "id")) private Collection<Privilege> privileges; }最后是Privilege:@Entity public class Privilege { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "privileges") private Collection<Role> roles; } 如您所见,我们正在考虑用户角色以及角色特权关系多对多双向。3.设置权限和角色接下来 - 让我们专注于在系统中对权限和角色进行一些早期设置。我们将把它与应用程序的启动绑定在一起,我们将在ContextRefreshedEvent上使用ApplicationListener在服务器启动时加载我们的初始数据:@Component public class InitialDataLoader implements ApplicationListener<ContextRefreshedEvent> { boolean alreadySetup = false; @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private PrivilegeRepository privilegeRepository; @Autowired private PasswordEncoder passwordEncoder; @Override @Transactional public void onApplicationEvent(ContextRefreshedEvent event) { if (alreadySetup) return; Privilege readPrivilege = createPrivilegeIfNotFound("READ_PRIVILEGE"); Privilege writePrivilege = createPrivilegeIfNotFound("WRITE_PRIVILEGE"); List<Privilege> adminPrivileges = Arrays.asList( readPrivilege, writePrivilege); createRoleIfNotFound("ROLE_ADMIN", adminPrivileges); createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege)); Role adminRole = roleRepository.findByName("ROLE_ADMIN"); User user = new User(); user.setFirstName("Test"); user.setLastName("Test"); user.setPassword(passwordEncoder.encode("test")); user.setEmail("test@test.com"); user.setRoles(Arrays.asList(adminRole)); user.setEnabled(true); userRepository.save(user); alreadySetup = true; } @Transactional private Privilege createPrivilegeIfNotFound(String name) { Privilege privilege = privilegeRepository.findByName(name); if (privilege == null) { privilege = new Privilege(name); privilegeRepository.save(privilege); } return privilege; } @Transactional private Role createRoleIfNotFound( String name, Collection<Privilege> privileges) { Role role = roleRepository.findByName(name); if (role == null) { role = new Role(name); role.setPrivileges(privileges); roleRepository.save(role); } return role; } }那么,在这个简单的设置代码中发生了什么?没有复杂的:我们正在创建这些权限我们正在创建角色并为其分配权限我们正在创建一个用户并为其分配一个角色请注意我们如何使用alreadySetup标志来确定安装是否需要运行。这仅仅是因为,取决于您在应用程序中配置了多少上下文 - ContextRefreshedEvent可能会被多次触发。我们只希望安装程序执行一次。这里有两个简短的注释 - 首先是术语。我们在这里使用权限 - 角色条款,但在Spring中,它们略有不同。在春季,我们的特权被称为角色,也被称为(授权)权力 - 这有点混乱。当然不是一个问题,但绝对值得注意。其次 - 这些Spring角色(我们的特权)需要一个前缀 ; 默认情况下,该前缀是“ROLE”,但可以更改。我们在这里并没有使用这个前缀,只是为了保持简单,但请记住,如果您没有明确地改变它,那就是必需的。4.自定义UserDetailsS​​ervice现在让我们来看看认证过程。我们将看到如何在我们自定义的UserDetailsS​​ervice中检索用户,以及如何从用户分配的角色和权限映射正确的权限集:@Service("userDetailsService") @Transactional public class MyUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private IUserService service; @Autowired private MessageSource messages; @Autowired private RoleRepository roleRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if (user == null) { return new org.springframework.security.core.userdetails.User( " ", " ", true, true, true, true, getAuthorities(Arrays.asList( roleRepository.findByName("ROLE_USER")))); } return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, getAuthorities(user.getRoles())); } private Collection<? extends GrantedAuthority> getAuthorities( Collection<Role> roles) { return getGrantedAuthorities(getPrivileges(roles)); } private List<String> getPrivileges(Collection<Role> roles) { List<String> privileges = new ArrayList<>(); List<Privilege> collection = new ArrayList<>(); for (Role role : roles) { collection.addAll(role.getPrivileges()); } for (Privilege item : collection) { privileges.add(item.getName()); } return privileges; } private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) { List<GrantedAuthority> authorities = new ArrayList<>(); for (String privilege : privileges) { authorities.add(new SimpleGrantedAuthority(privilege)); } return authorities; } }这里有趣的事情是特权(和角色)如何映射到GrantedAuthority实体。这种映射使整个安全配置具有高度的灵活性和强大功能 - 您可以根据需要将角色和权限混合匹配,最终将它们正确映射到权限并返回到框架。5. 用户注册最后 - 让我们来看看注册一个新用户。我们已经看到安装程序如何创建用户并为其分配角色(和特权) - 现在让我们来看看在注册新用户期间如何完成此操作:@Override public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException { if (emailExist(accountDto.getEmail())) { throw new EmailExistsException ("There is an account with that email adress: " + accountDto.getEmail()); } User user = new User(); user.setFirstName(accountDto.getFirstName()); user.setLastName(accountDto.getLastName()); user.setPassword(passwordEncoder.encode(accountDto.getPassword())); user.setEmail(accountDto.getEmail()); user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER"))); return repository.save(user); }在这个简单的实现中,我们假设一个标准用户正在被注册,所以ROLE_USER角色被分配给它。当然,更复杂的逻辑可以通过相同的方式轻松实现 - 无论是通过多种硬编码注册方法,还是允许客户端发送正在注册的用户类型。6,总结在本教程中,我们演示了如何使用JPA为Spring boot Spring Security支持的系统实现角色和权限。提示:源码下载:demo-springboot-security-jpa-login-reg.zip
  • Spring Boot Redis 秒杀实现

    简述本文主要通过一个简单的例子模拟实现秒杀情景,其中主要使用Redis事物进行实现spring boot为提供方便的环境简述本文主要通过一个简单的例子模拟实现秒杀情景,其中主要使用Redis事物进行实现spring boot为提供方便的环境。首先导入redis依赖pom.xml文件<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.leftso.demo</groupId> <artifactId>demo-redis-seckill</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo-redis-seckill</name> <description>Redis 实现产品秒杀</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Redispool配置配置redis的连接池,这个根据自己需求改。这里测试用。package com.leftso.demo.demoredisseckill; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Configuration public class JedisConfig { @Bean public JedisPool jedisPool(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 设置配置 jedisPoolConfig.setMaxTotal(1024); jedisPoolConfig.setMaxIdle(100); jedisPoolConfig.setMaxWaitMillis(100); jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错 jedisPoolConfig.setTestOnReturn(true); JedisPool pool=new JedisPool(jedisPoolConfig,"127.0.0.1",6379); return pool; } } 写一个秒杀的业务处理实现package com.leftso.demo.demoredisseckill.service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import java.util.List; public class Seckill implements Runnable { private JedisPool jedisPool; private String userName; private String productKey; public Seckill(JedisPool jedisPool, String userName, String productKey) { this.jedisPool = jedisPool; this.userName = userName; this.productKey = productKey; } @Override public void run() { Jedis jedis=jedisPool.getResource(); try { jedis.watch(productKey); String val=jedis.get(productKey); int valInt=Integer.valueOf(val); if (valInt>=1){ Transaction tx=jedis.multi(); tx.incrBy(productKey,-1);//原子操作 List<Object> list=tx.exec(); if (list==null||list.isEmpty()){ System.out.println("用户:"+userName+" 抢购失败。"); this.run();//再抢 }else{ System.out.println("用户:"+userName+ " 抢购成功!!!"); // jedis.setnx(productKey,) jedis.rpush(productKey+"user",userName);//成功用户添加入队列 } }else{ System.out.println("商品已抢购完毕-------"); } }catch (Exception e){ e.printStackTrace(); } } } 最后模拟访问package com.leftso.demo.demoredisseckill; import com.leftso.demo.demoredisseckill.service.Seckill; import org.junit.After; import org.junit.Before; 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; import redis.clients.jedis.JedisPool; import java.util.ArrayList; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class RedisSeckillTest { private final static Logger logger = LoggerFactory.getLogger(RedisSeckillTest.class); @Autowired JedisPool jedisPool; String productKey="SSSSSSKEY"; int productNum=10; @Before public void before(){ jedisPool.getResource().set(productKey,10+"");//设置产品默认库存数量 while (jedisPool.getResource().lpop(productKey+"user")!=null){ }//清空秒杀成功人用户列表 //end } @After public void after(){ String num=jedisPool.getResource().get(productKey); System.out.println("剩余库存:"+num); } @Test public void contextLoads() { try { for (int i = 0; i < 100; i++) { //每个用户件数 Thread t = new Thread(new Seckill(jedisPool,"用户"+i,productKey)); t.start(); } long size=jedisPool.getResource().llen(productKey+"user"); while (true){ if (size==productNum){ break; }else{ size=jedisPool.getResource().llen(productKey+"user"); } } List<String> successUsers=new ArrayList<>(); String user=jedisPool.getResource().lpop(productKey+"user"); while (user!=null){ successUsers.add(user); user=jedisPool.getResource().lpop(productKey+"user"); } System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束"); System.out.println("获奖名单:"+successUsers); Thread.currentThread().sleep(2000); } catch (Exception e) { e.printStackTrace(); } } } 执行单元测试,查看结果:.............................. 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 商品已抢购完毕------- 活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束 获奖名单:[用户66, 用户2, 用户8, 用户10, 用户56, 用户78, 用户33, 用户58, 用户16, 用户87] 剩余库存:0 Process finished with exit code 0您有任何想法欢迎评论区留言讨论,谢谢
  • spring boot整合mybaties

    spring boot框架整合mybaties数据库暂时选用MySQL<br /> 本博文主要讲解spring boot项目整合mybaties<br /> 1.最终项目结构图<br /> <img alt="结构" class="img-thumbnail" src="/assist/images/blog/e0b0519f-a4be-43fe-aabe-d57927ba82b5.jpg" style="height:564px; width:305px" /><br /> 2.文件清单<br /> 清单:pom.xml <pre> <code class="language-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.example</groupId> <artifactId>demo-springboot-mybaties</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-mybaties</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- mybaties --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> 清单:User.java <pre> <code class="language-java">package com.example.pojo; public class User { private Long id; private String userName; private String userSex; private int userAge; public User() { } public User(String userName, String userSex, int userAge) { super(); this.userName = userName; this.userSex = userSex; this.userAge = userAge; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserSex() { return userSex; } public void setUserSex(String userSex) { this.userSex = userSex; } public int getUserAge() { return userAge; } public void setUserAge(int userAge) { this.userAge = userAge; } } </code></pre> 清单:UserMapper.java <pre> <code class="language-java">package com.example.mapper; import java.util.List; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import com.example.pojo.User; public interface UserMapper { /** * 查询所有用户信息 * * @return */ @Select("select * from user") @Results(value = { @Result(property = "userSex", column = "user_sex", javaType = String.class), @Result(property = "userName", column = "user_name"), @Result(property = "userAge", column = "user_age") }) List<User> findList(); /** * 通过ID查询 * * @param id * @return */ @Select("select * from user u where u.id=#{id}") User findOne(@Param("id") Long id); /** * 新增一个 * * @param user */ @Insert("insert into user (user_name,user_sex,user_age) values(#{userName},#{userSex},#{userAge})") void insert(User user); /** * 修改 * * @param user */ @Update("update user u set u.user_name=#{userName},u.user_sex=#{userSex},u.user_age=#{userAge} where u.id=#{id}") void update(User user); /** * 删除 * * @param id */ @Delete("delete from user where id=#{id}") void delete(@Param("id") Long id); } </code></pre> 清单:UserController.java <pre> <code class="language-java">package com.example.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.example.mapper.UserMapper; import com.example.pojo.User; @RestController public class UserController { @Autowired UserMapper userMapper; /** * index * * @return */ @RequestMapping("/") public String index() { return "User Info By Mybaties"; } @RequestMapping("/user/list.json") public Object allUsers() { List<User> users = userMapper.findList(); return users; } } </code></pre> 清单:Application.java <pre> <code class="language-java">package com.example; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @MapperScan(basePackages = { "com.example.mapper" }) // 自动扫描mapper @EnableTransactionManagement//启用事物管理,在service上使用@Transactional(注意是spring的 注解) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } </code></pre> 清单:application.properties <pre> <code class="language-perl">#==================DataSource Config Start================== #name #spring.datasource.name=test #url #spring.datasource.url=jdbc:sqlserver://192.168.xxx.xxx;instanceName=sql_03;DatabaseName=edu;integratedSecurity=false spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8 #DriverClass #spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.driver-class-name=com.mysql.jdbc.Driver #DB username spring.datasource.username=root #DB password spring.datasource.password=root #==================DataSource Config End================== #==================mybaties Config Start================== #ORM Bean Package mybatis.type-aliases-package=com.example.pojo mybatis.mapper-locations=classpath:/mapper/*.xml #打印mybatiesSql语句 logging.level.com.example.mapper=DEBUG #==================mybaties Config End ================== </code></pre> 清单:UserMapperTest.java <pre> <code class="language-java">package com.example.test; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.example.mapper.UserMapper; import com.example.pojo.User; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; // @Test public void testInsert() { try { userMapper.insert(new User("xqlee1", "男", 26)); userMapper.insert(new User("xqlee2", "男", 23)); userMapper.insert(new User("xqlee3", "男", 27)); } catch (Exception e) { e.printStackTrace(); } } // @Test public void testUpdate() { try { User user = new User("测试0000", "男", 23); user.setId(1l); userMapper.update(user); } catch (Exception e) { e.printStackTrace(); } } //@Test public void testQuery() { try { List<User> users=userMapper.findList(); for(User u:users){ System.out.println("ID:"+u.getId()+" Name:"+u.getUserName()+" Sex:"+u.getUserSex()+" Age:"+u.getUserAge()); } } catch (Exception e) { e.printStackTrace(); } } @Test public void testDelete(){ try { userMapper.delete(1l); testQuery(); } catch (Exception e) { e.printStackTrace(); } } } </code></pre>
  • Redis 秒杀整合Spring Boot 2.x实现例子

    前言继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式前言继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式。Redis 秒杀活动项目结构图​与上一篇文章中的区别在于多了一个GoodService服务。该服务主要提供秒杀业务。Redis秒杀活动业务层$title(GoodsService.java) package com.leftso.demo.demoredisseckill.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; /** * 商品服务 */ @Service public class GoodsService { @Autowired JedisPool jedisPool; public static String productKey="GOODS_001";//某产品的ID int goodsStock=10;//某产品用于秒杀的库存 String successKey="Success_User_List";//成功秒杀用户的集合 /** * 初始化一些默认数据(正常情况这些数据来源于数据库) */ @PostConstruct public void init(){ Jedis jedis=jedisPool.getResource(); jedis.set(productKey,String.valueOf(goodsStock));//设置产品默认库存数量 while (jedis.lpop(successKey)!=null){ }//清空秒杀成功人用户列表 //end new Thread(()->{ long size=jedis.llen(successKey); while (true){ if (size==goodsStock){ break; }else{ size=jedis.llen(successKey); } } List<String> successUsers=new ArrayList<>(); String user=jedis.lpop(successKey); while (user!=null){ successUsers.add(user); user=jedis.lpop(successKey); } System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束"); System.out.println("抢购名单:"+successUsers); //可以在名单拿到后生成订单等其他业务操作。 String num=jedis.get(productKey); System.out.println("剩余库存:"+num); }).start(); } /** * 获取库存 * @param productKey * @return */ public int getStock(String productKey){ try (Jedis jedis=jedisPool.getResource();){ String val=jedis.get(productKey); if (val!=null){ return Integer.valueOf(val); } return -1; } } /** * 秒杀商品 * * @param productKey * @return */ public boolean seckill(String productKey, String userName) { if (getStock(productKey)<=0){ System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。"); return false; } try(Jedis jedis = jedisPool.getResource();) { jedis.watch(productKey); String val = jedis.get(productKey); int valInt = Integer.valueOf(val); if (valInt >= 1) { Transaction tx = jedis.multi(); tx.incrBy(productKey, -1);//原子操作 List<Object> list = tx.exec(); if (list == null || list.isEmpty()) { //System.out.println("用户:" + userName + " 抢购失败。"); this.seckill(productKey, userName);//再抢 } else { System.out.println("用户:" + userName + " 抢购成功!!!"); // jedis.setnx(productKey,) jedis.rpush(successKey, userName);//成功用户添加入队列 //处理成功后的业务逻辑 return true; } } else { System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。"); return false; } return false; } catch (Exception e) { e.printStackTrace(); return false; } } } 主要模拟环境与之前的文章一样。作为秒杀模拟场景。其他相关文件清单:pom.xml依赖$title(pom.xml) <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.leftso.demo</groupId> <artifactId>demo-redis-seckill</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo-redis-seckill</name> <description>Redis 实现产品秒杀</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> redis配置$title(JedisConfig.java) package com.leftso.demo.demoredisseckill; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.util.HashSet; import java.util.Set; @Configuration public class JedisConfig { /*** * 单机连接池 * @return */ @Bean public JedisPool jedisPool() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 设置配置 jedisPoolConfig.setMaxTotal(2048); jedisPoolConfig.setMaxIdle(400); jedisPoolConfig.setMaxWaitMillis(100); jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错 jedisPoolConfig.setTestOnReturn(true); JedisPool pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379); return pool; } /*** * redis集群用(这里暂时没用) * @return */ // @Bean public JedisCluster jedisCluster() { //创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象 Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("127.0.0.1", 7001)); nodes.add(new HostAndPort("127.0.0.1", 7002)); nodes.add(new HostAndPort("127.0.0.1", 7003)); nodes.add(new HostAndPort("127.0.0.1", 7004)); nodes.add(new HostAndPort("127.0.0.1", 7005)); nodes.add(new HostAndPort("127.0.0.1", 7006)); JedisCluster jedisCluster = new JedisCluster(nodes); //使用jedisCluster操作redis // jedisCluster.set("test", "my forst jedis"); // String str = jedisCluster.get("test"); // System.out.println(str); // //关闭连接池 // jedisCluster.close(); //注意由于集群式单例,不要再其他地方关闭连接池!!!!由系统关闭时统一关闭。 return jedisCluster; } } web 接口$title(SecKillController.java) package com.leftso.demo.demoredisseckill.controller; import com.leftso.demo.demoredisseckill.service.GoodsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; /** * 秒杀接口 */ @RestController public class SecKillController { @Autowired GoodsService goodsService; @GetMapping("/seckill") public Object seckill() { //创建随机用户名 String userName="用户+"+ UUID.randomUUID().toString().replace("-","").toUpperCase(); boolean suc=goodsService.seckill(GoodsService.productKey,userName); return suc?"Succes":"Fail"; } } Redis 秒杀活动测试 这里的测试主要使用ab工具进行测试。首先启动秒杀应用(注意启动一次只能测一次。再次测试请重启服务,例子简单就这么处理的。哈哈)ab测试命令:ab -c 1000 -n 3000 http://localhost:8080/seckill 执行结果:​通过测试的访问来看,错误请求有2994.好吧我目前也不知道啥情况。反正后台的输出是正常的。来看看后台的日志吧 2019-07-10 11:49:10.592 INFO 2656 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 9 ms 用户:用户+28C21F33CBD3452EB5C67B14A3939356 抢购成功!!! 用户:用户+126334102697433C8BB5B5D6E4061E38 抢购成功!!! 用户:用户+9ED7D0B472E9490E9C780B61B881E41B 抢购成功!!! 用户:用户+5AD862F2A389473AB2B4FB5EDC187617 抢购成功!!! 用户:用户+A264C1B9CB69498D8402B2E6C0B47589 抢购成功!!! 用户:用户+FC3D45232C73446E8032FDC36B0B8430 抢购成功!!! 用户:用户+D0F11B01EAB24CB385C23580CFC4E671 抢购成功!!! 用户:用户+73FD6DAED8564FEF8315CC4D6181ED77 抢购成功!!! 用户:用户+6967C042ADCA4FC9B42B70A51EE10752 抢购成功!!! 用户:用户+BBB5FF4878844B79BD11DC8C15EA9500 抢购成功!!! 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束 抢购名单:[用户+28C21F33CBD3452EB5C67B14A3939356, 用户+126334102697433C8BB5B5D6E4061E38, 用户+9ED7D0B472E9490E9C780B61B881E41B, 用户+5AD862F2A389473AB2B4FB5EDC187617, 用户+A264C1B9CB69498D8402B2E6C0B47589, 用户+FC3D45232C73446E8032FDC36B0B8430, 用户+D0F11B01EAB24CB385C23580CFC4E671, 用户+73FD6DAED8564FEF8315CC4D6181ED77, 用户+6967C042ADCA4FC9B42B70A51EE10752, 用户+BBB5FF4878844B79BD11DC8C15EA9500] 剩余库存:0 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。 细数了下获奖名单还是没错的。哈哈谁知道ab为啥那么多错误请求?评论告诉我一下谢谢。
  • Spring 5 MVC 整合 Hibernate 5

    在这个Spring5教程中,学习创建Spring 5 MVC Web应用程序,处理表单提交,集成hibernate连接到后端数据库,以及添加用于输入表单字段验证的hibernate验证器。<h2>引言</h2>     在这个Spring5教程中,学习创建Spring 5 MVC Web应用程序,处理表单提交,集成hibernate连接到后端数据库,以及添加用于输入表单字段验证的hibernate验证器。<br /> <br />     我们将创建一个简单的屏幕,我们可以添加用户字段(名称和电子邮件)。 这些细节将首先验证,然后使用休眠存储在HSQL数据库中。 该页面也将列出所有存储的用户。 <h2>一.Spring MVC 5整合hibernate5开发环境准备</h2> <ul> <li>Eclipse Neon.2 +</li> <li>JDK 1.8 +</li> <li>Spring 5.0.0.RELEASE</li> <li>Hibernate 5.2.11.Final</li> <li>Hibernate validator 5.4.1.Final</li> <li>Servlets 3.1.0</li> <li>HSQLDB 1.8.0.10</li> <li>Tomcat 7 maven plugin 2.2</li> </ul> <h2>二.Spring MVC 5整合hibernate5项目结构图</h2> 该项目具有典型的Maven Web应用程序结构。<br /> <img alt="Spring5" class="img-thumbnail" src="/assist/images/blog/2cd6c348063d40caabff4dfdc8e79010.png" /> <h2>三.Spring MVC 5整合hibernate5类图关系</h2> <img alt="" class="img-thumbnail" src="/assist/images/blog/3bb67013c0b84472824f0936c8f82b6e.png" /> <h2>四.Spring MVC 5整合hibernate5 maven依赖</h2> 查找用于在pom.xml文件中运行此示例的项目依赖项。 <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.howtodoinjava.spring5.demo</groupId> <artifactId>spring5-mvc-hibernate-example</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <failOnMissingWebXml>false</failOnMissingWebXml> <spring.version>5.0.0.RELEASE</spring.version> <hibernate.version>5.2.11.Final</hibernate.version> <hibernate.validator>5.4.1.Final</hibernate.validator> <c3p0.version>0.9.5.2</c3p0.version> <jstl.version>1.2.1</jstl.version> <tld.version>1.1.2</tld.version> <servlets.version>3.1.0</servlets.version> <jsp.version>2.3.1</jsp.version> <hsqldb.version>1.8.0.10</hsqldb.version> </properties> <dependencies> <!-- Spring MVC Dependency --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring ORM --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- Hibernate Core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- Hibernate-C3P0 Integration --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>${hibernate.version}</version> </dependency> <!-- c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>${c3p0.version}</version> </dependency> <!-- Hibernate Validator --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>${hibernate.validator}</version> </dependency> <!-- JSTL Dependency --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>javax.servlet.jsp.jstl-api</artifactId> <version>${jstl.version}</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>${tld.version}</version> </dependency> <!-- Servlet Dependency --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlets.version}</version> <scope>provided</scope> </dependency> <!-- JSP Dependency --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>${jsp.version}</version> <scope>provided</scope> </dependency> <!-- HSQL Dependency --> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> <build> <sourceDirectory>src/main/java</sourceDirectory> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/</path> </configuration> </plugin> </plugins> </build> </project></code></pre> <h2>五.配置Spring MVC 5 servlet分发器DispatcherServlet</h2>     随着Servlet 3.0规范的发布,可以用几乎没有xml来配置你的Servlet容器。 为此,Servlet规范中有ServletContainerInitializer。 在这个类中,你可以注册过滤器,监听器,servlet等,就像你在web.xml中一样。<br /> <br />     Spring提供了知道如何处理WebApplicationInitializer类的SpringServletContainerInitializer。 AbstractAnnotationConfigDispatcherServletInitializer类实现了内部实现WebApplicationInitializer的WebMvcConfigurer。 它注册一个ContextLoaderlistener(可选)和DispatcherServlet,并允许您轻松添加配置类来加载这两个类,并将过滤器应用于DispatcherServlet并提供servlet映射。<br />   <pre> <code class="language-java">public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { HibernateConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }</code></pre> <h2>六.Spring Web MVC 5 配置</h2> Spring Web MVC配置如下。 <pre> <code class="language-java">import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan(basePackages = { "com.howtodoinjava.demo.spring"}) public class WebMvcConfig implements WebMvcConfigurer { @Bean public InternalResourceViewResolver resolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setViewClass(JstlView.class); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); return resolver; } @Bean public MessageSource messageSource() { ResourceBundleMessageSource source = new ResourceBundleMessageSource(); source.setBasename("messages"); return source; } @Override public Validator getValidator() { LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); validator.setValidationMessageSource(messageSource()); return validator; } }</code></pre> <ol> <li><code>WebMvcConfigurer</code> 定义了通过使用@EnableWebMvc自定义或添加到默认的<code>@EnableWebMvc</code>配置的选项。</li> <li><code>@EnableWebMvc</code> 启用默认的Spring MVC配置,并注册DispatcherServlet所期望的Spring MVC基础架构组件。</li> <li><code>@Configuration</code> 指示一个类声明了一个或多个@Bean方法,并且可以被Spring容器处理,以在运行时为这些bean生成bean定义和服务请求。</li> <li><code>@ComponentScan</code> 注释用于指定要扫描的基本包。任何用@Component和@Configuration注解的类都将被扫描。</li> <li><code>InternalResourceViewResolver</code> 有助于映射逻辑视图名称,以便在特定的预配置目录下直接查看文件。</li> <li><code>ResourceBundleMessageSource</code> 使用指定的基本名称访问资源包(这里是消息)。</li> <li><code>LocalValidatorFactoryBean</code> 引导一个<code>javax.validation.ValidationFactory</code> ,并通过Spring Validator接口以及JSR-303 Validator接口和<code>ValidatorFactory</code> 接口本身公开它。</li> </ol> <h2>七.Hibernate 配置</h2> 在例子中使用Hibernate配置。 <pre> <code class="language-java">import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScans; import org.springframework.context.annotation.Configuration; import org.springframework.orm.hibernate5.HibernateTransactionManager; import org.springframework.orm.hibernate5.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.howtodoinjava.demo.spring.model.User; @Configuration @EnableTransactionManagement public class HibernateConfig { @Autowired private ApplicationContext context; @Bean public LocalSessionFactoryBean getSessionFactory() { LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); factoryBean.setConfigLocation(context.getResource("classpath:hibernate.cfg.xml")); factoryBean.setAnnotatedClasses(User.class); return factoryBean; } @Bean public HibernateTransactionManager getTransactionManager() { HibernateTransactionManager transactionManager = new HibernateTransactionManager(); transactionManager.setSessionFactory(getSessionFactory().getObject()); return transactionManager; } }</code></pre> <ul> <li><code>LocalSessionFactoryBean</code> 创建一个Hibernate <code>SessionFactory</code>. 这是在Spring应用程序上下文中设置共享Hibernate SessionFactory的常用方法。</li> <li><code>EnableTransactionManagement</code> 支持Spring的注解驱动事务管理功能。</li> <li><code>HibernateTransactionManager</code> 将Hibernate Session从指定的工厂绑定到线程,可能允许每个工厂有一个线程绑定的Session。 此事务管理器适用于使用单个Hibernate</li> <li><code>SessionFactory</code> 进行事务性数据访问的应用程序,但也支持事务内的直接<code>DataSource</code> 访问,即普通JDBC。</li> </ul> <strong>hibernate.cfg.xml:</strong> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.archive.autodetection">class,hbm</property> <property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.connection.password"></property> <property name="hibernate.connection.url">jdbc:hsqldb:mem:howtodoinjava</property> <property name="hibernate.hbm2ddl.auto">create</property> <property name="hibernate.c3p0.min_size">5</property> <property name="hibernate.c3p0.max_size">20</property> <property name="hibernate.c3p0.acquire_increment">2</property> <property name="hibernate.c3p0.acquire_increment">1800</property> <property name="hibernate.c3p0.max_statements">150</property> </session-factory> </hibernate-configuration></code></pre> <h2>八.Spring Controller and Path Mappings</h2> 控制器类有两个简单的GET和POST操作映射。 如果输入字段未被验证,则返回相同的表单bean以显示错误消息。 否则返回刷新视图。 <pre> <code class="language-java"> import java.util.Locale; import javax.validation.alid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import com.howtodoinjava.demo.spring.model.User; import com.howtodoinjava.demo.spring.service.UserService; @Controller public class UserController { @Autowired private UserService userService; @GetMapping("/") public String userForm(Locale locale, Model model) { model.addAttribute("users", userService.list()); return "editUsers"; } @ModelAttribute("user") public User formBackingObject() { return new User(); } @PostMapping("/addUser") public String saveUser(@ModelAttribute("user") @Valid User user, BindingResult result, Model model) { if (result.hasErrors()) { model.addAttribute("users", userService.list()); return "editUsers"; } userService.save(user); return "redirect:/"; } }</code></pre> <h2>九.Service and DAO 层</h2> 服务和DAO层是用@Service和@Repository注释标注的普通服务组件。 @交易注解应用于服务层以支持事务处理。<br /> <br /> <code>UserService</code> and <code>UserServiceImpl</code><br />   <pre> <code class="language-java">public interface UserService { void save(User user); List<User> list(); } @Service public class UserServiceImp implements UserService { @Autowired private UserDao userDao; @Transactional public void save(User user) { userDao.save(user); } @Transactional(readOnly = true) public List<User> list() { return userDao.list(); } }</code></pre> <code>UserDao</code> and <code>UserDaoImp</code><br />   <pre> <code class="language-java">public interface UserDao { void save(User user); List<User> list(); } @Repository public class UserDaoImp implements UserDao { @Autowired private SessionFactory sessionFactory; @Override public void save(User user) { sessionFactory.getCurrentSession().save(user); } @Override public List<User> list() { @SuppressWarnings("unchecked") TypedQuery<User> query = sessionFactory.getCurrentSession().createQuery("from User"); return query.getResultList(); } }</code></pre> User<br />   <pre> <code class="language-java">import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; @Entity @Table(name = "TBL_USERS") public class User { @Id @GeneratedValue @Column(name = "USER_ID") private Long id; @Column(name = "USER_NAME") @Size(max = 20, min = 3, message = "{user.name.invalid}") @NotEmpty(message="Please Enter your name") private String name; @Column(name = "USER_EMAIL", unique = true) @Email(message = "{user.email.invalid}") @NotEmpty(message="Please Enter your email") private String email; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }</code></pre> <h2>十.页面和消息</h2> 最后,使用JSP文件和消息资源包<br /> <br /> editUsers.jsp<br />   <pre> <code class="language-html"><%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Spring5 MVC Hibernate Demo</title> <style type="text/css"> .error { color: red; } table { width: 50%; border-collapse: collapse; border-spacing: 0px; } table td { border: 1px solid #565454; padding: 20px; } </style> </head> <body> <h1>Input Form</h1> <form:form action="addUser" method="post" modelAttribute="user"> <table> <tr> <td>Name</td> <td> <form:input path="name" /> <br /> <form:errors path="name" cssClass="error" /> </td> </tr> <tr> <td>Email</td> <td> <form:input path="email" /> <br /> <form:errors path="email" cssClass="error" /> </td> </tr> <tr> <td colspan="2"><button type="submit">Submit</button></td> </tr> </table> </form:form> <h2>Users List</h2> <table> <tr> <td><strong>Name</strong></td> <td><strong>Email</strong></td> </tr> <c:forEach items="${users}" var="user"> <tr> <td>${user.name}</td> <td>${user.email}</td> </tr> </c:forEach> </table> </body> </html></code></pre> messages.properties<br />   <pre> <code class="language-html">user.name.invalid = Name must be between {2} and {1} characters. user.email.invalid = Please enter valid email address.</code></pre> <h2>十一.演示这个栗子</h2> 让我们使用maven tomcat7插件运行应用程序。 执行maven target:tomcat7:run。<br /> 访问URL: <code>http://localhost:8080</code><br /> Initial Screen<br /> <img alt="Initial Screen" class="img-thumbnail" src="/assist/images/blog/830164ad1b5448dab1aa39d340300c95.png" /><br /> 无效的输入验证<br /> <img alt="无效的输入验证" class="img-thumbnail" src="/assist/images/blog/8ddf9070177c4ce3a00c5dfe4b099950.png" /><br /> 有效的表格提交<br /> <img alt="有效的表格提交" class="img-thumbnail" src="/assist/images/blog/6ef29b6dbc1f4fb7954a3cb9bac38159.png" /><br /> 检查服务器日志: <pre> <code class="language-html">Hibernate: call next value for hibernate_sequence Hibernate: insert into TBL_USERS (USER_EMAIL, USER_NAME, USER_ID) values (?, ?, ?) Hibernate: select user0_.USER_ID as USER_ID1_0_, user0_.USER_EMAIL as USER_EMA2_0_, user0_.USER_NAME as USER_NAM3_0_ from TBL_USERS user0_</code></pre> <a href="https://howtodoinjava.com/wp-content/downloads/spring5-mvc-hibernate-example.zip" rel="external nofollow" target="_blank">Sourcecode Download</a>
  • Spring AOP 实现原理

    Spring AOP 实现原理基础讲解<h2>什么是AOP</h2> <p>AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。</p> <p> </p> <p>而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。</p> <p> </p> <p>使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”</p> <p> </p> <p>实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。</p> <p> </p> <h2>AOP使用场景</h2> <p>AOP用来封装横切关注点,具体可以在下面的场景中使用:</p> <p> </p> <p>Authentication 权限</p> <p>Caching 缓存</p> <p>Context passing 内容传递</p> <p>Error handling 错误处理</p> <p>Lazy loading 懒加载</p> <p>Debugging  调试</p> <p>logging, tracing, profiling and monitoring 记录跟踪 优化 校准</p> <p>Performance optimization 性能优化</p> <p>Persistence  持久化</p> <p>Resource pooling 资源池</p> <p>Synchronization 同步</p> <p>Transactions 事务</p> <p> </p> <h2>AOP相关概念</h2> <p>方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。</p> <p> </p> <p>连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。</p> <p> </p> <p>通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice</p> <p> </p> <p>切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上</p> <p> </p> <p>引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口</p> <p> </p> <p>目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO</p> <p> </p> <p>AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。</p> <p> </p> <p>织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯JavaAOP框架一样,在运行时完成织入。</p> <p> </p> <h2>Spring AOP组件</h2> <p>下面这种类图列出了Spring中主要的AOP组件<br /> <img alt="Spring AOP结构图" class="img-thumbnail" src="/assist/images/blog/1e67c550-0908-411b-a4e1-77f8eed8fddd.png" style="height:1770px; width:2206px" /></p> <h2>如何使用Spring AOP</h2>   <p>可以通过配置文件或者编程的方式来使用Spring AOP。</p> <p> </p> <p>配置可以通过xml文件来进行,大概有四种方式:</p> <p>1.        配置ProxyFactoryBean,显式地设置advisors, advice, target等</p> <p>2.        配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象</p> <p>3.        通过<aop:config>来配置</p> <p>4.        通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点</p> <p> </p> <p>也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象, advisor等相关配置,最终通过 getProxy()方法来获取代理对象</p> <p> </p> <p>具体使用的示例可以google. 这里略去</p> <p> </p> <h2>Spring AOP代理对象的生成</h2>   <p>Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:<br />  </p> <pre> <code class="language-java">/** * <ol> * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false) * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口 * <li>调用Proxy.newProxyInstance创建代理对象 * </ol> */ public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource()); } Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } </code></pre> <p>那这个其实很明了,注释上我也已经写清楚了,不再赘述。</p> <p> </p> <p>下面的问题是,代理对象生成了,那切面是如何织入的?</p> <p>我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下Spring AOP是如何织入切面的。<br />  </p> <pre> <code class="language-java">publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { //eqauls()方法,具目标对象未实现此方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){ return (equals(args[0])? Boolean.TRUE : Boolean.FALSE); } //hashCode()方法,具目标对象未实现此方法 if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){ return newInteger(hashCode()); } //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知 if (!this.advised.opaque &&method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations onProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args); } Object retVal = null; if (this.advised.exposeProxy) { // Make invocation available ifnecessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } //获得目标对象的类 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } //获取可以应用到此方法上的Interceptor列表 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass); //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args) if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //创建MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } // Massage return value if necessary. if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned"this" and the return type of the method // is type-compatible. Notethat we can't help if the target sets // a reference to itself inanother returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come fromTargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } } </code></pre> <p>主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行joinpoint; 如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。</p> <p> </p> <p>首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:<br />  </p> <pre> <code class="language-java">public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { MethodCacheKeycacheKey = new MethodCacheKey(method); List<Object>cached = this.methodCache.get(cacheKey); if(cached == null) { cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this,method, targetClass); this.methodCache.put(cacheKey,cached); } returncached; } </code></pre> <p>可以看到实际的获取工作其实是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。</p> <p>下面来分析下这个方法的实现:<br />  </p> <pre> <code class="language-java">/** * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor, * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断 * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回. */ publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) { // This is somewhat tricky... we have to process introductions first, // but we need to preserve order in the ultimate list. List interceptorList = new ArrayList(config.getAdvisors().length); //查看是否包含IntroductionAdvisor boolean hasIntroductions = hasMatchingIntroductions(config,targetClass); //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); Advisor[] advisors = config.getAdvisors(); for (int i = 0; i <advisors.length; i++) { Advisor advisor = advisors[i]; if (advisor instanceof PointcutAdvisor) { // Add it conditionally. PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor; if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { //TODO: 这个地方这两个方法的位置可以互换下 //将Advisor转化成Interceptor MethodInterceptor[]interceptors = registry.getInterceptors(advisor); //检查当前advisor的pointcut是否可以匹配当前方法 MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher(); if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) { if(mm.isRuntime()) { // Creating a newobject instance in the getInterceptors() method // isn't a problemas we normally cache created chains. for (intj = 0; j < interceptors.length; j++) { interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm)); } } else { interceptorList.addAll(Arrays.asList(interceptors)); } } } } else if (advisor instanceof IntroductionAdvisor){ IntroductionAdvisor ia =(IntroductionAdvisor) advisor; if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) { Interceptor[] interceptors= registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } else { Interceptor[] interceptors =registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } } return interceptorList; } </code></pre> <p>这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.</p> <p> </p> <p>接下来我们再看下得到的拦截器链是怎么起作用的。</p> <pre> <code class="language-java">if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args); } else { //创建MethodInvocation invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } </code></pre>    从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码 <pre> <code class="language-java">public Object proceed() throws Throwable { // We start with an index of -1and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) { //如果Interceptor执行完了,则执行joinPoint return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //如果要动态匹配joinPoint if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){ // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice; //动态匹配:运行时参数是否满足匹配条件 if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) { //执行当前Intercetpor returndm.interceptor.invoke(this); } else { //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcutwill have // been evaluated statically before this object was constructed. //执行当前Intercetpor return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } </code></pre> <p>代码也比较简单,这里不再赘述。</p>