时间:2017年09月03日星期日
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:https://github.com/zccodere/s...
学习源码:https://github.com/zccodere/s...
课程章节
概览 AOP使用 AOP原理 AOP开源运用 课程实战 课程总结
面向切面编程是一种编程范式
编程范式概览
面向过程编程 面向对象编程 面向函数编程(函数式编程) 事件驱动编程(GUI开发中比较常见) 面向切面编程
AOP是什么
是一种编程范式,不是编程语言 解决特定问题,不能解决所有问题 是OOP的补充,不是替代
AOP的初衷
DRY:Don’t Repeat Yourself代码重复性问题 SOC:Separation of Concerns关注点分离 -水平分离:展示层->服务层->持久层 -垂直分离:模块划分(订单、库存等) -切面分离:分离功能性需求与非功能性需求
使用AOP的好处
集中处理某一关注点/横切逻辑 可以很方便地添加/删除关注点 侵入性少,增强代码可读性及可维护性
AOP的应用场景
权限控制 缓存控制 事务控制 审计日志 性能监控 分布式追踪 异常处理
支持AOP的编程语言
Java .NET C/C++ Ruby Python PHP …1-2 简单案例
案例背景
产品管理的服务 产品添加、删除的操作只能管理员才能进行 普通实现VS AOP实现
创建一个名为springaopguide的maven项目pom如下
完成后的项目结构如下
代码编写
1.编写Product类
package com.myimooc.springaopguide.domain; /** * @title 产品领域模型 * @describe 产品实体对象 * @author zc * @version 1.0 2017-09-03 */ public class Product { private Long id; private String name; 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; } }
2.编写CurrentUserHolder类
package com.myimooc.springaopguide.security; /** * @title 获取用户信息 * @describe 模拟用户的切换,将用户信息存入当前线程 * @author zc * @version 1.0 2017-09-03 */ public class CurrentUserHolder { private static final ThreadLocalholder = new ThreadLocal<>(); public static String get(){ return holder.get() == null ? "unkown" : holder.get(); } public static void set(String user){ holder.set(user); } }
3.编写AdminOnly类
package com.myimooc.springaopguide.security; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @title 管理员权限注解 * @describe 被该注解声明的方法需要管理员权限 * @author zc * @version 1.0 2017-09-03 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AdminOnly { }
4.编写SecurityAspect类
package com.myimooc.springaopguide.security; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.myimooc.springaopguide.service.AuthService; /** * @title 权限校验切面类 * @describe * @author zc * @version 1.0 2017-09-03 */ // 声明为一个切面 @Aspect @Component public class SecurityAspect { @Autowired private AuthService authService; // 使用要拦截标注有AdminOnly的注解进行操作 @Pointcut("@annotation(AdminOnly)") public void adminOnly(){ } @Before("adminOnly()") public void check(){ authService.checkAccess(); } }
5.编写AuthService类
package com.myimooc.springaopguide.service; import java.util.Objects; import org.springframework.stereotype.Service; import com.myimooc.springaopguide.security.CurrentUserHolder; /** * @title 权限校验类 * @describe 对用户权限进行校验 * @author zc * @version 1.0 2017-09-03 */ @Service public class AuthService { public void checkAccess(){ String user = CurrentUserHolder.get(); if(!Objects.equals("admin", user)){ throw new RuntimeException("operation not allow"); } } }
6.编写ProductService类
package com.myimooc.springaopguide.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.myimooc.springaopguide.domain.Product; /** * @title 产品服务类 * @describe 产品相关业务服务-传统方式实现权限校验 * @author zc * @version 1.0 2017-09-03 */ @Service public class ProductService { @Autowired private AuthService AuthService; public void insert(Product product){ AuthService.checkAccess(); System.out.println("insert product"); } public void delete(Long id){ AuthService.checkAccess(); System.out.println("delete product"); } }
7.编写ProductServiceAop类
package com.myimooc.springaopguide.service; import org.springframework.stereotype.Service; import com.myimooc.springaopguide.domain.Product; import com.myimooc.springaopguide.security.AdminOnly; /** * @title 产品服务类 * @describe 产品相关业务服务-AOP方式实现权限校验 * @author zc * @version 1.0 2017-09-03 */ @Service public class ProductServiceAop { @AdminOnly public void insert(Product product){ System.out.println("insert product"); } @AdminOnly public void delete(Long id){ System.out.println("delete product"); } }
8.编写AopGuideApplicationTests类
package com.myimooc.springaopguide; 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.myimooc.springaopguide.security.CurrentUserHolder; import com.myimooc.springaopguide.service.ProductService; import com.myimooc.springaopguide.service.ProductServiceAop; /** * @title 单元测试类 * @describe 测试权限校验服务是否生效 * @author zc * @version 1.0 2017-09-03 */ @RunWith(SpringRunner.class) @SpringBootTest public class AopGuideApplicationTests { @Autowired private ProductService productService; @Test(expected = Exception.class) public void annoInsertTest(){ CurrentUserHolder.set("tom"); productService.delete(1L); } @Test public void adminInsertTest(){ CurrentUserHolder.set("admin"); productService.delete(1L); } @Autowired private ProductServiceAop productServiceAop; @Test(expected = Exception.class) public void annoInsertAopTest(){ CurrentUserHolder.set("tom"); productServiceAop.delete(1L); } @Test public void adminInsertAopTest(){ CurrentUserHolder.set("admin"); productServiceAop.delete(1L); } }第二章:使用详解 2-1 本节内容
Spring AOP使用方式
XML配置+Pointcut expression【不推荐使用方式】 注解方式+ Pointcut expression【推荐使用该方式】
Aspectj注解
@Aspect:用于声明当前类是一个切面 @Pointcut:用于描述在哪些类、哪些方法上执行切面的代码 Advice:描述想要在这些方法执行的什么时机进行拦截
本章内容
Pointcut express:切面表达式 5种Advice:建言的五种细分怎么使用2-2 切面表达式
切面表达式
1.designators(指示器) execution() 描述通过什么样的方式去匹配哪些类、哪些方法 2.wildcards(通配符) * .. + 使用通配符进行描述 3.operators(运算符) && || ! 使用运算符进行多条件的判断
Designators(指示器)
匹配方法 execution() 匹配注解 @target() @args() @within() @annotation() 匹配包/类型 @within() 匹配对象 this() bean() target() 匹配参数 args()
Wildcards(通配符)
* 匹配任意数量的字符 + 匹配指定类及其子类 .. 一般用于匹配任意参数的子包或参数
Operators(运算符)
&& 与操作符 || 或操作符 ! 非操作符2-3 匹配包类
// 匹配 ProductServiceAop 类里面的所有方法 @Pointcut("within(com.myimooc.springaopguide.service.ProductServiceAop)") public void matchType(){} // 匹配 com.myimooc.springaopguide.service 包及子包下所有类的方法 @Pointcut("within(com.myimooc.springaopguide.service..*)") public void matchPackage(){}2-4 匹配对象
// 匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法 @Pointcut("this(com.myimooc.springaopguide.dao.DemoDao)") public void testDemo(){} // 匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法,这里即DemoDao的方法 @Pointcut("target(com.myimooc.springaopguide.dao.IDao)") public void targetDemo(){} // 匹配所有以Service结尾的bean里面的方法 @Pointcut("bean(*Service)") public void beanDemo(){}2-5 匹配参数
// 匹配任何以find开头而且只有一个Long参数的方法 @Pointcut("execution(* *..find*(Long))") public void argsDemo1(){} // 匹配任何只有一个Long参数的方法 @Pointcut("args(Long)") public void argsDemo2(){} // 匹配任何以find开头而且第一个参数为Long型的方法 @Pointcut("execution(* *..find*(Long,..))") public void argsDemo3(){} // 匹配第一个参数为Long型的方法 @Pointcut("args(Long,..))") public void argsDemo4(){}2-6 匹配注解
// 匹配方法标注有AdminOnly的注解的方法 @Pointcut("@annotation(com.myimooc.springaopguide.security.AdminOnly)") public void annoDemo(){} // 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolicy级别为CLASS @Pointcut("@within(com.google.common.annotations.Beta)") public void annoWithDemo(){} // 匹配标注有Repository的类底下的方法,要求的RetentionPolicy级别为RUNTIME @Pointcut("@target(org.springframework.stereotype.Repository)") public void annoTargetDemo(){} // 匹配传入的参数类标注有Repository注解的方法 @Pointcut("@args(org.springframework.stereotype.Repository)") public void annoArgsDemo(){}2-7 匹配方法
execution()格式
execution( modifier-pattern? // 修饰符匹配 ret-type-pattern // 返回值匹配 declaring-type-pattern? // 描述值包名 name-pattern(param-pattern) // 方法名匹配(参数匹配) throws-pattern?// 抛出异常匹配 )
execution()实例
// 匹配 使用public修饰符 任意返回值 在com.myimooc.springaopguide.service包及子下 // 以Service结尾的类 任意方法(任意参数) @Pointcut("execution(public * com.myimooc.springaopguide.service..*Service.*(..))") public void matchCondition(){}2-8 建言注解
5中Advice(建言)注解
@Before,前置通知 @After(finally),后置通知,方法执行完之后 @AfterReturning,返回通知,成功执行之后 @AfterThrowing,异常通知,抛出异常之后 @Around,环绕通知
5中Advice(建言)实例
// 定义切点,拦截使用NeedSecured注解修饰的方法 @Pointcut("@within(com.myimooc.demo.security.NeedSecured)") public void annoTargetVsWithinDemo(){} // 使用NeedSecured注解修饰 且 在com.myimooc包下的方法 @Before("annoTargetVsWithinDemo() && within(com.myimooc..*)") public void beforeDemo(){ System.out.println("被拦截方法执行之前执行"); } @After("annoTargetVsWithinDemo() && within(com.myimooc..*)") public void afterDemo(){ System.out.println("被拦截方法执行之后执行"); } @AfterReturning("annoTargetVsWithinDemo() && within(com.myimooc..*)") public void afterReturning(){ System.out.println("代码成功之后执行"); } @AfterThrowing("annoTargetVsWithinDemo() && within(com.myimooc..*)") public void afterThrowing(){ System.out.println("代码执行抛出异常之后执行"); } @Around("annoTargetVsWithinDemo() && within(com.myimooc..*)") public Object aroundDemo(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("相当于@Before"); try{ Object result = pjp.proceed(pjp.getArgs()); System.out.println("相当于@AfterReturning"); return result; }catch (Throwable throwable) { System.out.println("相当于@AfterThrowing"); throw throwable; }finally { System.out.println("相当于@After"); } }
Advice中的参数及结果绑定
@Before("annoTargetVsWithinDemo() && within(com.myimooc..*) && args(userId)") public void beforeWithArgs(JoinPoint joinPoint,Long userId){ System.out.println("被拦截方法执行之前执行,args:"+userId); } @AfterReturning(value="annoTargetVsWithinDemo() && within(com.myimooc..*)",returning="returnValue") public void getResult(Object returnValue){ if(returnValue != null){ System.out.println("代码成功之后执行,result:"+returnValue); } }第三章:实现原理 3-1 本节内容
上节回顾
Pointcut expression的组成部分 各种designators的区别 5中advice及参数、结果绑定
实现原理
概述 设计:代理模式、责任链模式 实现:JDK实现、cglib实现3-2 原理概述
原理概述:植入的时机
1.编译期(AspectJ) 2.类加载时(Aspectj 5+) 3.运行时(Spring AOP)【本节课讲解内容】
运行时值入
运行时织入是怎么实现的 从静态代理到动态代理 基于接口代理与基于继承代理3-3 代理模式
代理AOP对象
Caller:调用方 Proxy:AOP代理对象 Target:目标对象
代理模式类图
客户端通过接口来引用目标对象 代理对象把真正的方法委托目标对象来执行,自己执行额外的逻辑
代码编写
1.编写Subject类
package com.myimooc.myproxydemo.pattern; /** * @title 代理对象接口 * @describe * @author zc * @version 1.0 2017-09-13 */ public interface Subject { void request(); }
2.编写RealSubject类
package com.myimooc.myproxydemo.pattern; /** * @title 目标对象 * @describe 实现了Subject接口 * @author zc * @version 1.0 2017-09-13 */ public class RealSubject implements Subject{ @Override public void request() { System.out.println("real subject execute request"); } }
3.编写Proxy类
package com.myimooc.myproxydemo.pattern; /** * @title 代理对象 * @describe 同样也实现了Subject接口 * @author zc * @version 1.0 2017-09-13 */ public class Proxy implements Subject{ // 需要引用目标对象 private RealSubject realSubject; // 强制必须传入目标对象 public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { // 在目标对象方法执行之前做一些额外的事情 System.out.println("before"); try{ // 代理对象不会做真实的业务逻辑,还是委托给真实的目标对象执行 realSubject.request(); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after"); } } }
4.编写Client类
package com.myimooc.myproxydemo.pattern; /** * @title 客户端 * @describe 测试代理模式 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { Subject subject = new Proxy(new RealSubject()); subject.request(); } }3-4 JDK代理
静态代理与动态代理
静态代理的缺点:每当需要代理的方法越多的时候,重复的逻辑就越多 动态代理的两类实现:基于接口代理与基于继承代理 两类实现的代表技术:JDK代理与Cglib代理
JDK实现要点
类:java.lang.reflect.Proxy 接口:InvocationHandler 只能基于接口进行动态代理
代码编写
1.编写JdkSubject类
package com.myimooc.myproxydemo.jdkimpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import com.myimooc.myproxydemo.pattern.RealSubject; /** * @title 动态代理类 * @describe 相当于AOP的aspect * @author zc * @version 1.0 2017-09-13 */ public class JdkSubject implements InvocationHandler{ // 同样需要引入目标对象 private RealSubject realSubject; public JdkSubject(RealSubject realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在目标对象方法执行之前做一些额外的事情 System.out.println("before"); Object result = null; try{ // 代理对象不会做真实的业务逻辑,还是委托给真实的目标对象执行 result = method.invoke(realSubject, args); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after"); } return result; } }
2.编写Client类
package com.myimooc.myproxydemo.jdkimpl; import java.lang.reflect.Proxy; import com.myimooc.myproxydemo.pattern.RealSubject; import com.myimooc.myproxydemo.pattern.Subject; /** * @title 动态代理类 * @describe JDK实现动态代理测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new JdkSubject(new RealSubject())); subject.request(); } }3-5 JDK解析
JDK代理源码解析
Proxy.newProxyInstance(首先,调用该方法) getProxyClass0、ProxyClassFactory、ProxyGenerator(然后,分别调用方法,生成字节码) newInstance(最后,利用反射根据字节码生成实例)3-6 Cglib代理
代码编写
1.编写DemoMethodInterceptor类
package com.myimooc.myproxydemo.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * @title 需要植入的代码类 * @describe 需要实现MethodInterceptorj接口 * @author zc * @version 1.0 2017-09-13 */ public class DemoMethodInterceptor implements MethodInterceptor{ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before in cglib"); Object result = null; try{ // 代理类调用父类的方法 proxy.invokeSuper(obj, args); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after in cglib"); } return result; } }
2.编写Client类
package com.myimooc.myproxydemo.cglib; import com.myimooc.myproxydemo.pattern.RealSubject; import com.myimooc.myproxydemo.pattern.Subject; import net.sf.cglib.proxy.Enhancer; /** * @title 动态代理类 * @describe Cglib实现动态代理测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { // 实例化Enhancer对象 Enhancer enhancer = new Enhancer(); // 设置需要代理的对象 enhancer.setSuperclass(RealSubject.class); // 设置需要植入的代码 enhancer.setCallback(new DemoMethodInterceptor()); // 生成代理类 Subject subject = (Subject)enhancer.create(); subject.request(); } }
JDK与Cglib代理对比
JDK只能针对有接口的类的接口方法进行动态代理 Cglib基于继承来实现代理,无法对static、final类进行代理 Cglib基于继承来实现代理,无法对private、static方法进行代理3-7 Spring选择
Spring创建代理bean时序图
SpringAOP对两种实现的选择
如果目标对象实现了接口,则默认采用JDK动态代理 如果目标对象没有实现接口,则采用Cglib进行动态代理 如果目标对象实现了接口,但设置强制cglib代理,则使用cglib代理 在SpringBoot中,通过@EnableAspectJAutoProxy(proxyTargetClass=true)设置3-8 链式调用
当多个AOP作用到同一个目标对象时,采用责任链模式
责任链模式类图
代码编写
1.编写Handler类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 抽象接口 * @author zc * @version 1.0 2017-09-13 */ public abstract class Handler { // 后继Handler,是否有类进行处理 private Handler sucessor; // 对外暴露 public void execute(){ handleProcess(); if(sucessor != null){ sucessor.execute(); } } // 由子类实现 protected abstract void handleProcess(); public Handler getSucessor() { return sucessor; } public void setSucessor(Handler sucessor) { this.sucessor = sucessor; } }
2.编写Client类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { static class HandlerA extends Handler{ @Override protected void handleProcess() { System.out.println("handle by a"); } } static class HandlerB extends Handler{ @Override protected void handleProcess() { System.out.println("handle by b"); } } static class HandlerC extends Handler{ @Override protected void handleProcess() { System.out.println("handle by c"); } } public static void main(String[] args) { HandlerA handlerA = new HandlerA(); HandlerB HandlerB = new HandlerB(); HandlerC HandlerC = new HandlerC(); // 设置链接关系 handlerA.setSucessor(HandlerB); HandlerB.setSucessor(HandlerC); handlerA.execute(); } }
3.编写Chain类
package com.myimooc.myproxydemo.chain; import java.util.List; /** * @title 责任链模式 * @describe 封装链式关系 * @author zc * @version 1.0 2017-09-13 */ public class Chain { private Listhandlers; private int index = 0; public Chain(List handlers){ this.handlers = handlers; } public void proceed(){ if(index >= handlers.size()){ return; } handlers.get(index++).execute(this); } }
4.编写ChainHandler类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 对Handler进行封装 * @author zc * @version 1.0 2017-09-13 */ public abstract class ChainHandler { public void execute(Chain chain){ handleProcess(); chain.proceed(); } // 由子类实现 protected abstract void handleProcess(); }
5.编写ChainClient类
package com.myimooc.myproxydemo.chain; import java.util.Arrays; import java.util.List; /** * @title 责任链模式 * @describe 有顺序的链式调用测试类 * @author zc * @version 1.0 2017-09-13 */ public class ChainClient { static class ChainHandlerA extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by a"); } } static class ChainHandlerB extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by b"); } } static class ChainHandlerC extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by c"); } } public static void main(String[] args) { // 声明链式调用顺序 List第四章:代码解读 4-1 本节内容handlers = Arrays.asList( new ChainHandlerA(), new ChainHandlerB(), new ChainHandlerC() ); Chain chain = new Chain(handlers); chain.proceed(); } }
上节回顾
静态代理与动态代理 JDK代理与Cglib代理区别及局限 代理模式与责任链模式
Spring AOP在开源项目里面的应用:三个例子
事务:@Transactional:Spring如何利用Transaction进行事务控制 安全:@PreAuthorize:Spring Security如何利用PreAuthorize进行安全控制 缓存:@Cacheable:Spring Cache如何利用Cacheable进行缓存控制
通过案例来讲解,源码可到我的github地址查看
第五章:实战案例 5-1 案例背景实战案例背景
商家产品管理系统 记录产品修改的操作记录 什么人在什么时间修改了哪些产品的哪些字段修改为什么值
实现思路
利用aspect去拦截增删改方法 利用反射获取对象的新旧值 利用@Around的advice去记录操作记录5-2 案例实现
创建名为mydatalog的maven项目pom如下
4.0.0 com.myimooc mydatalog 0.0.1-SNAPSHOT jar mydatalog http://maven.apache.org org.springframework.boot spring-boot-starter-parent 1.5.2.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java org.springframework.boot spring-boot-starter-test test commons-beanutils commons-beanutils com.alibaba fastjson 1.2.36
完成后的项目结构图如下
受篇幅限制,源码请到我的github地址查看
第六章:课程总结 6-1 课程总结要点清单
AOP的适用范围及优劣势 AOP的概念及Spring切面表达式 AOP的实现原理及运用
使用SpringAOP的注意事项
不宜把重要的业务逻辑放到AOP中处理 无法拦截static、final、private方法 无法拦截内部方法调用
课程小结
合理利用面向切面编程提高代码质量 掌握SpringAOP概念及实现原理 了解AOP的优缺点及SpringAOP的使用局限
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/70475.html
摘要:入门篇学习总结时间年月日星期三说明本文部分内容均来自慕课网。主要的功能是日志记录,性能统计,安全控制,事务处理,异常处理等等。 《Spring入门篇》学习总结 时间:2017年1月18日星期三说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:https://git...
摘要:时间年月日星期日说明本文部分内容均来自慕课网。慕课网教学示例源码个人学习源码第一章课程介绍课程介绍本课程紧接着小时学会课程,请先看入门课。异常返回通知在连接点抛出异常后执行。 时间:2017年3月19日星期日说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:https://github.com/zccodere/s...个人学习源码:htt...
摘要:事务管理学习总结时间年月日星期二说明本文部分内容均来自慕课网。一致性一致性指事务前后数据的完整性必须保持一致。声明式事务管理基于的方式很少使用需要为每个进行事务管理的类,配置一个进行增强。 《Spring事务管理》学习总结 时间:2017年2月7日星期二说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com/教学示例源码:https://github.com...
摘要:时间年月日星期六说明本文部分内容均来自慕课网。慕课网教学源码学习源码第一章课程介绍课程简介是啥读音是轻量级的依赖注入框架说明一个的框架需要有基础什么是剥离注入轻量级代码少易维护性能优异,跟比较。 时间:2017年10月14日星期六说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com 教学源码:https://github.com/zccodere/s......
时间:2017年08月16日星期三说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学源码:无学习源码:https://github.com/zccodere/s... 第一章:课程介绍 1-1 课程介绍 课程目录 1.ssh知识点回顾 2.搭建ssm开发环境 3.struts2整合spring 4.spring整合hibernate 5.案例:使用ssh框架开发...
阅读 2475·2021-11-16 11:45
阅读 2445·2021-10-11 10:59
阅读 2253·2021-10-08 10:05
阅读 3820·2021-09-23 11:30
阅读 2373·2021-09-07 09:58
阅读 795·2019-08-30 15:55
阅读 774·2019-08-30 15:53
阅读 1923·2019-08-29 17:00