代理模式
什么是代理模式:
一种设计模式,提供间接对目标对象进行访问的方式,通过代理对象访问目标对象,从而在在目标对象现有的功能能上,增加额外的功能补充,实现扩展目标对象的功能。
代理模式主要包含三个角色:主题角色,目标类角色,代理类角色
- 主题类角色:可以是接口,也可以是抽象类
- 目标类角色:也叫委托类,真实主题角色,业务逻辑的具体执行者
- 代理类角色:内部含有对真实目标对象的引用,负责对目标对象方法的调用,并且在调用前后进行预处理
代理模式的分类
- 静态代理
静态代理是代理模式的实现方式之一,在程序运行前,手动创建代理类,从而实现对目标类中的方法进行增强。 - 动态代理
静态代理的例子中,目标类与代理类是一一对应的,如果多个目标类需要被代理,那么就需要在程序运行之前建立多个代理类为之代理。动态代理则可以简单地为各个目标类分别生成代理类,从而实现功能的增强,这样可以大大减小程序规模。在动态代理中,代理类是在运行时期生成的。因此,相比静态代理,动态代理可以很方便地对委托类的相关方法进行统一增强处理。
JDK动态代理
JDK动态代理主要是借助java.lang.reflect.Proxy
生成代理类
- java.lang.reflect.Proxy:
只需要传入目标类的类加载器,目标类的接口,以及InvocationHandler即可为目标类生成代理类 - java.lang.reflect.InvocationHandler
该接口只有一个invoke方法,通过该方法实现对委托类的代理的访问,是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑
public class User {
public static void main(String[] args) {
Singer target = new Target();
Singer proxy =
(Singer)Proxy.newProxyInstance(
Target.class.getClassLoader(),
Target.class.getInterfaces(),
new InvocationHandler() {
/**
* @param proxy 代理类实例
* @param method 目标类中的方法对象
* @param args 调用参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理增强");
Object result = method.invoke(target, args);
return result;
}
});
proxy.sing();
}
}
cglib代理
在jdk动态代理中,目标类必须实现接口,才能够被代理,cglib代理可以代理没有实现接口的目标类。
CGLib通过ASM动态操作指令生成了被代理类的子类,如果目标类是final修饰的,则不能够被代理重写了目标类中所有的非private、非final的方法,每一个重写的方法都有相应的代理方法。
public class StudentService {
public void saveOrUpdate() {
System.out.println("操作成功");
}
}
public class StudentTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 设置代理类的父类对象
enhancer.setSuperclass(StudentService.class);
// 设置代理的逻辑对象
enhancer.setCallback(new MethodInterceptor() {
/**
* @param proxy 代理对象实例
* @param method 目标类中的方法对象
* @param args 方法参数
* @param methodProxy 方法代理对象
*/
@Override
public Object intercept(Object proxy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("提交事务");
return result;
}
});
// 产生代理对象
StudentService studentService = (StudentService) enhancer.create();
studentService.saveOrUpdate();
}
}
Spring AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。资料来源于百度百科
即通过动态代理对方法进行增强,而不影响原本的业务逻辑代码。
实现方式:
JDK动态代理 + Cglib代理
术语:
- Aspect(切面): 包括切入点和通知
- Joint point(连接点):目标对象中所有可以增强的方法
- Pointcut(切点):增强的代码
- Advice(增强):增强代码
- Target(目标对象):目标对象.。
- Weaving(织入):将通知应用到切入点
案例:
配置文件:
<!-- 配置aop -->
<aop:config>
<!--
配置切入点
expression:切入点表达式
id 切入点的唯一标识
-->
<aop:pointcut
expression="execution(* com.aop.xml..*.*(..))"
id="pt1" />
<!--
配置切面
-->
<aop:aspect id="aspect" ref="teacherAspect">
<!-- 配置前置通知 -->
<aop:before method="beforAdvice" pointcut-ref="pt1"></aop:before>
<aop:after method="after" pointcut-ref="pt1"/>
<aop:after-returning method="afterReturn" pointcut-ref="pt1"/>
<aop:around method="arround" pointcut-ref="pt1"/>
<aop:after-throwing method="throwable" pointcut-ref="pt2"/>
</aop:aspect>
</aop:config>
表达式语法:
关于表达式在这里进行详细说明:
语法:execution([修饰符]返回值类型包名.类名.方法名(参数))
例如:public com.aop.xml.impl.TeacherServiceImpl.saveOrUpdate()
返回值可以使用号,表示任意返回值
例如:== com.aop.xml.impl.TeacherServiceImpl.saveOrUpdate()==
包名可以使用*
号,表示任意包,但是有几级包,需要写几个*
使用…来表示当前包,及其子包
类名可以使用*
号,表示任意类
方法名可以使用*
号,表示任意方法
参数列表可以使用…表示有无参数均可,有参数可以是任意类型
注解驱动:
- 注解解释:
@Aspect
声明该类是一个切面类
@Before
声明方法为前置通知
@AfterReturning
声明方法为后置通知
@AfterThrowing
声明方法为异常通知
@After
声明方法为最终通知
@Around
声明方法为环绕通知
所有通知注解拥有着一个共同的属性value
,且值为切入点表达式,或者切入点的引用
@Pointcut
声明执行切入点
事务管理
Spring中事务管理器主要是PlatformTransactionManager接口去管理,其主要方法为:
- getTransaction 获取事务状态
- commit 提交事务
- rollback 回滚事务
这是个接口,一般都用他的实现类进行事务管理
一般使用DataSourceTransactionManager
去管理JDBC或者Mybatis的事务。如果是Hibernate,那么就需要采用HibernateTransactionManager
去管理事务。
事务隔离级别
public interface TransactionDefinition {
/**
* 获取事务传播行为
*/
int getPropagationBehavior();
/**
* 获取事务隔离级别
*/
int getIsolationLevel();
/**
* 获取事务超时事件
*/
int getTimeout();
/**
* 获取事务是否是只读
*/
boolean isReadOnly();
/**
* 获取事务对象名称
*/
String getName();
}
具体的事务隔离级别:
- ISOLATION_DEFAULT 默认级别,归属下面级别中的某一种
- ISOLATION_READ_UNCOMMITTED 读未提交
- ISOLATION_READ_COMMITTED 读已提交 (oralce数据库默认级别)
- ISOLATION_REPEATABLE_READ 可重复读
- ISOLATION_SERIALIZABLE 串行化
事务传播行为:
-
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。(默认值)
-
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
-
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
-
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起
-
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
-
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
-
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
配置事务:
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务属性 -->
<tx:attributes>
<!--
name : 指定方法名称 *代表任意赐福
read-only 是否是只读事务
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚
-->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
注解方式配置
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
开启事务支持
@EnableTransactionManagement
整合mybatis
- 导入jar包
- 新建数据源文件datasource.properties
oracle.driver=oracle.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:xe
oracle.username=spring
oracle.password=spring
oracle.init=5
oracle.max=10
- 新建3个配置文件
- applicationContext-dao.xml
<!--1.引入属性文件,在配置中占位使用 -->
<context:property-placeholder location="classpath:datasource.properties" />
<!--
2.配置数据库连接池
-->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driver" value="${oracle.driver}"></property>
<property name="url" value="${oracle.url}"></property>
<property name="username" value="${oracle.username}"></property>
<property name="password" value="${oracle.password}"></property>
</bean>
<!--3 会话工厂bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源 -->
<property name="dataSource" ref="datasource"></property>
<!--
别名
<property name="typeAliasesPackage" value="com.spring.bean"></property>
-->
<!-- sql映射文件路径 -->
<property name="mapperLocations" value="classpath:com/spring/mapper/*Mapper.xml"></property>
</bean>
<!--4 自动扫描对象关系映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<!-- 指定要自动扫描接口的基础包,实现接口 -->
<property name="basePackage" value="com.spring.mapper"></property>
</bean>
- applicationContext-service.xml
<!-- 扫描service层注解 -->
<context:component-scan base-package="com.spring.service"></context:component-scan>
- applicationContext-trans.xml
<!--定义事物管理器,由spring管理事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务属性 -->
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 配置 aop -->
<aop:config>
<aop:pointcut expression="execution(* com.spring.service..*.*(..))" id="pt1"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
完成上述步骤之后spring就会在容器启动阶段为我们创建对应的mapper,提供事务支持。
Spring5新特性
- 需要JDK版本8以上
- 核心容器更新
- JetBrains Kotlin语言支持
- 响应式编程风格
- JUnit5支持