概述
Spring的两大特性:
- AOP
- IOC
关于IOC我们先放在一边,这篇文章主要聊一下AOP。
AOP(Aspect Orient Programming) 面向切面编程。切面能帮助我们模块化横切关注点。简言之:横切关注点,可以被描述为影响应用多处的功能。例如:日志 ,安全,事务,检查等。
在叙述AOP的实现原理之前,先来看一下如果Spring要实现AOP该做那些配置。使用了注解:
定义接口:
public interface Performance {
void perform();
}
定义接口实现类:
public class PerformanceImpl implements Performance {
public void perform() {
System.out.println("perform is perform ");
}
}
定义切面:
@Aspect //@Aspect 表明该类并不仅仅是一个pojo还是一个切面
public class Audience {
//定义可重用的切点
@Pointcut("execution(* com.spring.io.aspectj.PerformanceImpl.perform(..))")
public void perform() {
}
@Before("perform()") //在目标方法执行之前调用
public void silenceCellPhone() {
System.out.println("silence cell phone");
}
@Before("perform()") //在目标方法执行之后调用
public void takeSeats() {
System.out.println("take seats");
}
@AfterReturning("perform()") //目标方法返回后调用
public void applause() {
System.out.println(" applause ");
}
@AfterThrowing("perform()") //抛出异常后调用
public void demandRefund() {
System.out.println("demand refund");
}
}
java config 配置:
@Configuration
@EnableAspectJAutoProxy //启用AspectJ自动代理
public class ConcertConfig {
//声明Audience
@Bean
public Audience audience() {
return new Audience();
}
}
xml配置:
<context:component-scan base-package="com.spring.io"/>
<bean id = "performance" class="com.spring.io.aspectj.PerformanceImpl" />
方法调用:
public static void main( String[] args )
{
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
Performance bean = context.getBean(Performance.class);
bean.perform();
context.close();
//执行结果
//silence cell phone
//take seats
//perform is perform
//applause
}
通过以上代码,便通过Spring实现了一个切面功能。 那么它的工作流程是怎样的呢,我们接下来在进行分析。
AOP实现的关键就在AOP框架自动创建的AOP代理,AOP代理分为:
- 静态代理:在编译阶段就生成AOP代理类
- 动态代理:借助JDK动态代理(反射)、CGLIB等在内存“临时”生成AOP代理类
静态代理的代表是AspectJ,动态代理的代表是Spring AOP。关于静态代理本文不再叙述,感兴趣的可以自行研究。
SpringAOP的动态代理有两种方式:
- JDK动态代理: 通过反射技术生成代理类,必须有接口
- CGLIB动态代理: 没有接口则采用此种方式,通过继承做动态代理
我们说SpringAop是通过JDK动态代理和CGLIB实现的,那么我们就来写一个程序,模仿一下AOP代理类的生成,在回来看springaop中,spring帮助我们做了哪些事。
定义接口:
public interface Performance {
void perform();
}
定义接口实现类:
public class PerformanceImpl implements Performance {
public void perform() {
System.out.println("perform is perform ");
}
}
定义代理类
public class AduienceAop implements InvocationHandler{
private Object object; //定义代理对象
public AduienceAop(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("silence cell phone");
System.out.println("take seats");
method.invoke(object, args);
System.out.println(" applause ");
return null;
}
}
方法调用:
public static void main(String[] args) {
PerformanceImpl performance = new PerformanceImpl();
AduienceAop aduienceAop = new AduienceAop(performance);
Performance o = (Performance) Proxy.newProxyInstance(performance.getClass().getClassLoader(), performance.getClass().getInterfaces(), aduienceAop);
o.perform();
}
//执行结果
//silence cell phone
//take seats
//perform is perform
//applause
通过和上面两次的结果比对,发现程序的执行结果是一样的。 在springaop的配置中,我们没有去深入挖掘它的执行过程,但是在动态代理的执行过程,对我们确是很清晰。首先基于我们实现的手动代理类,我们定义了需要执行的业务逻辑。定义类接口,并实现了它。我们让切面实现了InvocationHandler接口,在invoke()方法中织入了切面需要执行的代码,并通过Proxy.newProxyInstance()方法创建了对象。在没有对原代码产生入侵的情况下,实现了调用,并加入了切面的功能。
由于spring的源码比较复杂,这里简述一下生成的过程:
在SpringAop中有几个重要的类:
- JdkDynamicAopProxy
JdkDynamicAopProxy类是final修饰的,实现了AopProxy,InvocationHandler接口。能够返回Proxy,所以当我们使用proxy时,最终执行的还是invoke()方法。 在spring的底层,会把我们定义的各个Adivce分别包裹成一个MethodInterceptor,这些方法按照加入Advised的顺序,构成一个AdivseChain,相当于方法拦截器,依次执行。
所以基于JDK实现的springAOP的动态代理方式的工作原理为:
接口 + 类 + 切入逻辑 ====》 代理类
基于CGLIB实现的springAOP的动态代理方式的工作原理为:
类 + 切入逻辑 === 》 代理类
SpringAOP的特点:
- Spring默认使用动态代理的方式实现AOP ,当动态代理不可用采用CGLIB实现
- 只能对方法进行切入,不能对接口,字段,静态代码块进行切入
- 同类中互相调用方法将不会使用代理类
- 性能不是最好的
网友评论