一. AOP 概念
二. AOP 实现方式
2.1 利用 Proxy 实现 AOP功能
2.2 利用 CGLib 实现 AOP功能
2.3 利用 Spring 注解方式 实现 AOP功能
2.4 利用 Spring XML 文件配置方式实现 AOP功能
一. AOP概念
- 对函数调用进行日志记录。
- 监控部分函数,如果抛出异常那么可以以短信或者邮件通知别人。
- 监控重要函数的运行时间。
二. AOP实现方式
2.1 用Proxy类实现AOP功能
AOP大都可以用在权限系统中,那么利用Proxy类实现主函数调用Student类的print()方法,打印出”hello world!”。若主函数创建的Student实例有名字,则可以打印,没有名字,则不可以打印。
一般来说,我们可以直接在print()方法中加入if 语句来进行判断,但是这样一个是代码量大,一个是不灵活,况且,假如判断条件需要修改的话,还得找回这个类,不方便。对于采用Proxy类方法,主函数 -->代理-->目标对象的方法。这样,以后不管是修改判断条件,还是查找等,可以直接在代理类中进行处理。
对于Proxy类有一个使用前提,就是目标对象必须要实现接口。否则,不能使用这个方法。
过程如下:
1. 创建Student类的接口:
public interface StudentInterface {
public void print();
}
public class StudentBean implements StudentInterface{
private String name;
public StudentBean(){}
public StudentBean(String name){this.name = name;}
public void print(){
System.out.println("Hello World!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ProxyFactory implements InvocationHandler {
private Object stu;
public Object createStudentProxy(Object stu){
this.stu = stu;
return Proxy.newProxyInstance(stu.getClass().getClassLoader(),
stu.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
StudentBean s = (StudentBean)stu;
Object object = null;
if(s.getName() != null)
object = method.invoke(stu, args);
else
System.out.println("名字为空,代理类已经拦截!");
return object;
}
}
解释:
- 首先调用代理工厂的createStudentProxy(Object stu)创建StudentBean类的代理类.
- 在该方法内,调用Proxy.newProxyInstance()方法创建代理对象。第一个参数是目标对象的类加载器,第二个参数是目标对象实现的接口,第三个参数传入一个InvocationHandler实例,该参数和回调有关系。
- 每当调用目标对象的方法的时候,就会回调该InvocationHandler实例的方法,也就是public Object invoke()方法,我们就可以把限制的条件放在这里,条件符合的时候,就可以调用method.invoke()方法真正的调用目标对象的方法,否则,则可以在这里过滤掉不符合条件的调用。
Proxy实现AOP功能总结:
- 目标对象必须实现接口。
- 调用Proxy.newProxyInstance()方法,返回创建的代理对象。
- 由于该方法需要一个实现了InvocationHandler接口的对象,所以我们还要重写该接口的invoke()方法。
- 我们的限制条件就可以放在这个invoke()方法中,当满足条件,就调用method.invoke()真正的调用目标对象的方法,否则,不做任何事情,直接过滤。
2.2 利用CGlib实现AOP功能
还是实现主函数调用Student类的print()方法,打印出”hello world!”。若主函数创建的Student实例有名字,则可以打印,没有名字,则不可以打印。但是这个时候Student类不再实现接口,不像利用 Proxy那样的实现接口。这时候使用第三方框架CGLib。
具体实现:
1. 引入CGlib的jar包。
2. 创建StudentBean类:
public class StudentBean {
private String name;
public StudentBean(){}
public StudentBean(String name){this.name = name;}
public void print(){
System.out.println(name +" hello world!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CGlibProxyFactory implements MethodInterceptor{
private Object object;
public Object createStudent(Object object){
this.object = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
StudentBean stu = (StudentBean)object;
Object result = null;
if(stu.getName() != null)
result = methodProxy.invoke(object, args);
else
System.out.println("方法已经被拦截...");
return result;
}
}
总体来说,使用CGlib的方法和使用Proxy的方法差不多,只是Proxy创建出来的代理对象和目标对象都实现了同一个接口。而CGlib的方法则是直接继承了目标对象。
4. 创建主类
public class Main {
public static void main(String[] args) {
StudentBean stu1 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean()));
StudentBean stu2 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean("whc")));
stu1.print();
stu2.print();
}
}
前提:介绍spring注解方法的话先介绍一些概念:
在上面的例子中,我们可以在proxy方式中重写invoke方法,也可以在cglib方式中重写intercept方法,并且在 里面调用invoke方法来实际调用目标方法。
那么我们可以在这个invoke()方法的前前后后加入我们的一些方法,比如在调用实际方法前,调用一个我 们自定义的方法,在调用实际方法后,也调用我们的一个自定义方法,那么这些就分别叫做前置通知,后置通知,除此之外,还有例外通知,最终通知,整个这个重 写方法,就叫做环绕通知。整个这个处理类,就叫做切面。(Ps:不太清楚的先搞清楚AOP的一些概念)
1. 引入spring的jar包:
有aspectjweaver-1.6.11.M2.jar,cglib.nodep-2.1.3.jar,spring.jar,aspectjrt.jar,common-annotations.jar,commons-logging-1.1.1.jar包。2. 配置aop命名空间:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<aop:aspectj-autoproxy/>
<bean id = "stuInterceptor" class = "com.springaop.test.StuInterceptor"></bean>
<bean id = "stu" class = "com.springaop.test.Student"></bean>
</beans>
public class Student {
public String print(String name){
System.out.println("print() method:" + name);
return "hello";
}
}
@Aspect
public class StuInterceptor {
/**
* 打印方法AOP
*/
@Pointcut("execution(* com.springaop.test.Student.print(..))")
// @Pointcut("execution(* com.springaop.test.Student.*(..))")
public void printMethod(){}
@Before("printMethod()")
public void printBeforeAdvice(){
System.out.println("printBeforeAdvice()!");
}
@AfterReturning(pointcut="printMethod()",returning="flag")
public void printAfterAdvice(String flag){
System.out.println("printAfterAdvice()! " + flag);
}
@After("printMethod()")
public void finallyAdvice(){
System.out.println("finallyAdvice()!");
}
@Around("printMethod() && args(name)")
public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{
Object result = null;
if(name.equals("whc"))
pjp.proceed();
else
System.out.println("print()方法以及被拦截...");
return result;
}
}
解释:
- 声明该类是切面,在类前使用@Aspect
- 声明切入点:
@Pointcut("execution(* com.springaop.test.Student.print(..))") // @Pointcut("execution(*com.springaop.test.Student.*(..))")
- 两种方式都可以。其中第一个*表示返回值可以为任意类型,com.springaop.test.Student.print表示指定的方法。com.springaop.test.Student.*表示该类中的任意方法。(..)表示任意参数。
- 前置通知:@Before("printMethod()"),里面的"printMethod()"即之前定义的切入点方法。
- 后置通知:@AfterReturning(pointcut="printMethod()",returning="flag")可以得到方法的返回值,放在flag中,flag要和后置通知的方法参数对应。
- 最终通知:@After("printMethod()")
- 环绕通知:@Around("printMethod() && args(name)"),可以传入参数。一般我们的权限条件就在这里写。如下环绕通知:
public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{}
必须传入一个ProceedingJoinPoint类型的参数,符合条件则可以直接调用pjp.proceed();就可以调 用目标对象的方法。如果不符合条件,则不调用pjp.proceed(); - 把切面配置在XMl文件中:如上配置。
- 在主函数中,创建Spring容器,即通过getBean方式得到实例对象,调用其方法即可。
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Student stu = (Student)ctx.getBean("stu"); stu.print("whc"); } }
输出结果:
printBeforeAdvice()!
print() method:whc
printAfterAdvice()! hello
finallyAdvice()!
2.4 利用springXML文件配置方式实现AOP功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<aop:aspectj-autoproxy/>
<bean id = "stu" class = "com.springaop.test.Student"></bean>
<bean id = "interceptor" class = "com.springaop.test.StuInterceptor"></bean>
<aop:config>
<aop:aspect id = "stuInterceptor" ref = "interceptor">
<aop:pointcut id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>
<aop:before pointcut-ref="mycut" method="printBeforeAdvice" />
<aop:after-returning pointcut-ref="mycut" method="printAfterAdvice"/>
<aop:after pointcut-ref="mycut" method="finallyAdvice"/>
<aop:around pointcut="mycut" method="printAroundAdvice"/>
</aop:aspect>
</aop:config>
</beans>
- 首先可以在切面的类把注释去掉,因为我们不再使用注释的方法。
- 然后再上面的配置文件中,我们定义了<aop:config>,进一步在里面配置了切面,切入点,前置,后置通知等等一系列的东西。
这里有一个问题就是如何传入参数的问题,比如我要为环绕方法传入一个name参数。那么,该如何在这里配置?一个解决办法就是在上述配置中不要如下代码:
<aop:pointcut id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>
<aop:before pointcut="execution(* com.springaop.test.Student.print(..))" method="printBeforeAdvice" />
<aop:around pointcut="execution(* com.springaop.test.Student.print(..)) and args(name)" method="printAroundAdvice" arg-names(name)/
总结:
- AOP概念:面向切面编程;能够在方法的前后执行某些代码,达到控制的目的。
- 实现方法有几种。1. Proxy 类方式(需要目标对象实现某一接口。目的就是 代理类也实现该接口,使得代理类能够完全代理该对象) 2. CGlib 方式(此时的代理方式为代理类继承代理对象,以此达到代理类和代理对象一致的目的) 3. Spring 注解方式 4. Spring XML 文件配置的方式。