在学习AOP之前,我们先简单理解下IOC和AOP的原理。
IOC:控制反转
简单明白什么是控制反转
传统的面向对象编程是在一个类里需要哪个对象就new一个,new多了,就会造成各个类彼此之间的依赖性很高,甚至会造成一个对象出了个问题,导致其他类对象都不能运行的情况。所以这就是所谓的耦合度很高。
那么肯定需要一个方法来解耦啦,那就是ioc,ioc它是一个容器,在容器里存放的是bean对象,那么一个类要成为ioc内置的bean对象,需要在java类里面加入相关的注解声明,有了这个声明,ioc容器加载时就会全权管理这个类对象的一切,包括生命周期,也可以理解ioc是beanFactory的升级版,我们知道,beanFactory通常是在需要的类里直接静态或者继承关系来获得beanFactory里面的对象或者方法的,这种方法的依赖性还是有的,那么作为它的升级版ioc,那就不一样了,它可以说完全不依赖任何java文件,而是哪个java类需要某个对象,直接注入提供给他,极大的降低了耦合度。
DI:依赖注入。其实和IOC是一个原理,它只是基于IOC思想的一个实现
依赖注入有三种方法
- set方法
- 构造器
- 接口(不推荐)
依赖注入很简单,不做描述,一般都是配置自动扫描,然后自动注入@autowrite
AOP:面向切面
说实话也是ioc思想的衍生,面向对象完善,是一个很值得研究深透的一个思想。具体东西不说,直接简单点解释。在开发中,比如要做一个日志监控功能,功能要求每个方法前后都要后台打印执行的开始,结束时间,我们可以写一个接口或者继承来实现,但总管这样,效率依旧很低。我们可以这样子想:既然这些方法都要一个日志功能,传统的面向接口不太好,那么我们就用aop面向切面吧! 把日志功能当成一个切面,而这个切面是由许多切点构成的,想一下:很多个点,然后就成了一个面。。但是这些点的业务需求是一样的,只是实现不一样,否则有其他的点混入进来,这个“面”就不完美了,不完美那就不提倡了。那么什么是点呢?点就是连接点,一个方法里前后加入日志功能,那么它就有两个点,一前一后,这两个点我们就可以在抽出来的那个所谓的 "面 "的类中插入日志功能,我们可以通过注解或者xml来插入,有5种插入顺序,下面的列子会看到,其内部通过java的动态代理来实现,好,上个经典的例子。。
配置文件
<!-- 自动扫描 -->
<context:component-scan base-package="aop"></context:component-scan>
<!-- 自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
有两个类:一个接口:ArithmeticCalculator 一个实现:ArithmeticCalculatorImpl
接口定义了简单的加减乘除
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
接口的实现,返回结果
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
接下来 我要在每个方法里前后,或者出现异常的时候都打印日志,因为这些功能都是相同的,而且每个方法里都有接入点,所以就组成一个切面类:AspectjTest.class
@Component // 加入到ioc容器中,让其自动扫描
@Aspect // 声明为一个切面
public class AspectjTest {
// 前置通知,在目标方法开始前执行
@Before(value = "execution(int aop.ArithmeticCalculatorImpl.*(int, int))")
public void beforeMthod(JoinPoint joinpoint) {
String methodName = joinpoint.getSignature().getName();
Object[] args = joinpoint.getArgs();
List<Object> largs = (List) Arrays.asList(args);
System.out.println("the methood " + methodName + " begins with " + largs);
}
//后置通知,在目标方法执行完成后,无论结果有没有异常,都会执行的一个通知
//在后置通知里还不能访问目标方法的结果
@After(value="execution(int aop.ArithmeticCalculatorImpl.*(int, int))")
public void afterMethod(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
System.out.println("the methood " + methodName + " ends");
}
//运行结束通知,在目标方法正常执行完成后的一个通知
//可以访问到返回结果
@AfterReturning(value="execution(int aop.ArithmeticCalculatorImpl.*(int, int))",returning="result")
public void afterReturnRunMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("the methood " + methodName + " ends with: "+result);
}
//异常通知,在目标方法执行时发生异常返回的一个通知
//可以访问到异常结果
@AfterThrowing(value="execution(int aop.ArithmeticCalculatorImpl.*(int, int))",throwing="expection")
public void afterThrowingMethod(JoinPoint joinPoint,Object expection){
String methodName = joinPoint.getSignature().getName();
System.out.println("the methood "+methodName+" throw: "+expection );
}
//环绕通知:需携带ProceedingJoinPoint类型的参数
//环绕通知类似动态代理的全过程,ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
//环绕通知必须有返回值,返回值为目标方法的返回值
@Around(value="execution(int aop.ArithmeticCalculatorImpl.*(int, int))")
public Object aroundMethod(ProceedingJoinPoint pjp){
String methodName = pjp.getSignature().getName();
Object result = null;
try {
//前置通知
System.out.println("the methood " + methodName + " begins with " +Arrays.asList(pjp.getArgs()));
//执行目标方法
result = pjp.proceed();
//运行结束通知(正常结束)
System.out.println("the methood " + methodName + " ends with: "+result);
} catch (Throwable e) {
//异常通知
System.out.println("the methood "+methodName+" throw: "+e);
}
//后置通知(不管有没有异常都会返回)
System.out.println("the methood " + methodName + " ends");
return result;
}
}
客户端测试下:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ArithmeticCalculator ar = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");
int result = ar.add(1, 2);
System.out.println("result:"+result);
int result2 = ar.div(3, 1);
System.out.println("result2:"+result2);
}
}
观察控制台,结合上述,便能理解AOP的美妙之处