代理模式:给一个对象提供一个代理,并由代理对象来控制真实对象的访问(调用者并不知道真实对象是什么)。
代理模式分静态代理和动态代理。这里只讨论动态代理,通俗的讲,动态代理就是在不修改代码的基础对被代理对象进行方法的增强。
JDK自带的动态代理就是基于接口的动态代理,被代理对象至少要实现一个接口,否则就无法使用代理。底层还是基于Java的反射来创建代理对象的。
JDK动态代理主要涉及两个类,java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
。下面来看个例子。
被代理类的接口IProduct
public interface IProduct {
void saleProduct(Float money);
}
被代理类Product实现上面这个接口
public class Product implements IProduct{
@Override
public void saleProduct(Float money) {
System.out.println("卖出一个产品,收到金额:" + money);
}
}
编写一个客户端类获取一个Product的动态代理的对象,来控制被代理对象的访问。 InvocationHandler的实现主要通过内部类实现(可以直接使用lambda表达式更简洁)。
public class Client {
public static void main(String[] args){
Product product = new Product();
// 基于接口的动态代理(jdk自带的)
// proxyProduct就是Proxy.newProxyInstance生成的代理对象,需要强转为被代理对象的接口类。
// 参数:
// ClassLoader:代理对象的类加载器。使用和被代理对象相同的类加载器,直接调用getClass().getClassLoader()获取。
// Class>[]:被代理对象的接口的字节码。直接调用getClass().getInterfaces()获取就行
// InvocationHandler:进行代码的增强。直接使用内部类或者new一个InvocationHandler的实现类(在实现类中进行代码增强)
IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader()
, product.getClass().getInterfaces(), new InvocationHandler() {
/**
* 进行代理增强的方法
* 参数:
* proxy:当前的代理对象
* method:被代理对象当前执行的方法
* args:被代理对象当前执行的方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// 获取方法执行的参数
Float money = (Float)args[0];
// 判断当前方法是否是销售方法
if ("saleProduct".equals(method.getName())){
// 返回值与被代理对象方法的相同
res = method.invoke(product, money * 0.8f);
}
return res;
}
});
// 调用这个代理对象,执行被代理对象的方法。
proxyProduct.saleProduct(1000f);
}
}
CGLIB动态代理是一个基于ASM字节码操作框架的代码生成库,它被广泛用于基于代理的AOP框架提供方法拦截。
本质上说,对于需要被代理的类,它只是动态的生成一个子类以覆盖非final的方法(所以它不能代理final类或final方法),同时绑定钩子回调自定义的拦截器。它比使用Java反射的JDK动态代理方法更快。
CGLIB动态代理的核心是net.sf.cglib.proxy.Enhancer类(用于创建被代理对象子类),net.sf.cglib.proxy.MethodInterceptor是最常用的回调类型(它是net.sf.cglib.proxy.Callback的一个子接口)。
使用前需要引入CGLIB的jar依赖。
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.2.12version>
dependency>
下面来看一个示例(被代理对象还是和上面的相同,这里实现一个客户端类)。
public class Client {
public static void main(String[] args){
Product product = new Product();
/**
* 基于子类的动态代理(cglib)
* Enhancer的create方法,通过Enhancer来创建代理对象,MethodInterceptor来增强方法
* 参数:
* Class:被代理对象的字节码
* Callback:用于增强方法。一般使用这个接口的子接口MethodInterceptor。
*/
Product proxyProduct = (Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过此方法
* @param proxy 当前代理对象
* @param method 被代理对象要执行的方法
* @param args1 要执行的方法的参数
* 以上三个参数与基于接口的动态代理(jdk)的参数一致
* @param methodProxy 当前执行方法的代理对象 一般使用这个对象来调用原始对象的方法,因为它性能更高
* @return 与被代理对象要执行的方法的返回值一致
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args1, MethodProxy methodProxy) throws Throwable {
Object res = null;
// 获取方法执行的参数
Float money = (Float)args1[0];
// 判断当前方法是否是销售方法
if ("saleProduct".equals(method.getName())){
// 修改原始方法的参数
args1[0] = money * 0.8f;
// 与JDK动态代理的一个区别,使用MethodProxy对象来调用原始对象的方法
res = methodProxy.invoke(product, args1);
}
return res;
}
});
// 通过代理对象执行被代理对象的方法
proxyProduct.saleProduct(1000f);
}
}
spring AOP是基于动态代理实现的,spring默认使用JDK动态代理实现AOP,也可以强制使用CGLIB。AOP是IoC的一种补充,IoC不依赖与AOP,但是AOP依赖与IoC。
Spring 使用 AspectJ 提供的 library 解释与 AspectJ 5 相同的注释,用于切入点解析和匹配。但是,AOP 运行时仍然是纯粹的 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。(也就是spring只是利用Aspectj来对切入点进行解析和匹配,真正的AOP的执行还是使用Spring AOP)。
在springboot中使用spring AOP非常的方便,只需引入一个spring支持AspectJ的starter依赖即可。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
在spring中也很简单,在引用spring AOP的基础上再引入一个aspectjweaver依赖就行。
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.2version>
dependency>
在spring中,要使用 Java @Configuration启用 @AspectJ 支持,还需添加@EnableAspectJAutoProxy注解(在springboot中不用)。
下面来看一个简单的示例(基于springboot)。
先写个Service类(包括接口和实现类)。
public interface AccountService {
Account findById(Integer aid);
}
@Service
public class AccountServiceImpl implements AccountService {
private static final Logger log = LoggerFactory.getLogger(AccountServiceImpl.class);
@Override
public Account findById(Integer aid) {
log.info("正在执行方法");
return accountMapper.findById(aid);
}
}
一个配置类,对service中的方法进行切面。
@Aspect // 声明切面, 这个bean将被spring自动检测,并用于配置Spring AOP
@Component // 将这个类注册为一个bean,交给spring来管理(所以spring AOP依赖于IoC)
public class Logger {
private static org.slf4j.Logger log = LoggerFactory.getLogger(Logger.class);
/**
* 通用化切入点表达式,其他切入点可以直接引用这个
*/
@Pointcut("execution(* tkmybatis.service.impl.*.*(..))")
public void pointCut(){
}
/**
* 前置通知,在切入点方法执行之前执行
*/
@Before("pointCut()")
public void printLogBefore(){
log.info("我在方法执行前");
}
/**
* 后置通知,方法正常返回后执行
*/
@AfterReturning("pointCut()")
public void printLogAfterReturning(){
log.info("我在方法执行正常返回后");
}
/**
* 异常通知,方法发生异常时执行
*/
@AfterThrowing("pointCut()")
public void printLogAfterThrow(){
log.info("我在方法执行抛出异常后");
}
/**
* 最终通知,无论方法是否执行正常,它都会在最后执行
*/
@After("pointCut()")
public void printLogAfter(){
log.info("我在方法执行最后");
}
@Around("pointCut()")
public Object printAround(ProceedingJoinPoint pjp){
Object res;
try{
Object[] args = pjp.getArgs();
log.info("我在方法执行前");
res = pjp.proceed(args);
log.info("我在方法执行正常返回后2");
return res;
}catch (Throwable t){
log.info("我在方法执行抛出异常后2");
throw new RuntimeException(t);
}finally {
log.info("我在方法执行最后");
}
}
}
对于切入点表达式execution(* tkmybatis.service.impl.*.*(..))
,这里做一下解释。
execution表达式本来有三个部分(可见类型 返回值 连接点的全限定名),这里的可见类型可以省略。
更多高级用法详见Spring官方文档 Spring AOP
Spring支持声明式的事务管理和编程式的事务管理。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。Spring推荐使用声明式的事务管理。
而声明式的事务管理就是基于Spring AOP的,其本质就是对要执行的方法进行拦截,在方法执行前加入一个事务,方法执行完成后根据情况判断是提交事务还是回滚事务(抛出了异常)。
通过org.springframework.transaction.annotation.Isolation 这个枚举类来指定。
public enum Isolation {
// 这是默认值,表示使用底层数据库的默认隔离级别。对于MySQL的InnoDB存储引擎,它是REPEATABLE_READ,其它一般的都是READ_COMMITTED
DEFAULT(-1),
// 跟没有一样,几乎不使用。
READ_UNCOMMITTED(1),
// 只能读取另一个事务已提交的事务,能防止脏读。也是一般数据库的默认隔离级别。
READ_COMMITTED(2),
// 可重复读(在一个事务内多次查询的结果相同,其它事务不可修改该查询条件范围内的数据,相当于加了个读锁)
REPEATABLE_READ(4),
// 所有的事务依次逐个执行,相当于串行化了,效率太低,一般也不使用。
SERIALIZABLE(8);
}
如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。org.springframework.transaction.annotation.Propagation枚举类中定义了7表示传播行为的枚举值。
public enum Propagation {
// 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
REQUIRED(0),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
SUPPORTS(1),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
MANDATORY(2),
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
REQUIRES_NEW(3),
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED(4),
// 以非事务方式运行,如果当前存在事务,则抛出异常。
NEVER(5),
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
NESTED(6);
}
声明式事务可以直接使用@Transactional注解(这里只讨论这个)来配置,当然也可以使用XML配置文件。
先来看一下@Transactional注解都有啥
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 别名为transactionManager,其实这两是同一个。就是事务的名字。
@AliasFor("transactionManager")
String value() default "";
// 事务的传播行为,默认值为REQUIRED
Propagation propagation() default Propagation.REQUIRED;
// 事务的隔离级别,默认为默认值(也就是使用底层数据库的隔离级别)
Isolation isolation() default Isolation.DEFAULT;
// 超时时间,默认为 -1,也就是没有超时时间。
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只读,默认为false。
boolean readOnly() default false;
// 会触发事务回滚的异常的字节码数组
Class<? extends Throwable>[] rollbackFor() default {};
// 不会触发事务回滚的异常的字节码数组
Class<? extends Throwable>[] noRollbackFor() default {};
}
这里直接在实现类上使用,这个注解还可以在接口(一般不在接口上使用)和方法上使用(可见性必须为public,否则会被忽略)。
如果是在spring中使用,需要在配置类中加上@EnableTransactionManagement注解(springboot则不需要)。proxy-target-class属性可以控制动态代理的类型,如果值为false或忽略此属性,则使用JDK的动态代理,可以设置为true以强制使用CGLIB来进行动态代理(如果被代理的类没有实现接口,将会自动使用CGLIB进行动态代理)。
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) // 这里触发回滚的异常设置为Exception
public class AccountServiceImpl implements AccountService {
}
参考:
CGLIB动态代理介绍
Spring AOP
Spring AOP的使用
Spring 事务管理
Spring 事务