Spring框架源码分析
Spring核心知识
Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。
为什么说Spring是一个一站式的轻量级开源框架呢?EE开发可分成三层架构,针对JavaEE的三层结构,每一层Spring都提供了不同的解决技术。
• WEB层:SpringMVC
• 业务层:Spring的IoC
• 持久层:Spring的JDBCTemplate(Spring的JDBC模板,ORM模板用于整合其他的持久层框架)
从上面的简要介绍中,我们要知道Spring的核心有两部分:
• IoC:控制反转。
举例来说,在之前的操作中,比方说有一个类,我们想要调用类里面的方法(不是静态方法),就要创建类的对象,使用对象调用方法实现。对于Spring来说,Spring创建对象的过程,不是在代码里面实现的,而是交给Spring来进行配置实现的。
AOP:面向切面编程。
SpringAOP原理
AOP编程技术
什么是AOP编程
AOP: Aspect Oriented Programming 面向切面编程。
面向切面编程(也叫面向方面):Aspect Oriented Programming(AOP),是目前软件开发中的一个热点。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP是OOP的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面(方面)编程。
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改 变这些行为的时候不影响业务逻辑的代码。
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
假设把应用程序想成一个立体结构的话,OOP的利刃是纵向切入系统,把系统划分为很多个模块(如:用户模块,文章模块等等),而AOP的利刃是横向切入系统,提取各个模块可能都要重复操作的部分(如:权限检查,日志记录等等)。由此可见,AOP是OOP的一个有效补充。
注意:AOP不是一种技术,实际上是编程思想。凡是符合AOP思想的技术,都可以看成是AOP的实现。
Aop, aspect object programming 面向切面编程
功能: 让关注点代码与业务代码分离!
关注点
关注点,重复代码就叫做关注点;
切面
关注点形成的类,就叫切面(类)!
面向切面编程,就是指 对很多功能都有的重复的代码抽取,再在运行的时候网业务方法上动态植入“切面类代码”。
切入点
执行目标对象方法,动态植入切面代码。
可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。
AOP底层实现原理
代理设计模式
什么是代理模式
通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既(AOP微实现) ,AOP核心技术面向切面编程。
代理模式应用场景
SpringAOP、事物原理、日志打印、权限控制、远程调用、安全代理 可以隐蔽真实角色
代理的分类
静态代理(静态定义代理类)
动态代理(动态生成代理类)
Jdk自带动态代理
Cglib 、javaassist(字节码操作库)
静态代理
什么是静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
静态代理代码
public interface IUserDao { void save(); } public class UserDao implements IUserDao { public void save() { System.out.println("已经保存数据..."); } } 代理类 public class UserDaoProxy implements IUserDao { private IUserDao target;
public UserDaoProxy(IUserDao iuserDao) { this.target = iuserDao; }
public void save() { System.out.println("开启事物..."); target.save(); System.out.println("关闭事物..."); }
}
|
动态代理
什么是动态代理
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
JDK动态代理
1)原理:是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)
2)实现方式:
1. 通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2. 通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3. 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4. 通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
缺点:jdk动态代理,必须是面向接口,目标业务类必须实现接口
// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象 public class InvocationHandlerImpl implements InvocationHandler { private Object target;// 这其实业务实现类对象,用来调用具体的业务方法 // 通过构造函数传入目标对象 public InvocationHandlerImpl(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; System.out.println("调用开始处理"); result = method.invoke(target, args); System.out.println("调用结束处理"); return result; }
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // 被代理对象 IUserDao userDao = new UserDao(); InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao); ClassLoader loader = userDao.getClass().getClassLoader(); Class<?>[] interfaces = userDao.getClass().getInterfaces(); // 主要装载器、一组接口及调用处理动态代理实例 IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl); newProxyInstance.save(); }
} |
CGLIB动态代理
原理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
什么是CGLIB动态代理
使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码
CGLIB动态代理相关代码
public class CglibProxy implements MethodInterceptor { private Object targetObject; // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理 public Object getInstance(Object target) { // 设置需要创建子类的类 this.targetObject = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开启事物"); Object result = proxy.invoke(targetObject, args); System.out.println("关闭事物"); // 返回代理对象 return result; } public static void main(String[] args) { CglibProxy cglibProxy = new CglibProxy(); UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao()); userDao.save(); } } |
CGLIB动态代理与JDK动态区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
Spring中。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。
因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。
AOP编程使用
注解版本实现AOP
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> 开启事物注解权限 @Aspect 指定一个类为切面类 @Pointcut("execution(* com.itmayiedu.service.UserService.add(..))") 指定切入点表达式 @Before("pointCut_()") 前置通知: 目标方法之前执行 @After("pointCut_()") 后置通知:目标方法之后执行(始终执行) @AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行) @AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行 @Around("pointCut_()") 环绕通知: 环绕目标方法执行
@Component @Aspect public class AopLog {
// 前置通知 @Before("execution(* com.itmayiedu.service.UserService.add(..))") public void begin() { System.out.println("前置通知"); }
// // 后置通知 @After("execution(* com.itmayiedu.service.UserService.add(..))") public void commit() { System.out.println("后置通知"); }
// 运行通知 @AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))") public void returning() { System.out.println("运行通知"); }
// 异常通知 @AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))") public void afterThrowing() { System.out.println("异常通知"); }
// 环绕通知 @Around("execution(* com.itmayiedu.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); } }
|
XML方式实现AOP
Xml实现aop编程: 1) 引入jar文件 【aop 相关jar, 4个】 2) 引入aop名称空间 3)aop 配置 * 配置切面类 (重复执行代码形成的类) * aop配置 拦截哪些方法 / 拦截到方法后应用通知代码 <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" 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">
<!-- dao 实例 -->
<bean id="userService" class="com.itmayiedu.service.UserService"></bean> <!-- 切面类 --> <bean id="aop" class="com.itmayiedu.aop2.AopLog2"></bean> <!-- Aop配置 --> <aop:config> <!-- 定义一个切入点表达式: 拦截哪些方法 --> <aop:pointcut expression="execution(* com.itmayiedu.service.UserService.*(..))" id="pt" /> <!-- 切面 --> <aop:aspect ref="aop"> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="pt" /> <!-- 前置通知: 在目标方法调用前执行 --> <aop:before method="begin" pointcut-ref="pt" /> <!-- 后置通知: --> <aop:after method="after" pointcut-ref="pt" /> <!-- 返回后通知 --> <aop:after-returning method="afterReturning" pointcut-ref="pt" /> <!-- 异常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pt" /> </aop:aspect> </aop:config>
</beans> public class AopLog2 {
// 前置通知 public void begin() { System.out.println("前置通知"); }
// // 后置通知 public void commit() { System.out.println("后置通知"); }
// 运行通知 public void returning() { System.out.println("运行通知"); }
// 异常通知 public void afterThrowing() { System.out.println("异常通知"); }
// 环绕通知 public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知开始"); proceedingJoinPoint.proceed(); System.out.println("环绕通知结束"); } }
|
AOP编程应用场景
日志记录,性能统计,安全控制,事务处理,异常处理
Spring事务使用
事务基本特性
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
事务控制分类
编程式事务控制
自己手动控制事务,就叫做编程式事务控制。
Jdbc代码:
Conn.setAutoCommite(false); // 设置手动控制事务
Hibernate代码:
Session.beginTransaction(); // 开启一个事务
【细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制】
(比较灵活,但开发起来比较繁琐: 每次都要开启、提交、回滚.)
声明式事务控制
Spring提供了对事务的管理, 这个就叫声明式事务管理。
Spring提供了对事务控制的实现。用户如果想用Spring的声明式事务管理,只需要在配置文件中配置即可; 不想使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。
Spring声明式事务管理,核心实现就是基于Aop。
【粗粒度的事务控制: 只能给整个方法应用事务,不可以对方法的某几行应用事务。】
(因为aop拦截的是方法。)
Spring声明式事务管理器类:
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
手写Spring事务框架
编程事务实现
概述
所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
案例
使用编程事务实现手动事务
使用编程事务实现,手动事务 begin、commit、rollback
@Component public class TransactionUtils {
@Autowired private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务 public TransactionStatus begin() { TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transaction; }
// 提交事务 public void commit(TransactionStatus transactionStatus) { dataSourceTransactionManager.commit(transactionStatus); }
// 回滚事务 public void rollback(TransactionStatus transactionStatus) { dataSourceTransactionManager.rollback(transactionStatus); } }
@Service public class UserService { @Autowired private UserDao userDao; @Autowired private TransactionUtils transactionUtils;
public void add() { TransactionStatus transactionStatus = null; try { transactionStatus = transactionUtils.begin(); userDao.add("wangmazi", 27); int i = 1 / 0; System.out.println("我是add方法"); userDao.add("zhangsan", 16); transactionUtils.commit(transactionStatus); } catch (Exception e) { e.printStackTrace(); } finally { if (transactionStatus != null) { transactionStatus.rollbackToSavepoint(transactionStatus); } }
}
} |
AOP技术封装手动事务
@Component @Aspect public class AopTransaction { @Autowired private TransactionUtils transactionUtils;
// // 异常通知 @AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))") public void afterThrowing() { System.out.println("程序已经回滚"); // 获取程序当前事务 进行回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }
// 环绕通知 @Around("execution(* com.itmayiedu.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("开启事务"); TransactionStatus begin = transactionUtils.begin(); proceedingJoinPoint.proceed(); transactionUtils.commit(begin); System.out.println("提交事务"); }
} |
使用事务注意事项
事务是程序运行如果没有错误,会自动提交事物,如果程序运行发生异常,则会自动回滚。
如果使用了try捕获异常时.一定要在catch里面手动回滚。
事务手动回滚代码
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
声明事务实现
概述
管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。
声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
XML实现声明
注解版本声明
<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> |
用法
@Transactional public void add() { userDao.add("wangmazi", 27); int i = 1 / 0; System.out.println("我是add方法"); userDao.add("zhangsan", 16); } |
手写Spring注解版本事务
注解
Jdk1.5新增新技术,注解。很多框架为了简化代码,都会提供有些注解。可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件。
注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
注解分类:内置注解(也成为元注解 jdk 自带注解)、自定义注解(Spring框架)
什么是内置注解
(1) @SuppressWarnings 再程序前面加上可以在javac编译中去除警告--阶段是SOURCE
(2) @Deprecated 带有标记的包,方法,字段说明其过时----阶段是SOURCE
(3)@Overricle 打上这个标记说明该方法是将父类的方法重写--阶段是SOURCE
@Overricle 案例演示
@Override public String toString() { return null; } |
@Deprecated案例演示
new Date().parse(""); |
@SuppressWarnings 案例演示
@SuppressWarnings({ "all" }) public void save() { java.util.List list = new ArrayList(); } |
实现自定义注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
3.@Documented
4.@Inherited
使用@interface 定义注解。
@Target(value = { ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface AddAnnotation {
int userId() default
t 0;
String userName() default "默认名称";
String[]arrays(); } 反射读取注解信息 public static void main(String[] args) throws ClassNotFoundException { Class classInfo = Class.forName("com.itmayiedu.entity.User"); // 获取到所有方法 Method[] methods = classInfo.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); AddAnnotation declaredAnnotation = method.getDeclaredAnnotation(AddAnnotation.class); if (declaredAnnotation == null) { // 结束本次循环 continue; } // 获取userId int userId = declaredAnnotation.userId(); System.out.println("userId:" + userId); // 获取userName String userName = declaredAnnotation.userName(); System.out.println("userName:" + userName); // 获取arrays String[] arrays = declaredAnnotation.arrays(); for (String str : arrays) { System.out.println("str:" + str); } } }
|
自定义事务注解
//编程事务(需要手动begin 手动回滚 手都提交) @Component() @Scope("prototype") // 设置成原型解决线程安全 public class TransactionUtils {
private TransactionStatus transactionStatus; // 获取事务源 @Autowired private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务 public TransactionStatus begin() { transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transactionStatus; }
// 提交事务 public void commit(TransactionStatus transaction) { dataSourceTransactionManager.commit(transaction); }
// 回滚事务 public void rollback() { System.out.println("rollback"); dataSourceTransactionManager.rollback(transactionStatus); }
}
注解类
@Autowired private TransactionUtils transactionUtils;
@AfterThrowing("execution(* com.itmayiedu.service.*.*.*(..))") public void afterThrowing() throws NoSuchMethodException, SecurityException { // isRollback(proceedingJoinPoint); System.out.println("程序发生异常"); // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // TransactionStatus currentTransactionStatus = // TransactionAspectSupport.currentTransactionStatus(); // System.out.println("currentTransactionStatus:" + // currentTransactionStatus); transactionUtils.rollback(); }
// // 环绕通知 在方法之前和之后处理事情 @Around("execution(* com.itmayiedu.service.*.*.*(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 调用方法之前执行 TransactionStatus transactionStatus = begin(proceedingJoinPoint); proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出异常不会执行后面代码 // 调用方法之后执行 commit(transactionStatus); }
public TransactionStatus begin(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
// // 判断是否有自定义事务注解 ExtTransaction declaredAnnotation = getExtTransaction(pjp); if (declaredAnnotation == null) { return null; } // 如果有自定义事务注解,开启事务 System.out.println("开启事务"); TransactionStatus transactionStatu = transactionUtils.begin(); return transactionStatu; }
public void commit(TransactionStatus transactionStatu) { if (transactionStatu != null) { // 提交事务 System.out.println("提交事务"); transactionUtils.commit(transactionStatu); } }
public ExtTransaction getExtTransaction(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 declaredAnnotation = objMethod.getDeclaredAnnotation(ExtTransaction.class); if (declaredAnnotation == null) { System.out.println("您的方法上,没有加入注解!"); return null; } return declaredAnnotation;
}
// 回滚事务 public void isRollback(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException { // // 判断是否有自定义事务注解 ExtTransaction declaredAnnotation = getExtTransaction(pjp); if (declaredAnnotation != null) { System.out.println("已经开始回滚事务"); // 获取当前事务 直接回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return; } }
使用自定义注解
@ExtTransaction public void add() { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); }
|
Spring事物传播行为
Spring中事务的定义:
Propagation(key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。)有以下选项可供使用:
PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。//如果外层方法没有事务,就会以非事务进行执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
--- 如果当前有事务,就是以非事务进行执行
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
默认传播行为为REQUIRED
SpringIOC原理
XML技术
什么是XML
它是可扩展标记语言(Extensible Markup Language,简称XML),是一种标记语言。
XML 全称为可扩展的标记语言。主要用于描述数据和用作配置文件。
XML 文档在逻辑上主要由一下 5 个部分组成:
XML 声明:指明所用 XML 的版本、文档的编码、文档的独立性信息
文档类型声明:指出 XML 文档所用的 DTD
元素:由开始标签、元素内容和结束标签构成
注释:以结束,用于对文档中的内容起一个说明作用
处理指令:通过处理指令来通知其他应用程序来处理非 XML 格式的数据,格式为
XML 文档的根元素被称为文档元素,它和在其外部出现的处理指令、注释等作为文档实体的子节点,根元素本身和其内部的子元素也是一棵树。
XML样例
<?xml version="1.0" encoding="UTF-8"?> <students> <student1 id="001"> <微信公众号>@残缺的孤独</微信公众号> <学号>20140101</学号> <地址>北京海淀区</地址> <座右铭>要么强大,要么听话</座右铭> </student1> <student2 id="002"> <新浪微博>@残缺的孤独</新浪微博> <学号>20140102</学号> <地址>北京朝阳区</地址> <座右铭>在哭泣中学会坚强</座右铭> </student2> </students> |
<?xml version="1.0" encoding="UTF-8"?>
作用xml文件头部要写的话,说明了xml的版本和编码,utf-8一般是网络传输用的编码
XML解析方式?
Dom4j、Sax、Pull
Dom4j与Sax区别
dom4j不适合大文件的解析,因为它是一下子将文件加载到内存中,所以有可能出现内存溢出,sax是基于事件来对xml进行解析的,所以他可以解析大文件的xml,也正是因为如此,所以dom4j可以对xml进行灵活的增删改查和导航,而sax没有这么强的灵活性,所以sax经常是用来解析大型xml文件,而要对xml文件进行一些灵活(crud)操作就用dom4j。
使用dom4j解析xml
解析XML过程是通过获取Document对象,然后继续获取各个节点以及属性等操作,因此获取Document对象是第一步,大体说来,有三种方式:
1.自己创建Document对象
Document document = DocumentHelper.createDocument(); Element root = document.addElement("students"); |
其中students是根节点,可以继续添加其他节点等操作。
2.自己创建Document对象
// 创建SAXReader对象 SAXReader reader = new SAXReader(); // 读取文件 转换成Document Document document = reader.read(new File("XXXX.xml")); |
3.读取XML文本内容获取Document对象
String xmlStr = "<students>......</students>"; Document document = DocumentHelper.parseText(xmlStr); |
解析xml代码
Xml配置:
<?xml version="1.0" encoding="UTF-8"?> <students> <student1 id="001"> <微信公众号>每特学院</微信公众号> <学号>20140101</学号> <地址>北京海淀区</地址> <座右铭>要么强大,要么听话</座右铭> </student1> <student2 id="002"> <新浪微博>蚂蚁课堂</新浪微博> <学号>20140102</学号> <地址>北京朝阳区</地址> <座右铭>在哭泣中学会坚强</座右铭> </student2> </students> |
Java代码
public static void main(String[] args) throws SAXException, DocumentException { XmlUtils xmlUtils = new XmlUtils(); xmlUtils.test001();
}
public void test001() throws DocumentException { SAXReader saxReader = new SAXReader(); Document read = saxReader.read(getClassPath("student.xml")); // 获取根节点 Element rootElement = read.getRootElement(); getNodes(rootElement); }
public InputStream getClassPath(String xmlPath) { InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(xmlPath); return resourceAsStream; }
public static void getNodes(Element rootElement) { System.out.println("获取当前名称:" + rootElement.getName()); // 获取属性信息 List<Attribute> attributes = rootElement.attributes(); for (Attribute attribute : attributes) { System.out.println("属性:" + attribute.getName() + "---" + attribute.getText()); } // 获取属性value String value = rootElement.getTextTrim(); if (!StringUtils.isEmpty(value)) { System.out.println("value:" + value); } // 使用迭代器遍历,继续遍历子节点 Iterator<Element> elementIterator = rootElement.elementIterator(); while (elementIterator.hasNext()) { Element next = elementIterator.next(); getNodes(next); } |
注意:
this.getClass().getClassLoader().getResourceAsStream(xmlPath) 获取当前项目路径xmlfsfs
XML与JSON区别
Xml是重量级数据交换格式,占宽带比较大。
JSON是轻量级交换格式,xml占宽带小。
所有很多互联网公司都会使用json作为数据交换格式
很多银行项目,大多数还是在使用xml。
什么是SpringIOC
spring ioc指的是控制反转,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。交由Spring来管理这些,实现解耦
SpringIOC原理
使用反射机制+XML技术
手写SpringIOCXML版本
/** * 手写Spring专题 XML方式注入bean * * @author 作者:余胜军 * */ public class ClassPathXmlApplicationContext { // xml路径地址 private String xmlPath;
public ClassPathXmlApplicationContext(String xmlPath) { this.xmlPath = xmlPath; }
public Object getBean(String beanId) throws Exception { // 1. 读取配置文件 List<Element> elements = readerXml(); if (elements == null) { throw new Exception("该配置文件没有子元素"); } // 2. 使用beanId查找对应的class地址 String beanClass = findXmlByIDClass(elements, beanId); if (StringUtils.isEmpty(beanClass)) { throw new Exception("未找到对应的class地址"); } // 3. 使用反射机制初始化,对象 Class<?> forName = Class.forName(beanClass); return forName.newInstance(); }
// 读取配置文件信息 public List<Element> readerXml() throws DocumentException { SAXReader saxReader = new SAXReader(); if (StringUtils.isEmpty(xmlPath)) { new Exception("xml路径为空..."); } Document read = saxReader.read(getClassXmlInputStream(xmlPath)); // 获取根节点信息 Element rootElement = read.getRootElement(); // 获取子节点 List<Element> elements = rootElement.elements(); if (elements == null || elements.isEmpty()) { return null; } return elements; }
// 使用beanid查找该Class地址 public String findXmlByIDClass(List<Element> elements, String beanId) throws Exception { for (Element element : elements) { // 读取节点上是否有value String beanIdValue = element.attributeValue("id"); if (beanIdValue == null) { throw new Exception("使用该beanId为查找到元素"); } if (!beanIdValue.equals(beanId)) { continue; } // 获取Class地址属性 String classPath = element.attributeValue("class"); if (!StringUtils.isEmpty(classPath)) { return classPath; } } return null; }
// 读取xml配置文件 public InputStream getClassXmlInputStream(String xmlPath) { InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(xmlPath); return resourceAsStream; }
} |
手写SpringIOC注解版本
/** * 手写Spring专题 注解版本注入bean * * @author 作者:余胜军 * */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class ClassPathXmlApplicationContext { // 扫包范围 private String packageName; ConcurrentHashMap<String, Object> initBean = null;
public ClassPathXmlApplicationContext(String packageName) { this.packageName = packageName; }
// 使用beanID查找对象 public Object getBean(String beanId) throws Exception { // 1.使用反射机制获取该包下所有的类已经存在bean的注解类 List<Class> listClassesAnnotation = findClassExisService(); if (listClassesAnnotation == null || listClassesAnnotation.isEmpty()) { throw new Exception("没有需要初始化的bean"); } // 2.使用Java反射机制初始化对象 initBean = initBean(listClassesAnnotation); if (initBean == null || initBean.isEmpty()) { throw new Exception("初始化bean为空!"); } // 3.使用beanID查找查找对应bean对象 Object object = initBean.get(beanId); // 4.使用反射读取类的属性,赋值信息 attriAssign(object); return object; }
// 使用反射读取类的属性,赋值信息 public void attriAssign(Object object) throws IllegalArgumentException, IllegalAccessException { // 1.获取类的属性是否存在 获取bean注解 Class<? extends Object> classInfo = object.getClass(); Field[] declaredFields = classInfo.getDeclaredFields(); for (Field field : declaredFields) { // 属性名称 String name = field.getName(); // 2.使用属性名称查找bean容器赋值 Object bean = initBean.get(name); if (bean != null) { // 私有访问允许访问 field.setAccessible(true); // 给属性赋值 field.set(object, bean); continue; } }
}
// 使用反射机制获取该包下所有的类已经存在bean的注解类 public List<Class> findClassExisService() throws Exception { // 1.使用反射机制获取该包下所有的类 if (StringUtils.isEmpty(packageName)) { throw new Exception("扫包地址不能为空!"); } // 2.使用反射技术获取当前包下所有的类 List<Class<?>> classesByPackageName = ClassUtil.getClasses(packageName); // 3.存放类上有bean注入注解 List<Class> exisClassesAnnotation = new ArrayList<Class>(); // 4.判断该类上属否存在注解 for (Class classInfo : classesByPackageName) { ExtService extService = (ExtService) classInfo.getDeclaredAnnotation(ExtService.class); if (extService != null) { exisClassesAnnotation.add(classInfo); continue; } } return exisClassesAnnotation; }
// 初始化bean对象 public ConcurrentHashMap<String, Object> initBean(List<Class> listClassesAnnotation) throws InstantiationException, IllegalAccessException { ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<String, Object>(); for (Class classInfo : listClassesAnnotation) { // 初始化对象 Object newInstance = classInfo.newInstance(); // 获取父类名称 String beanId = toLowerCaseFirstOne(classInfo.getSimpleName()); concurrentHashMap.put(beanId, newInstance); } return concurrentHashMap; }
// 首字母转小写 public static String toLowerCaseFirstOne(String s) { if (Character.isLowerCase(s.charAt(0))) return s; else return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString(); }
} |
常用反射工具类
public class ClassUtil {
/** * 取得某个接口下所有实现这个接口的类 */ public static List<Class> getAllClassByInterface(Class c) { List<Class> returnClassList = null;
if (c.isInterface()) { // 获取当前的包名 String packageName = c.getPackage().getName(); // 获取当前包下以及子包下所以的类 List<Class<?>> allClass = getClasses(packageName); if (allClass != null) { returnClassList = new ArrayList<Class>(); for (Class classes : allClass) { // 判断是否是同一个接口 if (c.isAssignableFrom(classes)) { // 本身不加入进去 if (!c.equals(classes)) { returnClassList.add(classes); } } } } }
return returnClassList; }
/* * 取得某一类所在包的所有类名 不含迭代 */ public static String[] getPackageAllClassName(String classLocation, String packageName) { // 将packageName分解 String[] packagePathSplit = packageName.split("[.]"); String realClassLocation = classLocation; int packageLength = packagePathSplit.length; for (int i = 0; i < packageLength; i++) { realClassLocation = realClassLocation + File.separator + packagePathSplit[i]; } File packeageDir = new File(realClassLocation); if (packeageDir.isDirectory()) { String[] allClassName = packeageDir.list(); return allClassName; } return null; }
/** * 从包package中获取所有的Class * * @param pack * @return */ public static List<Class<?>> getClasses(String packageName) {
// 第一个class类的集合 List<Class<?>> classes = new ArrayList<Class<?>>(); // 是否循环迭代 boolean recursive = true; // 获取包的名字 并进行替换 String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的things Enumeration<URL> dirs; try { dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); // 得到协议的名称 String protocol = url.getProtocol(); // 如果是以文件的形式保存在服务器上 if ("file".equals(protocol)) { // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 以文件的方式扫描整个包下的文件 并添加到集合中 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // 如果是jar包文件 // 定义一个JarFile JarFile jar; try { // 获取jar jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 从此jar包 得到一个枚举类 Enumeration<JarEntry> entries = jar.entries(); // 同样的进行循环迭代 while (entries.hasMoreElements()) { // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件 JarEntry entry = entries.nextElement(); String name = entry.getName(); // 如果是以/开头的 if (name.charAt(0) == '/') { // 获取后面的字符串 name = name.substring(1); } // 如果前半部分和定义的包名相同 if (name.startsWith(packageDirName)) { int idx = name.lastIndexOf('/'); // 如果以"/"结尾 是一个包 if (idx != -1) { // 获取包名 把"/"替换成"." packageName = name.substring(0, idx).replace('/', '.'); } // 如果可以迭代下去 并且是一个包 if ((idx != -1) || recursive) { // 如果是一个.class文件 而且不是目录 if (name.endsWith(".class") && !entry.isDirectory()) { // 去掉后面的".class" 获取真正的类名 String className = name.substring(packageName.length() + 1, name.length() - 6); try { // 添加到classes classes.add(Class.forName(packageName + '.' + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } } catch (IOException e) { e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); }
return classes; }
/** * 以文件的形式来获取包下的所有Class * * @param packageName * @param packagePath * @param recursive * @param classes */ public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) { // 获取此包的目录 建立一个File File dir = new File(packagePath); // 如果不存在或者 也不是目录就直接返回 if (!dir.exists() || !dir.isDirectory()) { return; } // 如果存在 就获取包下的所有文件 包括目录 File[] dirfiles = dir.listFiles(new FileFilter() { // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件) public boolean accept(File file) { return (recursive && file.isDirectory()) || (file.getName().endsWith(".class")); } }); // 循环所有文件 for (File file : dirfiles) { // 如果是目录 则继续扫描 if (file.isDirectory()) { findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes); } else { // 如果是java类文件 去掉后面的.class 只留下类名 String className = file.getName().substring(0, file.getName().length() - 6); try { // 添加到集合中去 classes.add(Class.forName(packageName + '.' + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } |
Maven相关依赖
<dependencies> <!-- 引入Spring-AOP等相关Jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_2</version> </dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
</dependencies> |
SpringIOC容器核心接口
SpringMVC原理
SpringMVC的运行流程
⑴ 用户发送请求至前端控制器DispatcherServlet
⑵ DispatcherServlet收到请求调用HandlerMapping处理器映射器。
⑶ 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
⑷ DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
⑸ 执行处理器(Controller,也叫后端控制器)。
⑹ Controller执行完成返回ModelAndView
⑺ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
⑻ DispatcherServlet将ModelAndView传给ViewReslover视图解析器
⑼ ViewReslover解析后返回具体View
⑽ DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
⑾ DispatcherServlet响应用户。
回顾Servet知识
什么是Servlet
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。
Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:
性能明显更好。
Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
Servlet 是独立于平台的,因为它们是用 Java 编写的。
服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。
什么是Servlet生命周期
Servlet 生命周期:
Servlet 加载—>实例化—>服务—>销毁。
init():
在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
service():
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
destroy():
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
手写SpringMVC思路
1.web.xml加载
为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。
2、初始化阶段
在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:
- 加载配置文件
- 扫描用户配置包下面所有的类
- 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
- 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出
3、运行阶段
每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:
- 异常的拦截
- 获取请求传入的参数并处理参数
- 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用
手写SpringMVC基本实现
/** * 手写SpringMVC框架 作者:每特教育-余胜军<br> * * @QQ644064779 1.自定义DispatcherServlet<br> * 2.servlet init()方法初始化###只会执行一次<br> * ######2.1获取当前包下所有的类<br> * ######2.2初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)- * value( 当前实例对象) <br> * ######2.3初始化HandlerMapping方法,将url和方法对应上 <br> * ########2.3.1使用Java反射技术读取类的信息,存放在map集合中key为url请求地址,value为对应方法 * <br> * ########2.3.2使用Java反射技术读取类的信息,存放在map集合中key为url请求地址,value为对应实例对象 * <br> * 3.servlet get或者post请求<br> * ######## 3.1.1获取请求地址,使用Java反射技术找到对应的方法和实例对象进行执行 <br> */ public class ExtDispatcherServlet extends HttpServlet { // mvc bean key=beanid ,value=对象 private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<String, Object>(); // mvc 请求方法 key=requestUrl,value=对象 private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<String, Object>(); // mvc 请求方法 key=requestUrl,value=方法 private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<String, String>();
/** * 初始化自定义SpringMVC容器 */ public void init() throws ServletException { try { // 1.获取当前包下所有的类 List<Class<?>> classes = ClassUtil.getClasses("com.itmayiedu.ext.controller"); // 2.初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)-value( // 当前实例对象) findClassMVCBeans(classes); // 3.初始化HandlerMapping方法,将url和方法对应上 handlerMapping(mvcBeans);
} catch (Exception e) {
} }
// 2.初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)-value( // 当前实例对象) public void findClassMVCBeans(List<Class<?>> classes) throws ClassNotFoundException, InstantiationException, IllegalAccessException { mvcBeans = new ConcurrentHashMap<String, Object>(); for (Class<?> classInfo : classes) { ExtController extController = classInfo.getDeclaredAnnotation(ExtController.class); if (extController != null) { // 默认类名小写 作为bean的名称 String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName()); mvcBeans.put(beanId, ClassUtil.newInstance(classInfo)); } }
}
// 3.初始化HandlerMapping方法,将url和方法对应上 public void handlerMapping(ConcurrentHashMap<String, Object> mvcBeans) { // 遍历mvc bean对象 for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) { // springmvc 注入object对象 Object mvcObject = entry.getValue(); // 判断类上是否有@ExtRequestMapping注解 Class<? extends Object> classInfo = mvcObject.getClass(); String requestBaseUrl = null; ExtRequestMapping classExtRequestMapping = classInfo.getAnnotation(ExtRequestMapping.class); if (classExtRequestMapping != null) { requestBaseUrl = classExtRequestMapping.value(); } // 遍历当前类的所有方法,判断方法上是否有注解 Method[] declaredMethods = classInfo.getDeclaredMethods(); for (Method method : declaredMethods) { ExtRequestMapping methodExtRequestMapping = method.getDeclaredAnnotation(ExtRequestMapping.class); if (methodExtRequestMapping != null) { String httpRequestUrl = methodExtRequestMapping.value(); mvcBeanUrl.put(requestBaseUrl + httpRequestUrl, mvcObject); mvcMethodUrl.put(requestBaseUrl + httpRequestUrl, method.getName()); } } } }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { doDispatch(req, resp); } catch (Exception e) { // TODO: handle exception } }
public void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // 1.获取请求url地址 String requestUrl = req.getRequestURI(); // 2.使用请求url查找对应mvc 控制器bean Object object = mvcBeanUrl.get(requestUrl); if (object == null) { resp.getWriter().println("http ext not found controller 404"); return; } // 3.获取对应的请求方法 String methodName = mvcMethodUrl.get(requestUrl); if (StringUtils.isEmpty(methodName)) { resp.getWriter().println("http ext not found Method 404"); return; } // 4.使用java反射技术执行方法 Class<? extends Object> classInfo = object.getClass(); String resultPage = (String) methodInvoke(classInfo, object, methodName); // 5.视图展示 viewdisplay(resultPage, req, resp); }
// 执行方法 public Object methodInvoke(Class<? extends Object> classInfo, Object object, String methodName) { try { Method method = classInfo.getMethod(methodName); Object result = method.invoke(object); return result; } catch (Exception e) { e.printStackTrace(); return null; } }
// 视图展示 public void viewdisplay(String pageName, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // 获取后缀信息 String suffix = ".jsp"; // 页面目录地址 String prefix = "/"; req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res); }
}
|
数据库连接池原理
基本原理
在内部对象池中,维护一定数量的数据库连接,并对外暴露数据库连接的获取和返回方法。
如外部使用者可通过getConnection方法获取数据库连接,使用完毕后再通过releaseConnection方法将连接返回,注意此时的连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。
线程池作用
①资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,增进了系统环境的平稳性(减少内存碎片以级数据库临时进程、线程的数量)
②更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池内备用。此时连接池的初始化操作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
③新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接技术。
④统一的连接管理,避免数据库连接泄露
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用的连接,从而避免了常规数据库连接操作中可能出现的资源泄露
常用数据库连接池
C3P0
C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
BoneCP
BoneCP 是一个开源的快速的 JDBC 连接池。BoneCP很小,只有四十几K(运行时需要log4j和Google Collections的支持,这二者加起来就不小了),而相比之下 C3P0 要六百多K。另外个人觉得 BoneCP 有个缺点是,JDBC驱动的加载是在连接池之外的,这样在一些应用服务器的配置上就不够灵活。当然,体积小并不是 BoneCP 优秀的原因,BoneCP 到底有什么突出的地方呢,请看看性能测试报告。池,Tomcat的数据源使用的就是DBCP。目前 DBCP 有两个版本分别是 1.3 和 1.4。1.3 版
DBCP
DBCP (Database Connection Pool)是一个依赖Jakarta commons-pool对象池机制的数据库连接本对应的是 JDK 1.4-1.5 和 JDBC 3,而1.4 版本对应 JDK 1.6 和 JDBC 4。因此在选择版本的时候要看看你用的是什么 JDK 版本了,功能上倒是没有什么区别。
Proxool
Proxool是一个Java SQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。
DBCP与C3P0区别
dbcp没有自动的去回收空闲连接的功能 c3p0有自动回收空闲连接功能 两者主要是对数据连接的处理方式不同!C3P0提供最大空闲时间,DBCP提供最大连接数。 前者当连接超过最大空闲连接时间时,当前连接就会被断掉。DBCP当连接数超过最大连接数时,所有连接都会被断
纯手写数据库连接池
读取外部配置信息
//外部配置文件信息 public class DbBean {
/* 链接属性 */ private String driverName = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql://localhost:3306/test";
private String userName = "root";
private String password = "root";
private String poolName = "thread01";// 连接池名字
private int minConnections = 1; // 空闲池,最小连接数
private int maxConnections = 10; // 空闲池,最大连接数
private int initConnections = 5;// 初始化连接数
private long connTimeOut = 1000;// 重复获得连接的频率
private int maxActiveConnections = 100;// 最大允许的连接数,和数据库对应
private long connectionTimeOut = 1000 * 60 * 20;// 连接超时时间,默认20分钟
public String getDriverName() { return driverName; }
public void setDriverName(String driverName) { this.driverName = driverName; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPoolName() { return poolName; }
public void setPoolName(String poolName) { this.poolName = poolName; }
public int getMinConnections() { return minConnections; }
public void setMinConnections(int minConnections) { this.minConnections = minConnections; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; }
public int getInitConnections() { return initConnections; }
public void setInitConnections(int initConnections) { this.initConnections = initConnections; }
public long getConnTimeOut() { return connTimeOut; }
public void setConnTimeOut(long connTimeOut) { this.connTimeOut = connTimeOut; }
public int getMaxActiveConnections() { return maxActiveConnections; }
public void setMaxActiveConnections(int maxActiveConnections) { this.maxActiveConnections = maxActiveConnections; }
public long getConnectionTimeOut() { return connectionTimeOut; }
public void setConnectionTimeOut(long connectionTimeOut) { this.connectionTimeOut = connectionTimeOut; }
}
|
创建数据库连接池
/** * 数据库连接池<br> * * 1.初始化<br> * ####线程池核心容器 空闲线程数、活动线程数<br> * ###构造函数 1.1.1初始化线程,存放在空闲线程池中<br> * 2.获取连接 <br> * ####1.判断存在线程数是否大于最大线程 如果大于最大线程数,则进行等待...<br> * ####2.判断空闲线程数是否大于0 如果空闲线程数<0,创建新的连接<br> * ####3.如果空闲线程数>0,则获取当前空闲线程,存入在活动线程集合中 <br> * 3.释放连接 <br> * ####3.1.1.判断空闲线程数是否大于最大线程数 <br> * ####3.1.2.如果空闲线程数小于最大线程数,将该连接收回到 空闲 线程集合中<br> * ####3.1.3.删除该连接对应的活动线程集合数据<br> * <br> * * * 作者: 每特教育-余胜军<br> * 联系方式:QQ644064779|WWW.itmayiedu.com<br> */ public class ConnectionPool implements IConnectionPool {
// 空闲线程集合 private List<Connection> freeConnection = new Vector<Connection>(); // 活动线程集合 private List<Connection> activeConnection = new Vector<Connection>(); // 记录线程总数 private static int connCount = 0; private DbBean dbBean;
public ConnectionPool(DbBean dbBean) { this.dbBean = dbBean; init(); }
public void init() { try {
for (int i = 0; i < dbBean.getInitConnections(); i++) { Connection newConnection = newConnection(); if (newConnection != null) { // 添加到空闲线程中... freeConnection.add(newConnection); } }
} catch (Exception e) {
} }
// 创建新的Connection private Connection newConnection() { try { if (dbBean == null) { return null; } Class.forName(dbBean.getDriverName()); Connection connection = DriverManager.getConnection(dbBean.getUrl(), dbBean.getUserName(), dbBean.getPassword()); connCount++; return connection; } catch (Exception e) { return null; }
}
public Connection getConnection() { // * ####1.判断活动线程数是否大于最大线程 如果大于最大线程数,则进行等待...<br> Connection connection = null; try {
if (connCount < dbBean.getMaxActiveConnections()) { // 还有活动线程可以使用 // * ####2.判断空闲线程数是否大于0 如果空闲线程数<0,创建新的连接<br> if (freeConnection.size() > 0) { connection = freeConnection.remove(0);// 等于freeConnection.get(0);freeConnection.remove(0); } else { // 创建新的连接 connection = newConnection(); }
boolean available = isAvailable(connection); if (available) { activeConnection.add(connection); } else { connCount--;// i--操作 connection = getConnection();// 递归调用getConnection方法 } } else { // 大于最大线程数,进行等待,重新获取连接 wait(dbBean.getConnTimeOut()); connection = getConnection();// 递归调用getConnection方法 } } catch (Exception e) { e.printStackTrace(); }
// * ####3.如果空闲线程数>0,则获取当前空闲线程,存入在活动线程集合中 <br> return connection; }
// 判断连接是否可用 public boolean isAvailable(Connection connection) { try { if (connection == null || connection.isClosed()) { return false; }
} catch (Exception e) { // TODO: handle exception } return true;
}
public void releaseConnection(Connection connection) { try { if (connection == null) { return; } if (isAvailable(connection)) { // 判断空闲线程数是否大于最大线程数 if (freeConnection.size() < dbBean.getMaxConnections()) { freeConnection.add(connection); } else { // 空闲线程数已经满了 connection.close(); } activeConnection.remove(connection); connCount--; notifyAll();
}
} catch (Exception e) {
}
}
}
|
测试运行结果
public class Test001 {
public static void main(String[] args) {
DBThread dBThread = new DBThread(); for (int i = 1; i <= 3; i++) { Thread thread = new Thread(dBThread, "用户线程" + i); thread.start(); }
}
}
class DBThread implements Runnable {
public void run() { for (int i = 0; i < 10; i++) { Connection connection = ConnectionPoolManager.getConnection(); System.out.println(Thread.currentThread().getName() + ",connection:" + connection); ConnectionPoolManager.releaseConnection(connection); } }
} |