文章目录
一、代理模式
1.理解代理模式
你刚到北京,要租房子,可以自己找,也可以找链家帮你找。其中链家是代理类,你是目标类。你们两个都有共同的行为:找房子。不过链家除了满足你找房子,另外会收取一些费用的。
2.静态代理
现在有个订单接口和实现类
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 修改订单
*/
void modify();
}
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void modify() {
try {
Thread.sleep(1010);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
}
现在有个需求是,统计每个方法的执行时间,同时要遵循OCP开闭原则,这里我们就可以设计一个代理类,用来执行统计时间的任务
public class OrderServiceProxy implements OrderService{ // 代理对象
// 目标对象
private OrderService orderService;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.generate();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void modify() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.modify();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}
如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸,而动态代理可以解决这个问题,因为在动态代理中可以在内存中动态的为我们生成代理类的字节码代码只需要写一次,代码也会得到复用。
3.动态代理
public class TimerInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
// 通过构造方法来传目标对象
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 目标执行之前增强。
long begin = System.currentTimeMillis();
// 调用目标对象的目标方法
Object retValue = method.invoke(target, args);
// 目标执行之后增强。
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
// 一定要记得返回哦。
return retValue;
}
}
测试类:
public class Test {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
// 调用代理对象的代理方法
orderServiceProxy.detail();
orderServiceProxy.generate();
}
}
优点:我们只需要提供一个代理类,无论目标类是哪一个,都是可以执行的。
二、Spring AOP
1. 理解Spring AOP
Aspect Oriented Programming 面向切面编程,是一种利用“横切”的技术(底层实现就是动态代理),对原有的业务逻辑进行拦截,并且可以在这个拦截的横切面上添加特定的业务逻辑,对原有的业务进行增强。
基于动态代理实现在不改变原有业务的情况下对业务逻辑进行增强
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务。这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
- 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
- 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
使用AOP可以很轻松的解决以上问题。
2.AOP专业术语
切点表达式:execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
- 可选项。
- 没写,就是4个权限都包括。
- 写public就表示只包括公开的方法。
返回值类型:
- 必填项。
- * 表示返回值类型任意。
全限定类名:
- 可选项。
- 两个点“…”代表当前包以及子包下的所有类。
- 省略时表示所有的类。
方法名:
- 必填项。
- *表示所有方法。
- set*表示所有的set方法。
形式参数列表:
-
必填项
-
() 表示没有参数的方法
-
(…) 参数类型和个数随意的方法
-
(*) 只有一个参数的方法
-
(*, String) 第一个参数类型随意,第二个参数是String的。
异常:
- 可选项。
- 省略时表示任意异常类型。
com.service包下的所有方法:
execution(public * com.service…*(…))com.service包下的所有以delete开头的方法:
execution(public * com.service..delete(…))
3. Spring AOP框架部署
3.1 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 创建SpringAOP配置文件
<?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: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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
4. AOP通知策略
AOP通知策略:就是声明将切面类中的切点方法如何织入到切入点
- before:前置通知,出现在方法的前面
- after:最终通知,一个方法中所有东西执行完了才会执行这个通知
- after-throwing:异常通知,出现了异常就会执行
- after-returning:后置通知
- around:环绕通知,出现在方法的前后,适用于事务等
5. Spring AOP实现
5.1 定义目标类
@Service
public class AccountService {
public void transfer() {
System.out.println("正在完成转账功能");
}
public void withdraw() {
System.out.println("正在完成取款功能");
//模拟异常
int b = 7/0;
}
}
5.2 定义切面类
@Component //交给spring容器管理
@Aspect //切面注解
@Order(1)//如果有多个切面类,可以通过这个数字确定优先级
public class TransactionAspect {
//通用切点
@Pointcut("execution(* com.Service.AccountService.*(..))")
public void Pointcut(){
}
//环绕通知,必须有返回值,不然传输的数据就会丢失
@Around("Pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
Object retValue = null;
try {
System.out.println("开启事务");
retValue = joinPoint.proceed();
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
return retValue;
}
}
5.3 修改配置文件
<!--扫描包,-->
<context:component-scan base-package="com"/>
<!--开启aop自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
-
<aop:aspectj-autoproxy proxy-target-class=“true”/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
-
proxy-target-class=“true” 表示采用cglib动态代理。
-
proxy-target-class=“false” 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
5.4 测试
@Test
public void testBefore(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transfer();
System.out.println("------------------");
//演示异常情况
accountService.withdraw();
}