1.Aop: 面向切面编程
具体的视图可以这样理解:
即通过切面来使代码不冗余,同样也便于修改。
2. Aop术语
- 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
- 通知(Advice): 切面必须要完成的工作 目标(Target): 被通知的对象
- 代理(Proxy): 向目标对象应用通知之后创建的对象
- 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
- 切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
3. 用 AspectJ 注解声明切面
具体步骤:
- 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例.
- 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
- 通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
首先写一个接口类 一个实现类,用于实现代码
接口类:
package SpringAop;
public interface Userformula {
public int add(int i,int j);
public int sub(int i,int j);
public int mul(int i,int j);
public int div(int i,int j);
}
实现类 (注意添加注解 @Component(value="userformula") )
package SpringAop;
import org.springframework.stereotype.Component;
@Component(value="userformula")
public class formulaimpl implements Userformula {
@Override
public int add(int i, int j) {
// TODO 自动生成的方法存根
int result=i+j;
return result;
}
@Override
public int sub(int i, int j) {
// TODO 自动生成的方法存根
int result=i-j;
return result;
}
@Override
public int mul(int i, int j) {
// TODO 自动生成的方法存根
int result=i*j;
return result;
}
@Override
public int div(int i, int j) {
// TODO 自动生成的方法存根
int result=i/j;
return result;
}
}
创建切面类:
需要添加 @Component @Aspect 两个注解
① : 前置通知
创建方法, 添加前置通知
如果要获取切入点的数据或者值,可以通过 JoinPoint 来获取
@Before("execution(public int SpringAop.*.*(int, int))")
public void beformethod(JoinPoint joinPoint) {
//切入点方法名
String methodName = joinPoint.getSignature().getName();
//将切入点获取的值传入集合
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
②:后置通知 (同理与前置)
@After("execution(public int SpringAop.*.*(int, int))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
③:返回通知
//返回通知 可以通过returning="result" 以及方法名中加Object result 来获取返回值
@AfterReturning(value="execution(public int SpringAop.*.*(int, int))",
returning="result")
public void afterreturnning(JoinPoint joinPoint,Object result) {
String methodName=joinPoint.getSignature().getName();
System.out.println("The method"+methodName+"result:"+result);
}
④:异常通知
//异常通知 同返回通知一样
@AfterThrowing(value="execution(public int SpringAop.*.*(int, int))",throwing="ex")
public void afterthrowig(JoinPoint joinPoint,Exception ex) {
String methodname=joinPoint.getSignature().getName();
System.out.println("The method"+methodname+"exception:"+ex);
}
⑤:环绕通知(不常用)
将通知全部围绕在一个里面
@Around(value="execution(public int SpringAop.*.*(int, int))")
public Object around(ProceedingJoinPoint pjd) {
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}
从上面可以看到每一个注解后面都需要添加exetution() 如果需要修改则需要修改多处,不符合现代代码风格,违背设计模式。
我们需要通过一种方式来封装起来。
利用 @Pointcut("execution(public int SpringAop.*.*(int, int))") 然后后面添加一个空方法,下面则只需要调用空方法就可以实现。
@Pointcut("execution(public int SpringAop.*.*(int, int))")
public void point() {
}
调用切入点方法实现前置通知。
@Before("point()")
public void beformethod(JoinPoint joinPoint) {
//切入点方法名
String methodName = joinPoint.getSignature().getName();
//将切入点获取的值传入集合
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
配置文件中添加Context 和 Aop
<context:component-scan base-package="SpringAop"></context:component-scan>
<!-- 起到切面作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
写一个主函数:
package SpringAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
ApplicationContext ctx=new ClassPathXmlApplicationContext("Applicationcontext.xml");
Userformula user=(Userformula) ctx.getBean("userformula");
int r=user.add(3, 6);
System.out.println("r的值为: "+r);
int s=user.div(100, 10);
System.out.println("s的值为"+s);
}
}
运行即可完成。
运行结果:
分别为 前置通知 后置通知 返回通知 因为无错所以没有报异常通知