Spring 特点
- 方便解耦,简化开发;
- AOP 编程的支持;
- 声明式事务的支持;
- 方便程序的测试;
- 方便集成各种优秀框架。
Spring IOC
public class SpringDemo1 {
@Test
/**
* 传统方式开发
*/
public void demo1(){
UserService userService = new UserServiceIml();
userService.sayHello();
}
@Test
/**
* Spring的方式实现
*/
public void demo2(){
//创建Spring的工厂
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过工厂获得类
UserService userService = (UserService) applicationContext.getBean("userService");
userService.sayHello();
}
}
复制代码
IOC-Inverse Of Control 控制反转-将原本在程序中手动创建UserService对象的控制权交由Spring框架管理。
DI-Dependency Injection 依赖注入,在Spring创建对象过程中将对象所依赖的属性注入进去。
Spring Bean 管理
XML方式
Bean的实例化的三种方式
<!--Bean的实例化的三种方式-->
<!--第一种:无参构造器的方式-->
<bean id="bean1" class="com.basic.ioc.demo2.Bean1"/>
<!--第二种:静态工厂的方式-->
<bean id="bean2" class="com.basic.ioc.demo2.Bean2Factory" factory-method="createBean2"/>
<!--第三种:实例工厂的方式-->
<bean id="bean3Factory" class="com.basic.ioc.demo2.Bean3Factory"/>-->
<bean id="bean3" factory-bean="bean3Factory" factory-method="createBean3"/>
复制代码
Bean的作用范围
<!-- Bean的作用范围===默认是单例singleton,修改为多例prototype-->
<bean id="person" class="com.basic.ioc.demo3.Person" scope="singleton"/>
<bean id="person" class="com.basic.ioc.demo3.Person" scope="prototype"/>
复制代码
Bean的常用配置
<!--UserService的创建权交给了Spring-->
<bean id="userService" class="com.basic.ioc.demo1.UserServiceIml">
<!--设置属性-->
<property name="name" value="李四"/>
</bean>
复制代码
Bean的生命周期的配置
Bean的属性注入方式
<!--Bean的构造方法的属性注入============================-->
<bean id="user" class="com.basic.ioc.demo4.User">
<constructor-arg name="name" value="张三" />
<constructor-arg name="age" value="23"/>
</bean>
<!--Bean的set方法的属性注入============================-->
<!-- <bean id="person" class="com.basic.ioc.demo4.Person">-->
<!-- <property name="name" value="李四"/>-->
<!-- <property name="age" value="24"/>-->
<!-- <property name="cat" ref="cat"/>-->
<!-- </bean>-->
<!-- <bean id="cat" class="com.basic.ioc.demo4.Cat">-->
<!-- <property name="name" value="Ketty"/>-->
<!-- </bean>-->
<!--Bean的p名称空间的属性注入============================-->
<bean id="person" class="com.basic.ioc.demo4.Person" p:name="香王" p:age="1000" p:cat-ref="cat"/>
<bean id="cat" class="com.basic.ioc.demo4.Cat" p:name="肥波"/>
<!--Bean的SpEL的属性注入=======================-->
<bean id="category" class="com.basic.ioc.demo4.Category">
<property name="name" value="#{'服装'}"/>
</bean>
<bean id="productInfo" class="com.basic.ioc.demo4.ProductInfo"/>
<bean id="product" class="com.basic.ioc.demo4.Product">
<property name="name" value="#{'男装'}"/>
<property name="price" value="#{productInfo.calculatePrice()}"/>
<property name="category" value="#{category}"/>
</bean>
复制代码
复杂数据类型的属性注入
<!--集合类型的属性注入==========================-->
<bean id="collectionBean" class="com.basic.ioc.demo5.CollectionBean">
<!--数组类型-->
<property name="arrs">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<!--List集合的属性注入-->
<property name="list">
<list>
<value>111</value>
<value>222</value>
<value>333</value>
</list>
</property>
<!--Set集合类型属性注入-->
<property name="set">
<set>
<value>lsp1</value>
<value>lsp2</value>
<value>lsp3</value>
</set>
</property>
<!--Map类型属性注入-->
<property name="map">
<map>
<entry key="aaa" value="111"></entry>
<entry key="bbb" value="222"></entry>
<entry key="ccc" value="333"></entry>
</map>
</property>
<!--Properties属性类型-->
<property name="properties">
<props>
<prop key="年龄">21</prop>
<prop key="学历">本科</prop>
</props>
</property>
</bean>
复制代码
Spring的Bean管理的注解
Spring AOP
面向切面编程
SpringAOP使用纯Java实现,不需要经过专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
AOP的底层实现
JDK的动态代理
通过Proxy类的newProxyInstance()方法生成一个指定接口的代理类实例,该方法需要传入三个参数:
- 代理类的类加载器
- 代理类实现的接口
- InvocationHandler接口-指派方法调用的调用处理程序
自定义JDK动态代理类:
public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
//jdk的动态代理,可以对实现接口的类产生代理
//利用Proxy产生UserDao的代理类
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在此对代理对象的方法进行增强,如果是要对save()方法增强,当前仅打印“权限校验”
if("save".equals(method.getName())){
System.out.println("权限校验");
return method.invoke(userDao, args);
}
return method.invoke(userDao, args);
}
}
复制代码
测试类:
public class SpingDemo1 {
@Test
public void demo1(){
UserDao userDao = new UserDaoImpl();
//对目标类进行增强,产生一个代理
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
//使用代理对象来调用目标类的方法
proxy.save();
proxy.delete();
proxy.update();
proxy.find();
}
}
复制代码
执行结果:
权限校验
保存用户
删除用户
修改用户
查询用户
复制代码
CGLIB的动态代理
对于不使用接口的业务类,无法使用jdk动态代理,CGLIB可以为一个类创建一个子类,解决无接口代理问题
使用CGLIB创建代理类需要引入CGLIB的依赖或者引入Spring的依赖
public class MyCglibProxy implements MethodInterceptor{
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao){
this.productDao = productDao;
}
public Object createProxy(){
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(productDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验===================");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
复制代码
public class SpringDemo2 {
@Test
public void demo1(){
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}
复制代码
总结:
-
Spring在运行期,生成动态代理对象,不需要特殊的编译器。Spring AOP的底层就是通过JDK动态代理或CGLIB动态代理技术,为目标Bean执行横向织入。
- 若目标对象实现了若干接口,Spring使用JDK的java.lang.reflect.Proxy类代理。
- 若目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。
-
程序中应优先对接口创建代理,便于程序解耦维护
-
标记为final的方法,不能被代理,因为无法进行覆盖
- JDk动态代理是针对接口生成子类,接口中方法不能使用final修饰
- CGLIB是针对目标生成子类,因此类或方法不能使用final修饰
-
Spring只支持方法连接点,不提供属性连接点
Sping AOP增强类型(通知类型)
按照通知Advice在目标类方法中的连接点位置,分为5类
前置通知 BeforeAdvice:方法执行前实施增强
后置通知 AfterReturningAdvice:方法执行后实施增强
环绕通知 MethodIntercepter:方法执行前后实施增强
异常抛出通知 ThrowAdvice:在方法抛出异常后实施
引介通知:在目标类中添加一些新的方法和属性(不在方法层面)
Sping AOP切面类型
- Advisor:一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:具有切点的切面,可以指定拦截目标类哪些方法
- IntroductionAdvisor:引介切面,针对引介通知而使用切面
Spring通过XML配置产生代理
<!--配置目标类============-->
<bean id="customerDao" class="com.basic.aop.demo4.CustomerDao"/>
<!--配置通知============== -->
<bean id="myAroundAdvice" class="com.basic.aop.demo4.MyAroundAdvice"/>
<!--Spring的AOP 产生代理对象-->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置目标类-->
<property name="target" ref="studentDao"/>
<!--配置通知============== -->
<bean id="myAroundAdvice" class="com.basic.aop.demo4.MyAroundAdvice"/>
<!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式:.任意字符 *任意次数 -->
<!--只对save()方法进行增强-->
<!--<property name="pattern" value=".*save.*"/>-->
<!--只对save()和delete()方法进行增强-->
<property name="patterns" value=".*save.*,.*delete.*"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<!-- 配置产生代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"/>
<!--目标类没有实现接口时,proxyTargetClass属性值为true-->
<property name="proxyTargetClass" value="true"/>
<!--采用拦截的名称-->
<property name="interceptorNames" value="myAdvisor"/>
</bean>
<!-- <!–Spring的AOP 产生代理对象–>-->
<!-- <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">-->
<!-- <!–配置目标类–>-->
<!-- <property name="target" ref="studentDao"/>-->
<!-- <!–实现的接口–>-->
<!-- <property name="proxyInterfaces" value="com.basic.aop.demo3.StudentDao"/>-->
<!-- <!–采用拦截的名称–>-->
<!-- <property name="interceptorNames" value="myBeforeAdvice"/>-->
<!-- <property name="optimize" value="true"></property>-->
<!-- </bean>-->
复制代码
AOP 的自动创建代理方式
- BeanNameAutoProxyCreator 根据Bean名称创建代理
- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理
- AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理
AspectJ的注解开发
通知类型:
- @Before 前置通知
- @AfterReutrning 后置通知
- @Around 环绕通知
- @AfterThrowing 异常抛出通知
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知 (未作学习)
切面类:
@Aspect
public class MyAspectAnno {
@Before(value="execution(* com.basic.aspectJ.demo1.ProductDao.save(..))")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@AfterReturning(value="execution(* com.basic.aspectJ.demo1.ProductDao.update(..))",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
}
@Around(value="execution(* com.basic.aspectJ.demo1.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知================");
Object obj = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕后通知================");
return obj;
}
@AfterThrowing(value="execution(* com.basic.aspectJ.demo1.ProductDao.findOne(..))",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
@After(value="execution(* com.basic.aspectJ.demo1.ProductDao.findAll(..))")
public void after(){
System.out.println("最终通知==================");
}
}
复制代码
通过@Pointcut为切点命名
- 在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义
- 切点方法:private void 无参数方法,方法名为切点名
- 当通知为多个切点时,可以使用||进行连接
修改后的切面类:
@Aspect
public class MyAspectAnno {
@Before(value="myPointcut1()")
public void before(JoinPoint joinPoint){
System.out.println("前置通知=================="+joinPoint);
}
@AfterReturning(value="myPointcut2()",returning = "result")
public void afterReturing(Object result){
System.out.println("后置通知=================="+result);
}
@Around(value="myPointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知================");
Object obj = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕后通知================");
return obj;
}
@AfterThrowing(value="myPointcut4()",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知=============="+e.getMessage());
}
@After(value="myPointcut5()")
public void after(){
System.out.println("最终通知==================");
}
@Pointcut(value="execution(* com.basic.aspectJ.demo1.ProductDao.save(..))")
private void myPointcut1(){}
@Pointcut(value="execution(* com.basic.aspectJ.demo1.ProductDao.update(..))")
private void myPointcut2(){}
@Pointcut(value="execution(* com.basic.aspectJ.demo1.ProductDao.delete(..))")
private void myPointcut3(){}
@Pointcut(value="execution(* com.basic.aspectJ.demo1.ProductDao.findOne(..))")
private void myPointcut4(){}
@Pointcut(value="execution(* com.basic.aspectJ.demo1.ProductDao.findAll(..))")
private void myPointcut5(){}
}
复制代码
基于AspectJ的XML方式开发
定义切面类:
public class MyAspectXml {
// 前置通知
public void before(JoinPoint joinPoint){
System.out.println("XML方式的前置通知=============="+joinPoint);
}
// 后置通知
public void afterReturing(Object result){
System.out.println("XML方式的后置通知=============="+result);
}
// 环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("XML方式的环绕前通知==============");
Object obj = joinPoint.proceed();
System.out.println("XML方式的环绕后通知==============");
return obj;
}
// 异常抛出通知
public void afterThrowing(Throwable e){
System.out.println("XML方式的异常抛出通知============="+e.getMessage());
}
// 最终通知
public void after(){
System.out.println("XML方式的最终通知=================");
}
}
复制代码
使用XML配置切面:
<!--XML的配置方式完成AOP的开发===============-->
<!--配置目标类=================-->
<bean id="customerDao" class="com.basic.aspectJ.demo2.CustomerDaoImpl"/>
复制代码
<!--配置切面类-->
<bean id="myAspectXml" class="com.basic.aspectJ.demo2.MyAspectXml"/>
<!--aop的相关配置=================-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut1" expression="execution(* com.basic.aspectJ.demo2.CustomerDao.save(..))"/>
<aop:pointcut id="pointcut2" expression="execution(* com.basic.aspectJ.demo2.CustomerDao.update(..))"/>
<aop:pointcut id="pointcut3" expression="execution(* com.basic.aspectJ.demo2.CustomerDao.delete(..))"/>
<aop:pointcut id="pointcut4" expression="execution(* com.basic.aspectJ.demo2.CustomerDao.findOne(..))"/>
<aop:pointcut id="pointcut5" expression="execution(* com.basic.aspectJ.demo2.CustomerDao.findAll(..))"/>
<!--配置AOP的切面-->
<aop:aspect ref="myAspectXml">
<!--配置前置通知-->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!--配置后置通知-->
<aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
<!--配置环绕通知-->
<aop:around method="around" pointcut-ref="pointcut3"/>
<!--配置异常抛出通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<!--配置最终通知-->
<aop:after method="after" pointcut-ref="pointcut5"/>
</aop:aspect>
</aop:config>
复制代码
JDBC Template
JDBCTemplate 是 Spring JDBC 的核心类,提供 CRUD 方法
Spring 事务管理
什么事事务
事务是正确执行一系列的操作(或动作),使得数据库从一种状态转换成另一种状态,且保证操作全部成功,或者全部失败。
事务原则是什么
- 事务必须服从ISO/IEC所制定的ACID原则。
- ACID原则的具体内涵如下:
- 原子性(Atomicity):即不可分割性,事务要么全部被执行,要么就全部不被执行。
- 一致性(Consistency):事务的执行使得数据库从一种正确状态转换成另一种正确状态。
- 隔离性(Isolation):在事务正确提交之前,它可能的结果不应显示给任何其他事务。
- 持久性(Durability):事务正确提交后,其结果将永久保存在数据库中。#
Java事务的实现
通过Java代码来实现对数据库的事务性操作
Java事务类型
- JDBC事务:用Connection对象控制,包括手动模式和自动模式;
- JTA(Java Transaction API)事务:与实现无关的,与协议无关的API;
- 容器事务:应用服务器提供的,且大多是基于JTA完成(通常基于JNDI的,相当复杂的API实现)。
Spring事务
数据的读取类型
- 脏读:事务没提交,提前读取;
- 不可重复读:两次读取的数据不一致;
- 幻读:事务不是独立执行时发生的一种非预期现象。
公众号
- 欢迎关注公众号:撸Java源码
推荐阅读: