它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。
@EnableAspectJAutoProxy
等同于aop:aspectj-autoproxy/
/**
* 计算类接口
*/
public interface Calculate {
/**
* 加法
* @param numA
* @param numB
* @return
*/
int add(int numA, int numB);
/**
* 减法
* @param numA
* @param numB
* @return
*/
int sub(int numA, int numB);
/**
* 除法
* @param numA
* @param numB
* @return
*/
int div(int numA, int numB);
/**
* 乘法
* @param numA
* @param numB
* @return
*/
int multi(int numA, int numB);
int mod(int numA, int numB);
}
@Component
public class TulingCalculate implements Calculate {
public int add(int numA, int numB) {
System.out.println("执行目标方法:add");
return numA+numB;
}
public int sub(int numA, int numB) {
System.out.println("执行目标方法:reduce");
return numA-numB;
}
public int div(int numA, int numB) {
System.out.println("执行目标方法:div");
return numA/numB;
}
public int multi(int numA, int numB) {
System.out.println("执行目标方法:multi");
return numA*numB;
}
public int mod(int numA,int numB){
System.out.println("执行目标方法:mod");
int retVal = ((Calculate)AopContext.currentProxy()).add(numA,numB);
return retVal%numA;
}
}
@Aspect
@Order
@Component
public class TulingLogAspect {
@Pointcut("execution(* tuling.TulingCalculate.*(..))")
public void pointCut(){};
@Before(value = "pointCut()")
public void methodBefore(JoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
}
@After(value = "pointCut()")
public void methodAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "pointCut()",returning = "result")
public void methodReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
@AfterThrowing(value = "pointCut()")
public void methodAfterThrowing(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】的<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));
}
}
@Configuration
@EnableAspectJAutoProxy /* */
@ComponentScan("tuling")
public class MainConfig {
@Bean
public Calculate calculate() {
return new TulingCalculate();
}
@Bean
public TulingLogAspect tulingLogAspect() {
return new TulingLogAspect();
}
@Bean
public Calculate calculate2() {
return new TulingCalculate();
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(MainConfig.class);
Calculate calculate = (Calculate) ctx.getBean("tulingCalculate");
int retVal = calculate.div(2,4);
}
目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容,所以大家可以放心使用。
我们看下最早的时候没有切面切点那些概念的时候,如何用接口的形式实现
举个例子
Spring常见代理创建方式
FactoryBean方式,创建单个动态代理: ProxyFactoryBean
1.指定续增强的接口:proxyInterfaces
2.指定需增强的实现类target
3.指定Advice、Advisor、Interceptor都行
public class EalyAopMainConfig {
// 被代理对象
@Bean
public Calculate tulingCalculate() {
return new TulingCalculate();
}
// Advice 方式
@Bean
public TulingLogAdvice tulingLogAdvice(){
return new TulingLogAdvice();
}
// Interceptor方式 , 可以理解为环绕通知
@Bean
public TulingLogInterceptor tulingLogInterceptor() {
return new TulingLogInterceptor();
}
/**
* FactoryBean方式单个: ProxyFactoryBean
*
* 此中方法有个致命的问题,如果我们只能指定单一的Bean的AOP,
* 如果多个Bean需要创建多个ProxyFactoryBean 。
* 而且,我们看到,我们的拦截器的粒度只控制到了类级别,类中所有的方法都进行了拦截。
* 接下来,我们看看怎么样只拦截特定的方法。
* @return*/
@Bean
public ProxyFactoryBean calculateProxy(){
ProxyFactoryBean userService=new ProxyFactoryBean();
userService.setInterceptorNames("tulingLogAdvice","tulingLogInterceptor");
// 根据指定的顺序执行
userService.setTarget(tulingCalculate());
return userService;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate calculateProxy = ctx.getBean("calculateProxy",Calculate.class);
System.out.println(calculateProxy.getClass());
calculateProxy.div(1,1);
}
创建的是tulingCalculate的动态代理,所以可以用接口来接收。
ProxyFactoryBean实现原理:把这些通知放到链当中,然后利用责任链模式
调用动态代理。
如果要用责任链,
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {
// 前置通知
MethodBeforeAdvice methodBeforeAdvice;
public MethodBeforeAdviceInterceptor(
MethodBeforeAdvice methodBeforeAdvice) {
this.methodBeforeAdvice = methodBeforeAdvice;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
methodBeforeAdvice.before(
invocation.getMethod(),
invocation.getArguments(),invocation.getThis()
);
return invocation.proceed();
}
}
之前的TulingLogAdvice也是继承了MethodBeforeAdvice
public class TulingLogAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getName();
System.out.println("执行目标方法【" + methodName + "】的<前置通知>,入参" + Arrays.asList(args));
}
}
那么看下main
public class MainStart {
public static void main(String[] args) throws Throwable {
// 把一条链上的都初始化
List<MethodInterceptor> list=new ArrayList<>();
list.add(new MethodBeforeAdviceInterceptor(new TulingLogAdvice()));
list.add(new TulingLogInterceptor());
// 递归依次调用
MyMethodInvocation invocation=new MyMethodInvocation(list);
invocation.proceed();
}
public static class MyMethodInvocation implements MethodInvocation{
protected List<MethodInterceptor> list;
protected final TulingCalculate target;
public MyMethodInvocation(List<MethodInterceptor> list) {
this.list = list;
this.target = new TulingCalculate();
}
int i=0;
@Override
public Object proceed() throws Throwable {
if(i==list.size()){
return target.add(2,2);
}
MethodInterceptor mi = list.get(i);
i++;
return mi.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return null;
}
@Override
public Method getMethod() {
try {
return target.getClass().getMethod("add",int.class,int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
@Override
public Object[] getArguments() {
return new Object[0];
}
}
}
刚刚介绍的ProxyFactoryBean
这种方式很麻烦,一个被增强的类就需要定义一个ProxyFactoryBean。如果有很多就会有很多ProxyFactoryBean。
并且只能控制到类的级别,方法的就不能控制了。
接下哎我们看看怎么拦截特定方法:
/**
* Advisor 种类很多:
* RegexpMethodPointcutAdvisor 按正则匹配类
* NameMatchMethodPointcutAdvisor 按方法名匹配
* DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
@Bean
public NameMatchMethodPointcutAdvisor tulingLogAspectAdvisor() {
NameMatchMethodPointcutAdvisor advisor=new NameMatchMethodPointcutAdvisor();
// 通知(Advice) :是我们的通知类 没有带切点
// 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
advisor.setAdvice(tulingLogAdvice());
advisor.setMappedNames("div");
return advisor;
}
@Bean
public ProxyFactoryBean calculateProxy(){
ProxyFactoryBean userService=new ProxyFactoryBean();
userService.setInterceptorNames("tulingLogAspectAdvisor");
userService.setTarget(tulingCalculate());
return userService;
}
注解方式的AOP会把@Before
@After
等注册成一个Advisor
这种方式还是没有解决每一个增强的类配置一个ProxyFactoryBean
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("tuling*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("tulingLogAspectAdvisor");
return beanNameAutoProxyCreator;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
tulingCalculate.div(1,1);
}
接下来看下看下BeanNameAutoProxyCreator
的方式
/**
* autoProxy: BeanPostProcessor手动指定Advice方式 BeanNameAutoProxyCreator
* @return */
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
//设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("tuling*");
//设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("tulingLogAdvice");
return beanNameAutoProxyCreator;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(EalyAopMainConfig.class);
Calculate tulingCalculate = ctx.getBean("tulingCalculate",Calculate.class);
tulingCalculate.div(1,1);
}
可以看到这样距离Aspect的方式已经很近了
我们知道,spring中的aop是通过动态代理实现的,那么他具体是如何实现的呢?spring通过一个切面类,在他的类上加入@Aspect注 解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
先来看是怎么解析切面的
是通过AnnotationAwareAspectJAutoProxyCreator这个bean的后置处理器生效的。看下类结构
会在第一个bean的后置处理器的地方去解析切面
AbstractAutoProxyCreator类的顶层InstantiationAwareBeanPostProcessor
,调用
postProcessBeforeInstantiation
,
子类实现,AbstractAutoProxyCreator
类
会在第一个Bean创建的时候,调用Bean的后置处理器,解析所有的切面。这个动作是非常消耗性能的,所以会放到缓存中。
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
//构建我们的缓存key
Object cacheKey = getCacheKey(beanClass, beanName);
// 没有beanName 或者 没有包含在targetSourcedBeans中(一般都不会包含,因为targetSource需要手动设置,一般情况不会设置)
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
//被解析过 直接返回
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
/**
* 注意看重写方法
* 判断是不是基础的bean (是不是切面类、通知、切点等)
* 判断是不是应该跳过 默认false (切面解析也在其中)
*/
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
/**
* TargetSource 代理逻辑的实现,, 在创建代理时默认是SingletonTargetSource
* 所以如果指定了TargetSource 说明有自己的代理逻辑实现,在这就直接创建代理
*/
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
是不是跳过:
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
/**
* 找到候选的Advisors(通知 前置通知、后置通知等..)
*/
List<Advisor> candidateAdvisors = findCandidateAdvisors();
for (Advisor advisor : candidateAdvisors) {
// 判断这个类的原因在于:
// AspectJPointcutAdvisor 是xml
// 如果 是当前beanName 就说明当前bean是切面类 那就跳过。
if (advisor instanceof AspectJPointcutAdvisor &&
((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
return true;
}
}
return super.shouldSkip(beanClass, beanName);
}
找到候选的Advisors(通知 前置通知、后置通知等…)
protected List<Advisor> findCandidateAdvisors() {
// 找出xml配置的Advisor和原生接口的AOP的Advisor 找出事务相关的advisor
List<Advisor> advisors = super.findCandidateAdvisors();
//找出Aspect相关的信息之后封装为一个advisor
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
//返回我们所有的通知
return advisors;
}
去容器中获取到所有的切面信息保存到缓存中buildAspectJAdvisors
。得到切面类的各种增强方法放到advisors
中。未完待续。。。