一、动态代理是什么
什么是动态代理以及为什么使用动态代理、平时编程中什么地方使用到了动态代理?
要理解什么是动态代理,首先需要知到代理是什么,其实很简单,就跟字面上一样,代理:代替别人打理事务。
在编程中代理就是面向切面编程的一种实现,然后我们就需要知道什么是面向切面编程,例如我们写的方法,在调用前、后需要打印一条日志或者执行事务或者计算调用方法所需要的时间等,就会每个方法上都加上重复的代码,会使得代码很臃肿,这时候就需要用到面向切面编程的思想。
面向切面编程就是把重复的代码抽出来,单独放在一个代理类里面、通过代理的方式来给每个方法添加需要的功能
然后平时我们使用的spring AOP就是面向切面编程的一种实现,而动态代理就是AOP的底层原理
二、代理分类
- 静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。(即代理类及对象要我们自己创建)
- 动态代理:代理类是在程序运行期间由 JVM 通过反射等机制动态的生成的,所以不存在代理类的字节码文件,动态生成字节码对象,代理对象和真实对象的关系是在程序运行时期才确定的。(即代理类及对象不要我们自己创建)
三、动态代理实现方式
- 针对真实类有接口使用 JDK 动态代理,需要使用 java.lang.reflect 包中的 Proxy 类与 InvocationHandler 接口;
- 针对真实类没实现接口使用 CGLIB 或 Javassist 组件。
四、两者区别:
静态代理优点:
- 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
- 把真实对象隐藏起来了,保护真实对象。
缺点
- 代理对象的某个接口只服务于某一种类型的对象,也就是为每个真实类创建一个代理类,比如项目还有其他业务类呢。
- 若需要代理的方法很多,则要为每一种方法都进行代理处理。
- 若接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。
动态代理优点:
- 对比静态代理,发现不需手动地提供那么多代理类。
缺点
- 真实对象必需实现接口(JDK 动态代理特有);
- 动态代理的最小单位是类(类中某些方法都会被处理),如果只想拦截一部分方法,可以在 invoke 方法中对要执行的方法名进行判断;
- 对多个真实对象进行代理的话,若使用 Spring 的话配置太多了,要手动创建代理对象,用起来麻烦。
五、代码
1.jdk
事务调用处理者
public class TransactionInvocationHandler implements InvocationHandler {
private Object object; //用来接收需要被代理的类
private TransactionManager tx; //用来提供事务
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public TransactionManager getTx() {
return tx;
}
public void setTx(TransactionManager tx) {
this.tx = tx;
}
/**
* @param proxy 真实对象的真实代理对象
* @param method 代理对象调用的方法
* @param args 方法中传入的参数
* @return 返回你想返回的,根据需求来
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = null;
try {
tx.begin();
o = method.invoke(object, args);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}
return o;
}
}
模拟一下业务需求
public class EmployeeServiceImpl implements EmployeeService {
@Override
public void save(String name, String password) {
System.out.println("保存了"+name+":"+password);
}
}
事务模拟
public class TransactionManager {
public void begin(){
System.out.println("事务开始了");
}
public void commit(){
System.out.println("事务提交了");
}
public void rollback(){
System.out.println("事务回滚");
}
}
测试类,给业务加事务
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class EmployeeServiceImplTest {
/**
* 动态代理
*/
@Autowired
private TransactionInvocationHandler transactionInvocationHandler;
@Test
public void save() {
EmployeeService proxy = (EmployeeService) Proxy.newProxyInstance(
this.getClass().getClassLoader(),//获取当前类的类加载器
transactionInvocationHandler.getObject().getClass().getInterfaces(),//获取被代理类的所有接口,
// 被代理类由配置文件中传入tih对象中的obj属性
transactionInvocationHandler);//传入该对象给被创建的代理类中的字段接收
proxy.save("JNX", "666");
}/*创建的的代理类中接收transactionInvocationHandler的对象调用invoke方法
创建好的proxy对象调用save方法时
方法中的method再调用接口中的save方法
*/
}
给spring注入bean,springboot则不需要写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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置事务管理器对象-->
<bean class="cn.xxx.handler.TransactionManager" id="tx"/>
<!-- 配置处理器执行对象-->
<bean class="cn.xxx.handler.TransactionInvocationHandler"
id="transactionInvocationHandler">
<property name="object">
<bean class="cn.xxx.service.impl.EmployeeServiceImpl"/>
</property>
<property name="tx" ref="tx"/>
</bean>
</beans>
2.CGlib
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class EmployeeServiceTest {
@Autowired
private TransactionInvocationHandler transactionInvocationHandler;
@Test
public void testSave() {
Enhancer enhancer = new Enhancer();
// 设置生成代理类继承的类
enhancer.setSuperclass(transactionInvocationHandler.getTarget().getClass());
// 设置生成代理类对象对象,要做什么事情
enhancer.setCallback(transactionInvocationHandler);
// 生成代理类,创建代理对象
EmployeeServiceImpl proxy = (EmployeeServiceImpl)enhancer.create();
proxy.save("罗老师", "666");
}
}
3、选用
JDK 动态代理是基于实现接口的,CGLIB 和 Javassit 是基于继承真实类的。
从性能上考虑:Javassit > CGLIB > JDK。
选用如下:
- 若真实类实现了接口,优先选用 JDK 动态代理。(因为会产生更加松耦合的系统,也更符合面向接口编程)
- 若真实类没有实现任何接口,使用 Javassit 和 CGLIB 动态代理。
其实我觉得吧,可以理解为真实类是房东,代理类是中介,这么理解他们之间的关系,房东需要收租,中介可以帮他收租,但不仅限于收租
AOP篇
一、AOP 术语
- Joinpoint:连接点,一般指需要被增强的方法。where:去哪里做增强。
- Pointcut:切入点,哪些包中的哪些类中的哪些方法,可认为是连接点的集合。where:去哪些地方做增强。
- Advice:增强,当拦截到 Joinpoint 之后,在方法执行的什么时机(when)做什么样(what)的增强。根据时机分为:前置增强、后置增强、异常增强、最终增强、环绕增强。
- Aspect:切面,Pointcut + Advice,去哪些地方 + 在什么时候 + 做什么增强。
- Target:被代理的目标对象。
- Weaving:织入,把 Advice 加到 Target 上之后,创建出 Proxy 对象的过程。
- Proxy:一个类被 AOP 织入增强后,产生的代理类。
二、使用
AOP的好处就是可以使用注解自定义需要的事务
@Component
@Aspect
public class MyTransactionManager {
// 定义切入点 WHERE
@Pointcut("execution(* cn.xxx.service.impl.*ServiceImpl.*(..))")
public void txPointcut() {}
@Before("txPointcut()")
public void begin() {
System.out.println("开启事务");
}
@AfterReturning("txPointcut()")
public void commit() {
System.out.println("提交事务");
}
@AfterThrowing("txPointcut()")
public void rollback() {
System.out.println("回滚事务");
}
}
关于AOP的其他见:
我的另一篇AOP常用注解