一、初识1. 架构演变
- 单一应用架构 ORM 数据访问框架
- 将垂直应用架构拆分成几个不相关的应用程序,放置在不同的服务器中 MVC WEB框架
- 分布式服务架构提取核心业务作为独立服务 RPC 集群间通信协议
- 基于访问压力实时管理集群容量,增加流动计算架构的调度中心,提高利用率 SOA
- JSP+Servlet+JavaBean
- JSP: Java Server Page
- Servlet: Server Applet 服务器端的小应用程序
- servlet生命周期
- jsp作用域
- JavaBean: 有些对象包括setter getter 属性等
- MVC三层架构
- Controller 接受客户端请求,查询Model,接受Model数据
- Model 数据库中的表
- View 前段框架、html等,用于渲染controller的结果
- EJB 重量级框架,借口太多,依赖性太强,侵入性太强(写代码需要继承或者实现很多父亲的借口),使用麻烦
- SSH Structs1/Structs2+Hibernate+Spring
- Struts2框架安全存在隐患,Restful模式的流程度逐渐普及
- SpringMVC以Spring为扩展,完成了Structs的工作,并结合web容器推出了SpringBoots
- Ibatis等Hibernate有很多替代品, Mybatis, JPA等
- SSM Spring+SprinvMVC+Mybatis
- SpringBoot
大部分所需的功能都在Spring全家桶中
2. 核心解释- 开源框架
- 为了简化企业发展,使发展更加优雅
- IOC和AOP的容器框架
- IOC 控制反转
- AOP 面向切面编程
- 容器 包括和管理应用对象的生命周期,桶与水的关系,spring是桶,管理对象是水
- Spring通过DI通过DI、简化AOP和消除样板的开发
- 巨大的生态系统构建在Spring上,将Spring扩展到各个领域
- 低侵入设计
- 基于Spring的应用,write once,run anywhere
- IOC容器降低了业务对象替换的复杂性,提高了组件之间的莲藕
- AOP允许集中管理安全、事务、日志等一些通用任务,以便更好地重用
- Spring的ORM和DAO与第三光持久层框架集成良好,简化了数据库访问
- 不强制应用依赖于Spring
保存对象,每次使用都不需要重新创建,从保存的地方,即从自己创建到他人创建,IOC是一种设计理念,具体实现称为DI,IOC容器是放置对象的地方,IOC不是spring,只是实现,
- 谁控制谁:IOC容器控制对象
- 控制什么:所需的对象和依赖的对象 userservice需要依赖userdao
- 逆转是什么:IOC容器创建后依赖对象注入对象,从主动创建到被动接受
- 需要逆转的是什么:依赖的对象
- 莲藕IOC容器可以节藕
- 生态
构建Spring项目只需导入以下包即可commonsns-logging-1.2.jar 日志spring-beans-5.2.3.RELEASE.jar Beansspring-context-5.2.3.RELEASE.jar Contextspring-core-5.2.3.RELEASE.jar Corespring-expression-5.2.3.RELEASE.jar SpEL
2. 创建ioc.xmlioc.xml
<beans><bean id="person" class="com.xxx.Person"> <property name="id" value="1"></property> </bean></beans>
Main.java
ApplicationContext ctx = new ClassPathXmlApplicationContext("ioc.xml");ctx.getBean("person", Person.class);ctx.getBean("person");ctx.getBean(Person.class);
ioc.name一般用于xml,但也有value和index用于构造器赋值,但在工作中不常用,认为容易出现问题
- p命名空间
<beans xmlns:p="http://www.springframework.org/schema/p'><bean id="person" class="com.xxx.Person" p:id="1" p:name="zhangsan"> </bean></beans>
注意点
- Applicationcontext表示,IOC容器的入口必须创建此类对象
- 读取配置文件的实现有两个类别
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- 默认情况下,容器启动后创建了容器中的对象,只对单例模式有效
- 默认为单例
- 通过setter/getter实现对象赋值的创建
- FactoryBean 当继承FactoryBean创建的对象交给Spring进行管理时,无论是否为单例,都只会在使用时创建;一般不需要
- 当对象获取和容器关闭时,spring容器可以指定相应的方法,多例不会调用destroy-mothod (一般不需要)
<bean id="person" class=" com. mashibing. bean. Person" init-method-"init" destroy-mothod="destory"></bean>
- 通过实现BeanPostProcesor,指定的Bean可以在创建前后执行额外的操作
- 与导入自定义Bean对象相同,将导入Jar包所需的类别写入例子:
<bean id "dataSource" class=" com. alibaba. druid. pool. DruidDataSource"><property name="username" value="root"></property> <property name="password" value="root"></property> <property name="url" value="jdbc:mysql://localhost:3306/demo"></property> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property></bean>
这样,德鲁伊的数据源就交给了Spring容器控制
- 引入外部配置文件赋值dataSource
- 导入context命名空间
<context: property-placeholder location="classpath:db.properties"></context: property-placeholder>
- 修改Bean对象value
- 命名空间
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/></beans>
- 修改Bean对象的导入方法
<bean id "dataSource" class=" com. alibaba. druid. pool. DruidDataSource"><property name="username" value="${jdbc.username}"></property> <property name="password" value="${password}"></property> <property name="url" value="${url}"></property> <property name="driverClassName" value="${driver}"></property></bean>
- tips: 属性可以重名,如username,可以是计算机名称,添加前缀区分
autowire
Mode
Explanation
default/no
不注入
byName
按id组装 通过setter方法后面的单词决定,而不是参数列表
byType
根据属性类型在容器中查询相应的bean,在多个相同类型中会出现异常
constructor
按结构装配,若有多种类型相同,则按id装配
5. SpEL可在ioc.通过xmlbean属性的value#{}
一些算术操作或方法调用的方法,应用场景不多,可以知道
@Component: 可以在任何类别中添加组件@Controller: 放在控制层,接受用户请求@Service: 放在业务逻辑层@Repository: 放在数据访问层
在spring运行中,不区分这四个注释,只用于提高代码可读性tips: 偷懒,都用Componentt
2. 配置包扫描- 告诉spring应该从哪个包开始扫描注释,需要提前导入context命名空间
<context:component-scan base-package="com.mashibing"></context:component-scan>
- 使用注释时,需要根据类名首字母小写从容器中取出@Controller(value="xxx在IOC容器中指定bean的id
- 确定要扫描的包后,可以控制和排除更细粒度的PersonServicee
<context:exclude-filter type="assignable" expression="com.mashibing.service.PersonService"/>
排除注解@Controller<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
include-exclude几乎不使用
即context.getBean()自动注入可通过@Autowired直接完成,默认情况下按类型进行装配规则
- 如果只有一个,直接赋值
- 如果没有发现,抛出异常,即使有,但不在Spring容器中
- 如果找到多个,继续按照变量名作为id匹配,即在ioc.xml中添加了几个或被继承的类别,在容器中,必须注意变量名必须是多个类别中的一个,以及首字母小写
- 直接组装匹配,注意,此时可以在变量名上添加注释
@Qualifier()
手动指定放入容器后的id名 - 匹配不能抛出异常
此注释也可以放在方法上。此时,在创建对象时,该方法将自动调用,方法中的参数将自动组装 @Resource
具有与@Autowired
相同的功能
- @resource由jdk提供,可以在更多的框架中使用,@autowired由spring提供,只能在spring中使用
- @Resource按名称组装,@Autowired按类型组装
- @使用@Resouce时,Qualifier将不再生效
AOP Aspect Oriented Programming OOP面向切面编程 Object Oritented Programming 面向对象编程解释:在程序运行过程中,将代码动态切入指定方法的指定位置:在程序运行过程中,当动态获取方法名称、参数和结果等相关信息时,动态代理可以实现流行的编程方法,即在程序运行过程中将代码动态切入指定方法的指定位置。
1. JDK中的动态代理CalculatorProxy.java
/** * 帮助calculator生成代理对象的类别 */public class CalculatorProxy { /** * * 为输入的参数对象创建动态代理对象 * @param calculator 被代理对象 * @return */ public static Calculator getProxy(final Calculator calculator){ ///代理对象的类加载器 ClassLoader loader = calculator.getClass().getClassLoader(); ////代理对象的界面 Class<?>[] interfaces = calculator.getClass().getInterfaces(); //方法执行器,执行被代理对象的目标方法 InvocationHandler h = new InvocationHandler() { /** * 实施目标方法 * @param proxy 代理对象,使用jdk,任何时候都不要操作这个对象 * @param method 目前将执行的目标对象的方法 * @param args 该方法调用外界传入的参数值 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //使用反射执行目标方法,实施目标方法后的返回值/// System.out.println(“这是动态代理执行的方法”); Object result = null; try { System.out.println(method.getName()+开始执行方法,参数是:"+ Arrays.asList(args)); result = method.invoke(calculator, args); System.out.println(method.getName()完成+方法执行,结果是:"+ result); } catch (Exception e) { System.out.println(method.getName()+”方法异常:"+ e.getMessage()); } finally { System.out.println(method.getName()+”方法的执行已经结束..."); } ///返回结果 return result; } }; Object proxy = Proxy.newProxyInstance(loader, interfaces, h); return (Calculator) proxy; }}
按上面的代码,通过CalculatorProxy
获取的Calculator
对象,在执行任何方法时,都会输出相应的日志信息。注:Calculator
它是一个接口,在使用时应该引入特定的实现对象CalculatorProxy.getProxy(new MyCalculator());
PS: 这种动态代理的实现调用了jdk的基本实现,不能为没有任何接口的类别创建代理对象。
2. Spring中的动态代理- pom依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.3.RELEASE</version> </dependency>
- applicationContext.xml
<context:component-scan base-package="com.mashibing"></context:component-scan>
- 添加组件注释
- 在logutil添加@Component注释
- 给Mycalculator添加@Service注释
- 在程序中设置切面类, Logutil.在java中添加@aspect注释
- 什么时候在哪里执行设置切面类的方法, execution中的参数支持正则表达式和条件表达式
@Component@Aspectpublic class LogUtil { /* 什么时候设置以下方法? @Before:在目标方法之前运行:预通知 @After:操作后的目标方法:后置通知 @AfterReturning:正常返回目标方法后:返回通知 @AfterThrowing:在目标方法抛出异常后开始运行:异常通知 @Around:环绕:环绕通知 编写注释后需要设置哪些方法来执行?使用表达式 execution(访问修饰符 返回值类型 方法全称) */ @Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int))") public static void start(){// System.out.println(XXX方法开始执行,使用的参数为:"+ Arrays.asList(objects));// System.out.println(method.getName()+开始执行方法,参数是:"+ Arrays.asList(objects)); System.out.println(”方法开始执行,参数为:"); } @AfterReturning("execution( public int com.mashibing.inter.MyCalculator.*(int,int))") public static void stop(){// System.out.println(XXX方法执行结束,结果是:"+ Arrays.asList(objects));// System.out.println(method.getName()+执行方法结束,结果是:"+ Arrays.asList(objects)); System.out.println()完成方法执行,结果是:"); } @AfterThrowing("execution( public int com.mashibing.inter.MyCalculator.*(int,int))") public static void logException(){// System.out.println(method.getName()+”方法异常:"+ e.getMessage()); System.out.println(方法异常:"); } @After("execution( public int com.mashibing.inter.MyCalculator.*(int,int))") public static void end(){// System.out.println(method.getName()+”方法的执行已经结束..."); System.out.println(”方法的执行已经结束..."); }}
- 打开基于注释的AOP功能
<context:component-scan base-package="com.mashibing"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- PS: 在spring容器中,如果有接口,则使用jdk自带的动态代理,如果没有接口,则使用cglib动态代理。 早期cglib比jdk快,目前差不多
执行顺序2. 执行顺序
- 1、正常执行:
@Before
@After
@AfterReturning
- 2、异常执行:
@Before
@After
@AfterThrowing
- 获取方法名、参数和结果
- 方法名和参数
@Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")public static void start(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); String name = joinPoint.getSignature().getName(); System.out.println(name+该方法开始实施,参数是:"+ Arrays.asList(args));}
- 结果值
@AfterReturning(value = "execution( public int com.mashibing.inter.MyCalculator.*(int,int))",returning = "result")public static void stop(JoinPoint joinPoint,Object result){ String name = joinPoint.getSignature().getName(); System.out.println(name+“方法执行完成,结果是:”+result);}
- 异常
@AfterThrowing(value = "execution( public int com.mashibing.inter.MyCalculator.*(int,int))",throwing = "exception")public static void logException(JoinPoint joinPoint,Exception exception){ String name = joinPoint.getSignature().getName(); System.out.println(name+“方法异常:”+exception);}
- 使用Spring AOP时,可修改方法的修改符和返回值,不能修改参数类型
- AOP示例基于配置:aop.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="logUtil" class="com.mashibing.util.LogUtil"></bean> <bean id="securityUtil" class="com.mashibing.util.SecurityUtil"></bean> <bean id="myCalculator" class="com.mashibing.service.MyCalculator"></bean> <!--配置aop--> <aop:config> <aop:pointcut id="globalPoint" expression="execution( Integer com.mashibing.service.MyCalculator.*(..))"/> <!--声明切面--> <aop:aspect ref="logUtil"> <!--提取通用表达式--> <aop:pointcut id="myPoint" expression="execution( Integer com.mashibing.service.MyCalculator.*(..))"/> <!--使用哪些方法来定义通知?--> <aop:before method="start" pointcut-ref="myPoint"></aop:before> <aop:after method="logFinally" pointcut-ref="myPoint"></aop:after> <aop:after-returning method="stop" pointcut-ref="myPoint" returning="result"></aop:after-returning> <aop:after-throwing method="logException" pointcut-ref="myPoint" throwing="e"></aop:after-throwing> <aop:around method="around" pointcut-ref="myPoint"></aop:around> </aop:aspect> <aop:aspect ref="securityUtil"> <aop:before method="start" pointcut-ref="globalPoint"></aop:before> <aop:after method="logFinally" pointcut-ref="globalPoint"></aop:after> <aop:after-returning method="stop" pointcut-ref="globalPoint" returning="result"></aop:after-returning> <aop:after-throwing method="logException" pointcut-ref="globalPoint" throwing="e"></aop:after-throwing> <aop:around method="around" pointcut-ref="globalPoint"></aop:around> </aop:aspect> </aop:config></beans>
3.一些相关概念1. AOP的核心概念和术语名称
解释
Join连接点 point
在每种方法中可以填写额外代码的地方,在方法中可以嵌入的地方\*方法的数量
Pointcut切入点
该方法实际切入代码的地方是连接点的子集
切面Aspect
将要嵌入的代码包装成单独的类别,并添加@Aspect注释,称为切面
通知Advice
在截面上的某个特定点执行动作
2. 通知类型前置通知 Before Advice: 执行方法前执行后返回通知 After returning advice: 方法返回结果后执行 后置异常通知(程序终端将无法执行) After throwing advice: 方法抛出异常后,执行后置通知 After (finally) advice: 方法执行完成后执行 环绕通知(无论如何均匀执行) (Around Advice): 方法前后执行
3. AOP应用场景- 日志管理
- 权限认证
- 安全检查
- 事务控制
@Before -> @After -> @Afterreturning或@Before -> @After -> @AfterThrowing
若使用环绕通知@Around,则
- 正常结束:环绕前置通知: -> @Before -> 后置通知环绕 -> 环绕返回通知 -> @After -> @AfterReturning
- 异常结束:环绕前置通知: -> @Before -> 围绕异常通知 -> 环绕返回通知 -> @After -> @AfterReturning
- 如果要收到普通通知,需要异常抛出/br>执行顺序改为:环绕前置通知 -> @Before -> 围绕异常通知 -> 环绕返回通知 -> @After -> @Afterthrowin示例:
@Around("execution(public Integer com.mashibing.service.MyCalculator.*(Integer,Integer))") public Object around(ProceedingJoinPoint pjp) throws Throwable { Signature signature = pjp.getSignature(); Object[] args = pjp.getArgs(); Object result = null; try { System.out.println("log---通知start周围:"+signature.getName()+开始执行方法,参数为:"+Arrays.asList(args)); ///通过反射调用目标的方法相当于执行method.invoke(),可自行修改结果值 result = pjp.proceed(args); System.out.println("log---stop环绕通知”+signature.getName()+执行方法结束); } catch (Throwable throwable) { System.out.println("log---围绕异常通知:"+signature.getName()+“异常”); throw throwable; }finally { System.out.println("log---环绕返回通知:"+signature.getName()+方法返回结果为:"+result); } return result; }
5. 多切面执行顺序:根据首字母的排序,可以在切面类中添加注释@Order()注明指定顺序。理解
六、AOP声明事务1. 事务类型- 编程事务直接在代码中添加处理事务的逻辑,可能需要显式调用begintranscation()、commit()、rollback()等事务管理方法
- 在配置文件中添加注释或直接定义声明式事务,将事务管理代码与业务方法分离,并以声明的形式管理事务
- 将事务管理引入xml
- 添加数据源
<context:component-scan base-package="com.mashibing"></context:component-scan><context:property-placeholder location="classpath:db.properties"></context:property-placeholder><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClassName" value="${jdbc.driverName}"></property> <property name="url" value="${jdbc.url}"></property></bean>
- 将事务管理引入容器
<!--配置事务管理器的bean对象--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property></bean><!--打开基于注释的事务管理器的配置--><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在需要异常回滚的方法上添加注释
@Transactional
@Transactional(isolation = Isolation.SERIALIZABLE)public void buyBook() throws FileNotFoundException { map.put(1, 2); bookDao.getPrice(1); bookDao.updateBalance("zhangsan", 100); bookDao.updateStock(1); int i = 1/0; new FileInputStream("aaa.txt");}
3. @Transcational注释详细信息- 传播事务的特点 propagation表示不同事务之间的关系,即事务套事务
传播特性
描述
示例 M1 {M2();M3();}
REQUIRED
如果外层有正在运行的事务,内部事务将由外部事务管理。如果内部事务中有一个回滚,所有事务都将回滚;如果没有事务在运行,则启动新事务并在自己的事务中运行
情形1:
M2、M3: REQUIRED/br>M1被默认事务类型调用;M3出错时,M2也会回滚
情形2:
如果M1不是事务,那么M2、M3相互独立,如果M3出错回滚,M2不会回滚
REQUIRES_NEW
如果有事务在运行,挂起正在运行的事务,然后开始新的事务,在自己的事务中运行
情形1:
M1:事务 M2:REQUIRES_NEW M3:REQUIRED 如果M3出错,M2不会回滚
情形2:
M2:REQUIRED M3:REQUIRES_NEW 如果M3异常回滚,M2回滚
情形3:
M2:REQUIRES_NEW M3:REQUIRES_NEW 即使M3出错回滚,M2也不会回滚
SUPPORTS
若有事务运行,则在事务中运行,否则不能在事务中运行
情形1:
M1 非事务 M2:SUPPORTS 如果M2出错,M2不会回滚
情形2:
M1 事务 M2:SUPPORTS 如果M2出错,M2回滚
NOT_SUPPORTED
当前的方法不在事务中运行。如果有正在运行的事务,则将事务挂起
M1 事务 M2 NOT_SUPPORTED 如果M2出错,M2不会回滚
NEVER
当前的方法不能在事务中运行,如果有,则抛出异常
M1 事务 M2 NEVER 立即抛出异常
MANDATORY
必须在事务内运行,否则抛出异常
M1 非事务 M2 MANDATORY 异常
NESTED
如果有事务在运行,当前的方法应该在嵌套事务中运行,否则将启动新的事务
M1 {try{M2}; M3 }
情形1
M1 事务 M2 REQUIRED M3 事务, M2异常回滚,M3也回滚
情形2
M1 事务 M2 REQUIRES_NEW M3 事务, 即使M2异常回滚 M3也正常执行,不回滚
情形3
M1 事务 M2 NESTED M3 事务, 即使M2异常回顾,M3也正常执行不回滚
情形4
M1 事务 M2 REQUIRES_NEW M3 事务, M1异常(M3执行后), 则 M2不回滚 M3回滚
情形5
M1 事务 M2 NESTED M3 事务, M3实施后M1出现异常,M2回滚,M3回滚
- 事务隔离等级 isolation
DEFAULT
使用数据库中设置的隔离级别READ_COMMITTED
读已提交READ_UNCOMMITTED
读未提交REPEATABLE_READ
可重复读SERIALIZABLE
可序列化
- 事务的超时间 timeout默认单位秒,一旦方法执行超过此时间,抛出异常并触发事务的回滚
- 只读事务 在readonly事务执行过程中,其他事务(或自己)不得修改数据库中的数据,否则会抛出异常
- 设置什么异常不回滚数据? rollBackFor / rollbackForclasname示例: 如除0异常
rollBackFor = {ArithmeticException.class}
或rollBackForClassName = {"java.lang.ArithmeticException"}
- 设置哪些异常回滚 noRollBackFor / norollbackForclasname默认显式抛出的异常不会回滚
基于配置模式的声明事务
<!--事务的配置--> <!--声明一个事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.mashibing.service.*.*(..))"/> <!--事务建议--> <aop:advisor advice-ref="myAdvice" pointcut-ref="txPointcut"></aop:advisor> </aop:config> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!--配置事务的属性--> <tx:attributes> <!--什么方法可以添加事务?--> <tx:method name="*" propagation="REQUIRED" read-only="true" isolation="DEFAULT"/> <tx:method name="updatePrice" propagation="REQUIRED"></tx:method> <tx:method name="update*" propagation="REQUIRED"></tx:method> </tx:attributes> </tx:advice>