-手写Spring注解版本&事务传播行为
视频参考C:\Users\Administrator\Desktop\蚂蚁3期\【www.zxit8.com】 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring注解版本&事务传播行为\0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring注解版本&事务传播行为
今天视频要手写一个@Transaction事务注解框架
我们首先来看一个spring 事务的常见配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 开启注解 -->
<context:component-scan base-package="com.itmayiedu"></context:component-scan>
<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean> <!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 配置事物 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事物 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
</beans>
在业务层中@Transaction来使用注解
@Transactional
public void add() {
userDao.add("wangmazi", 27);
int i = 1 / 0;
System.out.println("我是add方法");
userDao.add("zhangsan", 16);
}
接下来我们要手写@Transactional注解框架
接下来首先学习下java的注解以及利用反射读取注解
@Target(value = { ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AddAnnotation { int userId() default 0; String userName() default "默认名称"; String[]arrays();
}
接下来利用反射读取注解
package com.itmayiedu.annotation; import java.lang.reflect.Method; public class User { @AddAnnotation(userName = "张三", userId = 18, arrays = { "1" })
public void add() { } public void del() { } public static void main(String[] args) throws ClassNotFoundException {
// 怎么样获取到方法上注解信息 反射机制
Class<?> forName = Class.forName("com.itmayiedu.annotation.User");
// 获取到当前类(不包含继承)所有的方法
Method[] declaredMethods = forName.getDeclaredMethods();
for (Method method : declaredMethods) {
// 获取该方法上是否存在注解
System.out.println("####方法名称" + method.getName());
AddAnnotation addAnnotation = method.getAnnotation(AddAnnotation.class);
if (addAnnotation == null) {
// 该方法上没有注解
System.out.println("该方法上没有加注解..");
continue;
}
// 在该方法上查找到该注解
System.out.println("userId:" + addAnnotation.userId());
System.out.println("userName:" + addAnnotation.userName());
System.out.println("arrays:" + addAnnotation.arrays());
System.out.println();
}
} }
现在有个User类,类中add方法我们添加了注解,接下来使用反射来操作User类,通过反射获得add方法,然后判断该方法上面是否使用了注解,如果使用了把注解的值打印出来
我们来看下打印的日志
####方法名称main
该方法上没有加注解..
####方法名称add
userId:18
userName:张三
arrays:[Ljava.lang.String;@6607db7d ####方法名称del
该方法上没有加注解..
接下来我们重点讲解手写@Transaction spring的事物注解
例如现在add方法上我们使用@ExtTransactional如何实现注解了。定义一个aop的切面类,在切面类中定义切点可以拦截到controller类的add方法被调用了。在切面中利用反射技术判断add方法上面是否使用了注解,如果使用了注解就开启事务,没有就不开启事务
第一步:需要将spring框架配置文件中的开启事务管理注解去掉
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.itmayiedu"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean> <!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 3.配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> </beans>
去掉:
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
第二步:自定义一个注解
package com.itmayiedu.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; // 事务注解 设置传播行为
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction { }
第三步:自定义一个事务管理工具的实现类,这里使用的是jdbc的事务管理器来实现对事务的管理
package com.itmayiedu.transaction; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute; // 编程事务(需要手动begin 手动回滚 手都提交)
@Component
@Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子
public class TransactionUtils { // 全局接受事务状态
private TransactionStatus transactionStatus;
// 获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务
public TransactionStatus begin() {
System.out.println("开启事务");
transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transactionStatus;
} // 提交事务
public void commit(TransactionStatus transaction) {
System.out.println("提交事务");
dataSourceTransactionManager.commit(transaction);
} // 回滚事务
public void rollback() {
System.out.println("回滚事务...");
dataSourceTransactionManager.rollback(transactionStatus);
} }
上面这个类有几个地方需要注意的,dao层使用的是jdbcTemplate,使用jdbc的方式,所以要使用jbdc数据源的事务管理器
DataSourceTransactionManager,其他框架hibernate的事务管理器为
Spring声明式事务管理器类:
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
第二:对于事务管理工具类一个事务应该对于一个事务管理工具类的实例,spring默认代理对象都是单例模式,如果是单例模式多个事务就是多个线程共享一个对象,会出现线程安全问题
所以@Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子,定义为多实例类型
接下来,定位aop实现对调用方法的拦截,实现事务管理
package com.itmayiedu.aop; import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.itmayiedu.annotation.ExtTransaction;
import com.itmayiedu.transaction.TransactionUtils; // 自定义事务注解具体实现
@Aspect
@Component
public class AopExtTransaction {
// 一个事务实例子 针对一个事务
@Autowired
private TransactionUtils transactionUtils; // 使用异常通知进行 回滚事务
@AfterThrowing("execution(* com.itmayiedu.service.*.*.*(..))")
public void afterThrowing() {
// 获取当前事务进行回滚
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
transactionUtils.rollback();
} // 环绕通知 在方法之前和之后处理事情
@Around("execution(* com.itmayiedu.service.*.*.*(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable { // 1.获取该方法上是否加上注解
ExtTransaction extTransaction = getMethodExtTransaction(pjp);
TransactionStatus transactionStatus = begin(extTransaction);
// 2.调用目标代理对象方法
pjp.proceed();
// 3.判断该方法上是否就上注解
commit(transactionStatus);
} private TransactionStatus begin(ExtTransaction extTransaction) {
if (extTransaction == null) {
return null;
}
// 2.如果存在事务注解,开启事务
return transactionUtils.begin();
} private void commit(TransactionStatus transactionStatus) {
if (transactionStatus != null) {
// 5.如果存在注解,提交事务
transactionUtils.commit(transactionStatus);
} } // 获取方法上是否存在事务注解
private ExtTransaction getMethodExtTransaction(ProceedingJoinPoint pjp)
throws NoSuchMethodException, SecurityException {
String methodName = pjp.getSignature().getName();
// 获取目标对象
Class<?> classTarget = pjp.getTarget().getClass();
// 获取目标对象类型
Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
// 获取目标对象方法
Method objMethod = classTarget.getMethod(methodName, par);
ExtTransaction extTransaction = objMethod.getAnnotation(ExtTransaction.class);
return extTransaction;
} }
package com.itmayiedu.dao; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; @Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) {
String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
int updateResult = jdbcTemplate.update(sql, name, age);
System.out.println("updateResult:" + updateResult);
} }
package com.itmayiedu.service; //user 服务层
public interface UserService { public void add(); public void del();
}
接下来在实现类使用
@ExtTransaction就实现了我们的自定义事务注解
package com.itmayiedu.service.impl; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import com.itmayiedu.dao.UserDao;
import com.itmayiedu.service.LogService;
import com.itmayiedu.service.UserService; //user 服务层
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionUtils transactionUtils; @ExtTransactionpublic void add() {
userDao.add("test001", 20);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21); }
// 方法执行完毕之后,才会提交事务 public void del() {
System.out.println("del");
} }
接下来我们分析下事务的传播特性,我们使用spring框架默认自带的注解
我们在配置文件中开启事务管理,然后将上面我们自己编写的AopExtTransaction事务框架代码屏蔽掉,以免和spring默认的框架起冲突
注意:spring的事务传播特性是在两个不同的service中相互调用
我们在新建一个dao
package com.itmayiedu.dao; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; @Repository
public class LogDao {
@Autowired
private JdbcTemplate jdbcTemplate; public void add(String name) {
String sql = "INSERT INTO t_log(log_name) VALUES(?);";
int updateResult = jdbcTemplate.update(sql, name);
System.out.println("##LogDao##updateResult:" + updateResult);
} }
package com.itmayiedu.service.impl; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import com.itmayiedu.dao.LogDao;
import com.itmayiedu.service.LogService; @Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao; @Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
// int i = 1 / 0;
} }
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.itmayiedu"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- . 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean> <!-- . JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- .配置事务 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
</beans>
使用spring框架的默认事务管理,这里需要开启注解事务管理的配置
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
对应的就是在addLog()方法上面使用@Transactional注解
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
}
@Transactional
public void add() {
// 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。
logService.addLog(); // 后面程序发生错误,不能影响到我的回滚### 正常当addLog方法执行完毕,就应该提交事务
userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
}
我们来看下运行的情况
第一种情况:正常情况下,事务正常 log日志和人员信息都会添加成功
第二种情况,如果在addLog方法中发送了异常
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0;
}
这个时候因为addLog中发送了异常,这个时候就会把异常抛出去给aop框架处理,后续的代码就不会再继续执行
就不会再执行
logService.addLog(); // 后面程序发生错误,不能影响到我的回滚### 正常当addLog方法执行完毕,就应该提交事务
userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
因为addLOg方法出现了异常就不会再执行后续的userDao.add("test001", 200);方法,抛出异常的时候aop框架会对事务进行回滚,所以ogDao.add("addLog" + System.currentTimeMillis());不会添加数据到数据库中
第三种情况
如果在addLog方法中发送了异常
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0;
}
但是在userService的add方法中使用了try catch进行捕获,我们来看下数据库的情况
@Transactional(rollbackFor=Exception.class)
public void add() {
// 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 try{
logService.addLog();
}catch(Exception e){
System.out.println(e.toString());
} userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21); }
我们来看下这种情况下的运行情况
运行居然抛出了异常
Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy13.add(Unknown Source)
at com.itmayiedu.Test001.main(Test001.java:12)
数据库中没有插入任何数据,为啥出现上面这种情况了
##Transaction rolled back because it has been marked as rollback-only spring 具备多种事务传播机制,最常用的是REQUIRED,即如果不存在事务,则新建一个事务;如果存在事务,则加入现存的事务中。 示例代码如下:
public void A() {
querySomething(...);
try {
B()
} catch () {
}
saveSomethinf();
}
public void B() {
throw Exception()
}
此时B会和A存在一个事务中。如果B抛出异常没有捕获,即使在A中捕获并处理,仍会发生异常:Transaction rolled back because it has been marked as rollback-only 因为spring会在A捕获异常之前提前捕获到异常,并将当前事务设置为rollback-only,而A觉得对异常进行了捕获,它仍然继续commit,当TransactionManager发现状态为设置为rollback-only时, 则会抛出UnexpectedRollbackException 相关代码在AbstractPlatformTransactonManager.java中:
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
// Throw UnexpectedRollbackException only at outermost transaction boundary
// or if explicitly asked to.
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
processCommit(defStatus);
}
解决方法: 在抛出异常的最原始地方处理异常,即在spring捕获到异常之前处理掉
在一个transactional中如果有另一transaction,内层的事务会跟随外层的事务,变为一个整体的事务。
如果内层transaction发生了异常,即使你捕捉了这个异常,那么整体的Transaction也会被定义成RollbackOnly,这也正是事务管理的原则。所以当外层事务想提交整个事务时,出现异常。
解决版本就是在logService中对异常进行try catch处理
@Transactional
public void addLog() {
try{
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0;
}catch(Exception e){ } }
@Transactional(rollbackFor=Exception.class)
public void add() {
// 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 logService.addLog(); userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21); }
上面这种情况:log日志和人员信息都会添加到数据库中, int i = 1 / 0;发现了异常但是我们自定义了try catch处理异常,就不会把异常抛出去给aop框架进行处理,对数据进行回滚
因为logService.addLog(); 对外没有抛出异常,后续的 userDao.add("test001", 200);代码也会正常执行,把人员数据添加到数据库中
第五情况在add添加人员方法中抛出了异常
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0; }
@Transactional(rollbackFor=Exception.class)
public void add() {
// 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 logService.addLog();
userDao.add("test001", 200);
int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21); }
在调用add方法中抛出了异常,因为当前事务addLog的事务传播属性是 @Transactional,PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
所以logService.addLog();和userDao.add("test001", 200);是在同一个事务中,当发现异常的时候,如果没有对异常使用try catch进行处理,会把异常抛出去给aop框架进行处理
异常前面的
logService.addLog(); 和 userDao.add("test001", 200);都会回滚
-手写Spring注解版本&事务传播行为的更多相关文章
- 手写Spring事务框架
Spring事务基于AOP环绕通知和异常通知 编程事务 声明事务 Spring事务底层使用编程事务+AOP进行包装的 = 声明事务 AOP应用场景: 事务 权限 参数验证 什么是AOP技术 AO ...
- Spring事务原理分析--手写Spring事务
一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...
- 手写spring事务框架-蚂蚁课堂
1.视频参加C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Sp ...
- 《四 spring源码》利用TransactionManager手写spring的aop
事务控制分类 编程式事务控制 自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false); // 设置手动控制事务 Hibern ...
- 手写spring
体系结构 Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入.下面的部分对在 Spring 框架中所有可用的 ...
- 一个老程序员是如何手写Spring MVC的
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...
- 我是这样手写 Spring 的(麻雀虽小五脏俱全)
人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...
- 我是这样手写Spring的,麻雀虽小五脏俱全
人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
随机推荐
- html5学习之路_001
安装环境 安装intellij idea作为开发环境 打开环境 新建一个html文件,打开之后出现代码框架,再次基础上继续编码即可,例如: <!DOCTYPE html> <html ...
- 2020 网鼎杯wp
2020 网鼎杯WP 又是划水的一天,就只做出来4题,欸,还是太菜,这里就记录一下做出的几题的解题记录 AreUSerialz 知识点:反序列化 打开链接直接给出源码 <?php include ...
- 前端练手小项目——网页版qq音乐仿写
qq音乐网页版仿写 一些步骤与注意事项 一开始肯定就是html+css布局和页面了,这段特别耗时间,耐心写完就好了 首先要说一下大致流程: 一定要先布局html!,所以一定要先分析页面布局情况,用不同 ...
- Ubuntu18.04兼容Python2.7、Python3.6、Python3.8以及pip、pip2、pip3问题
Ubuntu18.04兼容Python2.7.Python3.6.Python3.8以及pip.pip2.pip3问题 此为记录我重装Ubuntu后安装Python的过程 安装Python3.8 目前 ...
- Java实现 LeetCode 739 每日温度(暴力循环)
739. 每日温度 根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数.如果之后都不会升高,请在该位置用 0 来代替. 例如,给定一个列表 temper ...
- Java实现 LeetCode 506 相对名次
506. 相对名次 给出 N 名运动员的成绩,找出他们的相对名次并授予前三名对应的奖牌.前三名运动员将会被分别授予 "金牌","银牌" 和" 铜牌&q ...
- Java实现 蓝桥杯VIP 算法训练 FBI树
问题描述 我们可以把由"0"和"1"组成的字符串分为三类:全"0"串称为B串,全"1"串称为I串,既含"0&q ...
- java实现 洛谷 P1427 小鱼的数字游戏
题目描述 小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字(长度不一定,以0结束,最多不超过100个,数字不超过2^32-1),记住了然后反着念出来(表示结束的数字0就不要念出来了).这对小鱼的 ...
- CICD | Jenkins & Gitlab集成:WebHook触发构建
在上一篇博客中,我们学习了Jenkins的搭建和插件+流水线的基本使用方法,Jenkins极大地提升了部署效率. 最近想学习一下如何集成GitLab webhook,实现进一步解放双手,目标: 推送( ...
- Python基础语法之“print()”函数
print()函数是Python入门的第一个必学知识点,它经常被用来调试已写的代码,检验效果,今天小老鼠就带你盘点一下print()函数在Python中如何使用. print()函数的工作流程是这样的 ...