为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
静态代理和动态代理
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的
.class
文件就已经存在了- 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。
- 缺点:
- 一个代理类只能为一个接口服务
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法
代理接口
public interface Subject {
public void dealTask(String taskName);
}
真正执行任务的类
public class RealSubject implements Subject {
@Override
public void dealTask(String taskName) {
System.out.println("正在执行任务:"+taskName);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代理类
public class ProxySubject implements Subject {
//代理类持有一个委托类的对象引用
private Subject delegate;
public ProxySubject(Subject delegate) {
this.delegate = delegate;
}
/**
* 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间
*
* @param taskName
*/
@Override
public void dealTask(String taskName) {
long stime = System.currentTimeMillis();
//将请求分派给委托类处理
delegate.dealTask(taskName);
long ftime = System.currentTimeMillis();
System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");
}
}
静态代理类工厂
public class SubjectStaticFactory {
//客户类调用此工厂方法获得代理对象。
//对客户类来说,其并不知道返回的是代理类对象还是委托类对象。
public static Subject getInstance(){
return new ProxySubject(new RealSubject());
}
}
客户类
public class Client1 {
public static void main(String[] args) {
Subject proxy = SubjectStaticFactory.getInstance();
proxy.dealTask("DBQueryTask");
}
}
- 动态代理:在程序运行时,运用反射机制动态创建而成
jdk
动态代理 只能代理实现接口的类,不能实现接口的类就不能实现JDK
的动态代理CGLib
动态代理
Spring AOP:前置增强、后置增强、环绕增强,抛出增强
- 前置增强类实现了
org.springframework.aop.MethodBeforeAdvice
接口 - 后置增强类实现了
org.springframework.aop.AfterReturningAdvice
接口 - 环绕增强类需要实现
org.aopalliance.intercept.MethodInterceptor
接口 - 抛出增强类需要实现
org.springframework.aop.ThrowsAdvice
接口
- 引入增强类,扩展了
org.springframework.aop.support.DelegatingIntroductionInterceptor
类
以上定义了一个引入增强类,扩展了 org.springframework.aop.support.DelegatingIntroductionInterceptor
类,同时也实现了新定义的Apology
接口。在类中首先覆盖了父类的invoke()
方法,然后实现了Apology
接口的方法
需要注意proxyTargetClass
属性,它表明是否代理目标类,默认为false
,也就是代理接口了,此时Spring
就用JDK
动态代理。如果为true
,那么Spring
就用CGLib
动态代理
saySorry()
方法是可以被greetingImpl
对象来直接调用的,只需将其强制转换为该接口即可
Spring AOP :切面
通过切面,将增强类与拦截匹配条件组合在一起,然后将这个切面配置到ProxyFactory
中,从而生成代理
Advisor
(切面)封装了Advice
(增强)与Pointcut
(切点 )
注意以上代理对象的配置中的interceptorNames
,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。
需要强调的是,这里的切点表达式是基于正则表达式的。
示例中的aop.demo.GreetingImpl.good.*
表达式后面的.*
表示匹配所有字符,翻译过来就是“匹配 aop.demo.GreetingImpl
类中以 good
开头的方法”
除了RegexpMethodPointcutAdvisor
以外,在Spring AOP
中还提供了几个切面类,比如:
DefaultPointcutAdvisor
:默认切面(可扩展它来自定义切面)NameMatchMethodPointcutAdvisor
:根据方法名称进行匹配的切面StaticMethodMatcherPointcutAdvisor
:用于匹配静态方法的切面
Spring AOP:自动代理
- 扫描
Bean
名称
以上使用BeanNameAutoProxyCreator
只为后缀为Impl
的Bean
生成代理。
需要注意的是,这个地方我们不能定义代理接口,也就是interfaces
属性,因为我们根本就不知道这些Bean
到底实现了多少接口。此时不能代理接口,而只能代理类。
所以这里提供了一个新的配置项,它就是optimize
。若为true
时,则可对代理生成策略进行优化(默认是false
的)。也就是说,如果该类有接口,就代理接口(使用JDK
动态代理);
如果没有接口,就代理类(使用CGLib
动态代理)。而并非像之前使用的proxyTargetClass
属性那样,强制代理类,而不考虑代理接口的方式。可见Spring AOP
确实为我们提供了很多很好地服务
CGLib
创建代理的速度比较慢,但创建代理后运行的速度却非常快,而JDK
动态代理正好相反。如果在运行的时候不断地用CGLib
去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用CGLib
去创建代理,并放入Spring
的ApplicationContext
中以备后用。
- 扫描切面配置
这里无需再配置代理了,因为代理将会由DefaultAdvisorAutoProxyCreator
自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。
Spring + AspectJ
- 基于注解:通过 AspectJ execution 表达式拦截方法
类上面标注的@Aspect
注解,这表明该类是一个Aspect
(其实就是Advisor
)。
该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注@Around
注解,在注解中使用了AspectJ
切点表达式。方法的参数中包括一个ProceedingJoinPoint
对象,它在AOP
中称为Joinpoint
(连接点),可以通过该对象获取方法的任何信息
execution(* aop.demo.GreetingImpl.*(..))
execution():表示拦截方法,括号中可定义需要匹配的规则。
第一个“*”:表示方法的返回值是任意的。
第二个“*”:表示匹配该类中所有的方法。
(..):表示方法的参数是任意的。
配置<aop:aspectj-autoproxy proxy-target-class="true"/>
- 基于注解:通过 AspectJ
@annotation
表达式拦截方法
除了@Around
注解外,其实还有几个相关的注解,稍微归纳一下吧:
@Before:前置增强
@After:后置增强
@Around:环绕增强
@AfterThrowing:抛出增强
@DeclareParents:引入增强
除了使用@Aspect
注解来定义切面类以外,Spring AOP
也提供了基于配置的方式来定义切面类:
<?xml version="1.0" encoding="UTF-8"?>
<beans ...">
<bean id="greetingImpl" class="aop.demo.GreetingImpl"/>
<bean id="greetingAspect" class="aop.demo.GreetingAspect"/>
<aop:config>
<aop:aspect ref="greetingAspect">
<aop:around method="around" pointcut="execution(* aop.demo.GreetingImpl.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
使用<aop:config>
元素来进行AOP
配置,在其子元素中配置切面,包括增强类型、目标方法、切点等信息。