1、动态代理
在面向对象的思想中,一些重复的代码可以被封装成一个方法或者一个类供以后使用,那么对于一个核心业务的边缘业务,也可以进行“封装”,让这些边缘业务可以自动在核心业务运行时生效,我们只需要专注核心业务即可,这种需求可以利用代理来实现,JDK为我们提供的其中一种代理就是动态代理。
例如租房,个人可以看房和签合同,他委托中介为他寻找房源,只需要关键时候来看房签合同就可以,中介在租房后还提供售后服务,并且中介可以为任何人提供服务。这种行为就是动态代理,中介就是代理类完成非核心业务,而最终的核心业务(看房和签合同)需要借助个人即被代理类完成,所以表面看上去中介可以完成租房的所有操作。下面是代码示例:
1、首先,我们需要一个租房的核心业务接口
public interface IRentHouse {
//核心业务是租房
void rent();
}
2、其次,需要一个人实现租房接口
public class Tenant implements IRentHouse {
@Override
public void rent() {
System.out.println("个人看房,签订租房合同。");
}
}
3、需要一个中介进行租房代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//中介需要实现JDK提供的InvocationHandler接口,实现被代理方法执行前后的拦截
public class HousingAgency implements InvocationHandler {
//被代理的类 可以是任意类型 所以用Object定义
private Object object;
public HousingAgency(Object object) {
this.object = object;
}
@Override
//Object proxy JDk提供的参数 不能使用
//Method method 被代理的方法
//Object[] args 方法里的参数 不知道什么类型 所以用Object类型的数组
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//这是非核心业务
System.out.println("中介寻找房源,联系房东。。。");
//执行核心业务 接受可能有的返回值
result = method.invoke(object, args);
//这也是非核心业务
System.out.println("中介提供售后,解决争执。。。");
return result;
}
}
4、通过Proxy类创建代理对象(测试)
import com.fan.spring.proxy.HousingAgency;
import com.fan.spring.service.IRentHouse;
import com.fan.spring.service.Tenant;
import org.junit.Test;
import java.lang.reflect.Proxy;
public class TestProxy {
@Test
public void testProxy(){
//new Tenant()就是被代理的对象 核心业务通过这个对象来完成
HousingAgency housingAgency=new HousingAgency(new Tenant());
//生成具有附加功能的IRentHouse代理对象
//Tenant.class.getClassLoader()代理对象的类加载器
//Tenant.class.getInterfaces()代理对象实现的接口
IRentHouse iRentHouse= (IRentHouse) Proxy.newProxyInstance(Tenant.class.getClassLoader(),
Tenant.class.getInterfaces(),
housingAgency);
//执行方法
iRentHouse.rent();
}
}
5、执行结果
在控制台打印输出了:
中介寻找房源,联系房东。。。
个人看房,签订租房合同。
中介提供售后,解决争执。。。
6、总结
1、JDK提供的Proxy代理的对象必须要实现一个接口 从最终生成代理对象的第二个参数可以看出,必须getInterfaces()。
2、InvocationHandler接口的实现类可以代理任何对象,也就是说这个对象可以实现不同的接口,也可以是同一堆接口的不同实现类。比如说除了租房,还可以有另一个InvocationHandler接口的实现类提供打印日志的功能,所有其他类都可以用它做代理,来实现方法执行前后打印日志。
3、根据第二点,如果租房功能既需要中介完成寻找房源提供售后,有需要打印日志,那么就要再写一个InvocationHandler接口的实现类,来同时完成这些功能。也就是说InvocationHandler接口的实现类的非核心业务是固定不变的,不够灵活。
4、Proxy生成代理类十分繁琐,需要传入各种参数并且进行类型转换。
1、Spring中的AOP
在动态代理中我们发现,每个InvocationHandler只能实现一种扩展,并且生成实例很麻烦,那么有没有一种可以直接生成被代理类对象,并且可以对其中的扩展功能进行自定义组合呢?答案是可以,Spring中AOP集成了Aspect,可以方便我们进行面向切面编程。
1、关键词解释
1、连接点:
AOP中,对一个方法有五个连接点,分别是在方法执行前(Before)、方法执行后(After)、出现异常后(AfterThrowing)、返回返回值后(AfterReturning)和环绕方法(Around)。这5个连接点是AOP为我们提供的,从名称中就可以看出它们的生效范围。在某种程度上来说,一个方法本身可以看做一个大的连接点,这个在之后的代码实例中可以看出来。
2、增强(通知)
增强(或者称为通知)是切入的扩展功能,比如说打印日志、安全验证和计算执行时间等等。
3、切点
切点是相对于连接点的一个概念,在5个连接点中,我们选择进行增强的连接点被称为切点。比如我要在增删改的异常连接点(AfterThrowing)进行事务回滚,环绕(Around)进行开启事务和事务提交,那么这两个连接点的组合就被称为切点。在比如要对某个操作方法执行前(Before)进行安全验证,那么这一个连接点就被称为切点。
4、切面
切点+增强=切面。具体来说就是在哪些地方进行了哪些扩展。
2、切面代码示例
1、需要进行切入的类
注意这个类的包名com.fan.spring.service
package com.fan.spring.service;
import com.fan.spring.mapper.PersonMapper;
import com.fan.spring.pojo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//这是一个service层,注入了Mapper对象模拟数据操作
@Service
public class PersonServiceImpl implements IPersonService {
@Autowired
PersonMapper personMapper;
@Override
public void add(Person person) {
personMapper.add(person);
}
@Override
public void delete(Integer id) {
personMapper.delete(id);
}
@Override
public void update(Person person) {
personMapper.update(person);
}
@Override
public String test() {
System.out.println("\n有返回值的方法\n");
return "返回了一个字符串";
}
}
2、Spring的配置
3、切面示例
package com.fan.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
//这个注解表示这是一个切面类
@Aspect
public class MyAop {
/*execution表达式里面就是切点 关于这个表达式的具体解析可以自行在网上搜索*/
@Pointcut("execution(public * com.fan.spring.service.*.*(..))")
public void myPointCut() {
}
//在切入之前就要在5个连接点中先选择一个,然后再输入具体方法 这就是为什么说一个方法也可以看成一个连接点
@Before("myPointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("前置切入===>" + joinPoint.getSignature().getName());
}
@After("myPointCut()")
public void after(JoinPoint joinPoint) {
System.out.println("后置切入===>" + joinPoint.getSignature().getName());
}
@Around("myPointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("前环绕切入===>" + proceedingJoinPoint.getSignature().getName());
Object obj = proceedingJoinPoint.proceed();
System.out.println("后环绕切入===>" + proceedingJoinPoint.getSignature().getName() + "---" + obj);
return obj;
}
@AfterReturning(pointcut = "myPointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("返回值切入===>" + joinPoint.getSignature().getName() + "-----" + result);
}
@AfterThrowing(pointcut = "myPointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
System.out.println("异常切入===>" + joinPoint.getSignature().getName() + "---" + e);
}
}
4、代码测试
@Test
public void testAop() {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
//获取实例后直接调用方法 不需要向动态代理一样麻烦
PersonServiceImpl personServiceImpl = ac.getBean("personServiceImpl", PersonServiceImpl.class);
personServiceImpl.test();
}
输出结果
前环绕切入===>test
前置切入===>test
有返回值的方法
后环绕切入===>test---返回了一个字符串
后置切入===>test
返回值切入===>test-----返回了一个字符串
@Test
public void testAop() {
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonServiceImpl personServiceImpl = ac.getBean("personServiceImpl", PersonServiceImpl.class);
personServiceImpl.add(null);
}
输出结果
前环绕切入===>add
前置切入===>add
增加。。。
后环绕切入===>add---null
后置切入===>add
返回值切入===>add-----null
3、总结
利用连接点进行灵活选择,根据实际代码加入不同增强,这样切面的可扩展性无疑比原生的动态代理要强很多,而且实际在使用时,只需要关注核心业务即可,实例化也很简单,易用性大大增强