AOP(Aspect Orient Programming),也就是面向切面编程。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程。
常常通过 AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。
Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。Spring 默认使用 Java 动态代理来创建 AOP 代理, 这样就可以为任何接口实例创建代理了
其中需要程序员参与的只有三个部分:
所以进行 AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法。
底层实现代码—最原始方式,
@Test
public void t1(){
//1被代理对象--原型对象
Person p = new Person();
//2实现代理的工具类 --bean的代理 的 工厂
//ProxyFactory factory = new ProxyFactory();//低版本,不建议使用
ProxyFactoryBean factory = new ProxyFactoryBean();
//3给代理工厂设置 原型对象(代理的目标)
factory.setTarget(p); //※※
//切面 = 切点 + 通知
//4 定义切点
JdkRegexpMethodPointcut cut = new JdkRegexpMethodPointcut();
//5为切点设置正则表达式
cut.setPattern(".*run.*");
//6定义通知(环绕通知)
Advice advice = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前面拦拦.....");
Object returnValue = invocation.proceed();//放行
System.out.println("后面拦拦.....");
return returnValue;
}
};
//7声明切面 : Advisor = cut + advice
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
//8把切面设置到代理工厂中
factory.addAdvisors(advisor); //※※
//9从代理工厂中 获取 代理后的对象
Person p2 = (Person) factory.getObject();
//10用代理后的对象去执行方法,则如果符合切面规则就会被拦截
p2.run(); //拦
p2.hello(); //不拦
}
也可以通过XML方式来实现—将前面的代码改写成XML方式
advisor
XML方式调用代码如下
@Test
public void t1(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("cn/hncu/v1/v1.xml");
Person p = ctx.getBean("personProxyFactory", Person.class);
p.run(); //拦
p.hello(); //不拦
}
XML方式还可以简化,自动代理和把切点直接用正则表达式代替
.*run.*
关于自动代理----我们也可以手动写一个自动代理的类
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/*
* 通过写自定义自动代理,学习两个知识点:
* 1. 获取项目中的全局容器: 通过实现ApplicationContextAware监听器接口
* ---该技术可以让一个普通类(非Web环境)获得到项目的全局bean容器
* 2. 获取当前别的模块从bean容器中取出的bean: 通过实现BeanPostProcessor监听器接口
*
*/
public class MyAutoProxyCreator implements ApplicationContextAware, BeanPostProcessor {
private ApplicationContext ctx=null;
@Override
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx = ctx;
}
//////////下面两个是后处理bean的监听抽象方法//////////////
//该方法的执行时机: ctx容器在创建当前bean时,初始化之后
@Override //参数1为当前从容器读出的bean
public Object postProcessAfterInitialization(Object bean, String arg1)
throws BeansException {
if (bean instanceof Person) {
//把用户模块当前将要获取的bean转换成代理后的对象(bean),返回出去
ProxyFactoryBean factory = new ProxyFactoryBean();
factory.setTarget(bean);//原型
//到bean容器(ctx) 中遍历出所有 advisor(本例简化为只获一个),添加到factory中
Advisor ad = ctx.getBean(Advisor.class);
factory.addAdvisors(ad);
return factory.getObject();//代理后的对象
}else{
return bean;//放行
}
}
//该方法的执行时机: ctx容器在创建当前bean时,初始化之前
@Override//参数1为当前从容器读出的bean
public Object postProcessBeforeInitialization(Object bean, String arg1)
throws BeansException {
return bean; //直接放行
}
}
第二代技术的重点就是切点语言,可以实现拦截更加准确,如通过返回值来拦截方法,也可以具体到拦截某个具体的类的方法,甚至包名也可以具体规定,
具体代码—Java底层方式
/*
* 切点语言:
* 1) 框架: execution( 切点语言表达式 )
* 2) 表达式格式: 返回类型 包名.[子包名.]类名.方法名(参数类型列表)
* 3) "."号是包名之间 或 包名与类名之间 或 类名与方法名 之间的间隔符
* 4) "…"在包路径位置代表的是任意深的目录,在参数类型列表中代表的是任意个数与类型的参数
* 5) "*"号 是操作系统中的通配符
*/
@Test
// 切面=切点+通知
public void t1() {
// 1代理工厂及代理原型
ProxyFactoryBean factory = new ProxyFactoryBean();
factory.setTarget(new Person());
// 2声明切点-----版本2的标志,就是切点采用aspectj
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
// 设置切点的表达式---切点语言
cut.setExpression("execution( void cn.hncu.v2.Person.run() )"); // 写死的
// 3通知
Advice advice = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前面拦3.....");
Object res = invocation.proceed();// 放行
System.out.println("后面拦3.....");
return res;
}
};
// 4切面=切点+通知
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
// 5把切面加到代理工厂
factory.addAdvisor(advisor);
// //////从代理工厂中取出的bean都是被代理过的
Person p = (Person) factory.getObject();
p.run();
p.hello();
}
具体代码XML方式
具体调用还是和第一代技术一样
第三代技术进步在与将切面单独出来写成一个类,类中通过注解的方式将切点语言嵌入到切面,而通知呢,就直接在方法上面写,切面代码如下
package cn.hncu.v3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//切面= 切点 + 通知
//@Aspect = @Pointcut + (@Before | @After | Around | @AfterReturning | @AfterThrowing 等当中至少有1种)
@Aspect
public class MyAdvisor2 {
//用表达式字符串来代替切点
private final String CUT="execution( * cn..Cat.*(..) )";
@Before(CUT)
public void bf(){
System.out.println("前面拦拦......");
}
@Around(CUT)
public Object around(ProceedingJoinPoint p) throws Throwable{ //ProceedingJoinPoint 是 JoinPoint 的子类
System.out.println("前前前2拦拦.....:"+ p.getKind()+","+p.getSignature().getName()+","+p.getTarget() );
Object res = p.proceed(); //放行
System.out.println("后后后2拦拦....");
return res;
}
}
这样XML容器配置变得更为简单
利用基本标签的切面技术 把一个POJO做成切面,进步在与切面里面只要写具体的拦截方法,比如前面兰兰,环绕栏。不需要注解来解释哪个是前面栏,接下来再在XML配置文件中,具体通过方法名来指定怎么栏,就是通知
先是通知代码
package cn.hncu.v4;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
public class MyAdvisor2 {
public void bf(){
System.out.println("前面拦拦......");
}
//如果非要做周围通知,那么可以注入一个ProceedingJoinPoint参数
public Object around(ProceedingJoinPoint p) throws Throwable{ //ProceedingJoinPoint 是 JoinPoint 的子类
System.out.println("前前前2拦拦.....:"+ p.getKind()+","+p.getSignature().getName()+","+p.getTarget() );
Object res = p.proceed(); //放行
System.out.println("后后后2拦拦....");
return res;
}
}
配置文件代码
第一代技术很重要因为他是整个技术的底层原理,采用的是动态代理技术,想象一下就是把我们要代理的对象经过一个可以进行加工的容器中处理后就完成了代理,容器中需要知道怎么具体代理,代理那个方法;这就是我们的切面的两个构成要素切点和通知,当设置好了后直接去容器拿我们要的代理对象就好了;
第二代技术进步切点语言的进步,看起来很小的一点,其实作用很大,因为切点语言实现了从哪儿拦截,包括包名,返回值等等让这个切点变得强大可以从哪儿都可以切入,
. /*
* 切点语言:
* 1) 框架: execution( 切点语言表达式 )
* 2) 表达式格式: 返回类型 包名.[子包名.]类名.方法名(参数类型列表)
* 3) "."号是包名之间 或 包名与类名之间 或 类名与方法名 之间的间隔符
* 4) "…"在包路径位置代表的是任意深的目录,在参数类型列表中代表的是任意个数与类型的参数
* 5) "*"号 是操作系统中的通配符
*/
第三代·技术把切面从容器中分离了出来,单独设置切点和通知,在容器中仅仅是把要代理的对象加进去,再加上自动代理就好了。
第四代技术是利用基本标签的切面技术 把一个POJO做成切面,切面只写通知,写好了再配置到容器中去,切点语言直接嵌入在容器中写,
总的来说每一代技术的底层原理和思想都没有变只是开发方式变得越来越简单。