Spring框架自我总结
目录
正文
Spring表达式语言SpEL
SpEL:字面量:
(1)整数:<property name="count" value="#{5}"/>
(2)小数:<property name="frequency" value="#{89.7}"/>
(3)科学计数法:<property name="capacity" value="#{1e4}"/>
(4)String可以使用单引号或者双引号作为字符串的定界符号:<property name=“name” value="#{'Chuck'}"/> 或 <property name='name' value='#{"Chuck"}'/>
Boolean:<property name="enabled" value="#{false}"/>
引用 Bean、属性和方法
(1)
(2)
SpEL支持的运算符号
(1)
(2)
静态代理
静态代理角色分析
抽象角色:一般使用抽象类或接口来实现;
真实角色:被代理的角色;
代理角色:代理真实角色,代理真实角色后一般还会做一些附属操作;
客户:使用代理角色来进行一些操作;
代码分析
Rent.java—-抽象角色
package com.ren.staticProxy;
/*
* 抽象角色:真实角色和代理角色的公共方法;
*/
public interface Rent {
public void rent();
}
Host.java—-真实角色
package com.ren.staticProxy;
/*
* 只要写纯粹属于自己的业务就好了
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("出租房屋");
}
}
Proxy.java—-代理角色
package com.ren.staticProxy;
/*
* 代理:除了依赖真实角色外还有自己的业务逻辑
*/
public class Proxy implements Rent {
private Host host;
//通过setter方法注入来获取 host
public void setHost(Host host) {
this.host = host;
}
//带顾客看房
public void findHouse() {
System.out.println("代理带顾客找房");
}
//收中介费
public void fare() {
System.out.println("交钱");
}
@Override
public void rent() {
findHouse();
host.rent();//依赖雇主
fare();
}
}
Client.java—-客户
package com.ren.staticProxy;
/*
*客户是 实现,也可以通过配置文件实现
*/
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy();
proxy.setHost(host);
proxy.rent();
}
}
使用静态代理的优缺点
优点:
是真实角色的业务更加纯粹,不在去关注一些公共的事情;
公共的业务由代理来实现,实现了业务分工;
公共业务发生扩展时变得更加集中和方便;
缺点:
类多了,多了代理类,工作量变大了,开发效率降低了;
因为静态代理的缺点,所以引出了动态代理;
动态代理
1.动态代理和静态代理的角色相同;
2.动态代理的代理类是自动生成的;
3.动态代理分为两类:
(1)基于接口的动态代理:JDK动态代理;
(2)基于类的动态代理:cglib动态代理;
现在javasist生成动态代理;
4.jdk动态代理—Proxy和InvocationHandler接口:
Rent.java —-抽象角色
package com.ren.dynamicProxy;
/*
* 抽象角色:真实角色和代理角色的公共方法;
*/
public interface Rent {
public void rent();
}
Host.java —-真实角色
package com.ren.dynamicProxy;
/*
* 只要写纯粹属于自己的业务就好了
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("出租房屋");
}
}
ProxyInvocationHander.java —-生成代理类的:
可以代理所有接口;
package com.ren.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 1).InvocationHandler 是代理实例的调用处理程序 实现的接口。
* 2). ProxyInvocationHander这个类只能代理接口
* */
public class ProxyInvocationHander implements InvocationHandler{
/*
* 1.动态代理代理Rent接口
* private Rent rent;
* public void setRent(Rent rent) {
* this.rent = rent;
* }
*2.修改:是动态代理代理所有接口:如下:
*/
private Object target;//目标对象---真实对象(代理类所代理的对象)
public void setTarget(Object target) {
this.target = target;
}
/**
* 生成代理类
* 参数三:InvocationHandler
* */
public Object getProxy()
{
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/*
* (non-Javadoc)
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
* 1)invoke方法:在代理实例上处理方法调用并返回结果。
* 2)参数:proxy - 代理类(在其上调用方法的代理实例)
* method - 代理类的调用处理程序的方法对象(对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。)
* args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//findHouse();
//result相当于调用代理类和真实角色公共的方法的返回值;
Object result = method.invoke(target, args);//第一个参数是真实对象,即该方法所在的对象;
//fare();
return result;
}
public void log(String methodName)
{
System.out.println("调用"+methodName+"方法");
}
// //带顾客看房
// public void findHouse() {
// System.out.println("代理带顾客找房");
// }
// //收中介费
// public void fare() {
// System.out.println("交钱");
// }
//
}
Client.java—-客户
package com.ren.dynamicProxy;
import com.ren.dao.service.UserService;
import com.ren.dao.service.UserServiceImpl;
/*
*客户是 实现,也可以通过配置文件实现
*/
public class Client {
public static void main(String[] args) {
// Host host = new Host();
// ProxyInvocationHander pih = new ProxyInvocationHander();
// pih.setTarget(host);
// Rent proxy = (Rent) pih.getProxy();
// proxy.rent();
UserService userService = new UserServiceImpl();
ProxyInvocationHander pih = new ProxyInvocationHander();
pih.setTarget(userService);
UserService proxy = (UserService) pih.getProxy();
proxy.getUser();
}
}
总结:动态代理一般代理某一类业务;一个动态代理可以代理多个类;
AOP
AOP简介
- AOP:Aspect-Oriented Programming,:面向切面编程, 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充。
- AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点。
- AOP在Spring中的作用:
(1)提供声明式服务(声明式事务);
(2)允许用户使用自定义切面; 传统编程模式:
AOP编程模式:在不改变原有代码的基础上增加功能。
- AOP 的好处:
(1)每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
(2)业务模块更简洁, 只包含核心业务代码. - AOP实际上是为了解决真实角色中的代码重复问题,即方法共有的业务,比如:日志、安全、异常、缓存、权限、事务等等;这些业务又叫领域业务—–领域模型;
- 领域模型比如:
支付业务—接入第三方接口—安全检查—身份验证—资金验证—支付; - Spring 的AOP可以看成是动态代理的框架;
AOP 术语
- 关注点:日志、事务、异常、安全、缓存等;
- 切面(Aspect):关注点的模块化;
- 连接点(Joinpoint):程序执行的某个特定位置;
- 通知(Advice):在切面的某个连接点上执行的动作叫通知;
- 切点(pointcut):AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点。
- 织入(Weaving):把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象;
- 目标(Target): 被通知的对象;
- 代理(Proxy): 向目标对象应用通知之后创建的对象
Spring AOP
AspectJ:Java 社区里最完整最流行的 AOP 框架;
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP;
Spring 中使用AOP需要导包:aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
基于 AspectJ 注解配置AOP
切面:是一个带有 @Aspect 注解的 Java 类;
通知:是标注有某种注解的简单的 Java 方法;
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行;
@After: 后置通知, 在方法执行之后执行(不管是否抛出异常);
@AfterRunning: 返回通知, 在方法返回结果之后执行;
@AfterThrowing: 异常通知, 在方法抛出异常之后;
@Around: 环绕通知, 围绕着方法执行;指定切面的优先级:@Order(序号越小越高);(或通过实现 Ordered 接口, getOrder() 方法的返回值越小越高)
切入点表达式:
(1)eg:execution(* com.ren.dao.service.impl..(..)):第一个 * 代表任意修饰符及任意返回值.第二个 * 代表任意类. 第三个 * 代表任意方法. .. 匹配任意数量的参数.
(2)在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来.例子:
自定义切面:MyLog.java:
package com.ren.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
/*
* 通过注解实现AOP
*/
@Aspect//标志是一个切面
@Order(0)//指定优先级
public class MyLog {
//前置通知
@Before("execution(* com.ren.dao.service.impl.*.*(..))")
public void before()
{
System.out.println("方法执行前。。");
}
//后置通知
@After("execution(* com.ren.dao.service.impl.*.*(..))")
public void after()
{
System.out.println("方法执行后。。");
}
//环绕通知
@Around("execution(* com.ren.dao.service.impl.*.*(..))")
public Object aroud(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());//void com.ren.dao.service.UserService.delete(int)
//执行目标方法
Object result = jp.proceed();
System.out.println("环绕后");
return result;
}
}
Spring 配置文件:
<?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">
<!--在 IOC 容器中将切面声明为 Bean 实例 -->
<bean id="log1" class="com.ren.log.MyLog"></bean>
<!-- 在 Spring IOC 容器中启用 AspectJ 注解支持 -->
<aop:aspectj-autoproxy />
</beans>
基于XML配置AOP
- 当使用 XML 声明切面时, 需要在 根元素中导入 aop Schema。
- 在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在
<aop:config>
元素内部; - 声明切面:用
<aop:aspect>
元素; - 声明切入点:用
<aop:pointcut>
元素:
(1)定义在 元素下: 只对当前切面有效;
(2)定义在 元素下: 对所有切面都有效;
(3)基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点; - 例子:
Log.java—切面
package com.ren.log;
public class Log {
public void before()
{
System.out.println("...方法执行前...");
}
public void after()
{
System.out.println("...方法执行后...");
}
}
Spring配置文件:beans.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-4.1.xsd">
<!--配置连接点所在程序 -->
<bean id="userServiceImpl" class="com.ren.dao.service.impl.UserServiceImpl"></bean>
<!--配置日志切面 -->
<bean id="log" class="com.ren.log.Log"></bean>
<aop:config>
<!-- 声明切面 -->
<aop:aspect ref="log">
<!-- 声明切入点 -->
<aop:pointcut expression="execution(* com.ren.dao.service.impl.*.*(..))" id="pointcut"/>
<!--声明通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:before method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
如果有多种切面时也可以如下配置:
<?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="log" class="com.ren.log.Log1"></bean>
<bean id="userService" class="com.ren.dao.service.impl.UserServiceImpl"></bean>
<bean id="afterLog" class="com.ren.log.AfterLog"></bean>
<!--
1).pointcut:是配置切面位置;
2).advisor:是配置切面的关注点,即是切入什么业务呢;
3).方式一实现:
-->
<aop:config>
<aop:pointcut expression="execution(* com.ren.dao.service.impl.UserServiceImpl.*(..))" id="poincut"/>
<aop:pointcut expression="execution(* com.ren.dao.service.impl.*.*(..))"
id="poincut"/>
<aop:advisor advice-ref="log" pointcut-ref="poincut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="poincut"/>
</aop:config>
</beans>
基于XML和 注解配置比较
正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会.
Spring中的事务管理
事务简介
1. 事务管理定义:
是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
2.事务定义:
事务是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
3事务的四个关键属性(ACID):
(1)原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
(2)一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
(3)隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
(4)持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
Spring 中的事务管理器
- Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
- Spring 的核心事务管理抽象是TransActionManager它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.
- Spring 中的事务管理器的不同实现:(以普通的 Bean 形式声明在 Spring IOC 容器)
(1)class DataSource TransactionManager:在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取;
(2)class jta TransactionManager:在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理;
(3)class Hibernate TransactionManager:用 Hibernate 框架存取数据库; - 事务管理是一种横切关注点。
- 由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理。
用事务通知声明式地管理事务
先将tx Schema 定义添加到 根元素中去;
<tx:advice>
元素声明事务通知;声明了事务通知后, 需要将它与切入点关联起来:在
<aop:config>
元素中声明一个增强器通知与切入点关联起来.设定传播事务属性:
<tx:method name="方法名" propagation="传播属性"> 元素
设置;设置隔离事务属性:
<tx:method name="方法名" propagation="传播属性" isolation="事务的隔离级别"> 元素
设置;. 设置回滚事务属性:
设置超时和只读事务属性:
- 示例代码:Spring配置:
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<!-- 自动扫描包 (实现注解配置)-->
<context:component-scan base-package="com.ren.spring.hibernate"></context:component-scan>
<!--1.配置数据源 -->
<!-- 导入资源文件:先加入context命名空间 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!--2.配置Hibernate的SessionFactory实例 :通过Spring提供的:LocalSessionFactoryBean-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- 配置数据源属性 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 配置Hibernate配置文件的位置及名称 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<!-- 配置Hibernate映射文件的位置及名称,可以使用通配符 -->
<property name="mappingLocations" value="classpath:com/ren/spring/hibernate/entities/*.hbm.xml"></property>
</bean>
<!--3.配置Spring的声明式事务 -->
<!--配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!--配置事务属性,需要事务管理器-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切点,并把切点和事务属性关联起来-->
<aop:config >
<aop:pointcut expression="execution(* com.ren.spring.hibernate.service.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
用 @Transactional 注解声明式地管理事务
- @Transactional 添加到公有方法上:该方法支持事务处理;
- @Transactional 添加到类上:这个类中的所有公共方法都会被定义成支持事务处理的;
- Spring配置文件中:启用
<tx:annotation-driven>
元素, 并为之用transaction-manager指定事务管理器就可以了. - 事务传播属性:在 @Transactional 注解的 propagation 属性中定义;
设置隔离事务属性:
设置回滚事务属性:
设置超时和只读事务属性:
- 示例代码:
事务传播属性
- 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
- 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为:
并发事务所导致的问题
- 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题
- 并发事务所导致的问题可以分为下面三种类型:
(1)脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
(2)不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
(3)幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
事务的隔离级别
- 从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行.
- 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
事务的隔离级别可以通过隔离事务属性指定; - Spring 支持的事务隔离级别:
- 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
(1)Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
(2)Mysql 支持 4 中事务隔离级别.
超时和只读属性
- 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.
- 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
- 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
- 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
每天积累一点点,每天进步一点点!