Spring(一):AOP

包括:
一. AOP 概念
二. AOP 实现方式
     2.1 利用 Proxy 实现 AOP功能
     2.2 利用 CGLib 实现 AOP功能
     2.3 利用 Spring 注解方式 实现 AOP功能
     2.4 利用 Spring XML 文件配置方式实现 AOP功能


一. AOP概念
       AOP也就是面向切面编程。利用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();  
}  


2. 创建Student类:
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;  
    }  
}  

3. 创建代理工厂类:
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;  
    }  
}  

解释:

  1. 首先调用代理工厂的createStudentProxy(Object stu)创建StudentBean类的代理类.
  2. 在该方法内,调用Proxy.newProxyInstance()方法创建代理对象。第一个参数是目标对象的类加载器,第二个参数是目标对象实现的接口,第三个参数传入一个InvocationHandler实例,该参数和回调有关系。
  3. 每当调用目标对象的方法的时候,就会回调该InvocationHandler实例的方法,也就是public Object invoke()方法,我们就可以把限制的条件放在这里,条件符合的时候,就可以调用method.invoke()方法真正的调用目标对象的方法,否则,则可以在这里过滤掉不符合条件的调用。

Proxy实现AOP功能总结:

  1. 目标对象必须实现接口。
  2. 调用Proxy.newProxyInstance()方法,返回创建的代理对象。
  3. 由于该方法需要一个实现了InvocationHandler接口的对象,所以我们还要重写该接口的invoke()方法。
  4. 我们的限制条件就可以放在这个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;  
    }  
}  


3. 创建CGLib代理类:
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;  
    }  
}  

        在这一步中,我们使用一个Enhancer类来创建代理对象,不再使用Proxy。使用Enhancer类,需要为其实例指定一个父类,也就是我们 的目标对象。这样,我们新创建出来的对象就是目标对象的子类,有目标对象的一样。除此之外,还要指定一个回调函数,这个函数就和Proxy的 invoke()类似。

        总体来说,使用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();  
    }  
}  



2.3 利用spring注解方式实现AOP功能

前提:介绍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>  

3.创建目标对象类:
public class Student {  
    public String print(String name){  
        System.out.println("print() method:" + name);  
        return "hello";  
    }  
}  

4.创建切面:
@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;  
    }  
}  

解释:

  1. 声明该类是切面,在类前使用@Aspect
  2. 声明切入点:
    @Pointcut("execution(* com.springaop.test.Student.print(..))")
    
    // @Pointcut("execution(*com.springaop.test.Student.*(..))")

  3. 两种方式都可以。其中第一个*表示返回值可以为任意类型,com.springaop.test.Student.print表示指定的方法。com.springaop.test.Student.*表示该类中的任意方法。(..)表示任意参数。
  4. 前置通知:@Before("printMethod()"),里面的"printMethod()"即之前定义的切入点方法。
  5. 后置通知:@AfterReturning(pointcut="printMethod()",returning="flag")可以得到方法的返回值,放在flag中,flag要和后置通知的方法参数对应。
  6. 最终通知:@After("printMethod()")
  7. 环绕通知:@Around("printMethod() && args(name)"),可以传入参数。一般我们的权限条件就在这里写。如下环绕通知:
    public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{}

    必须传入一个ProceedingJoinPoint类型的参数,符合条件则可以直接调用pjp.proceed();就可以调 用目标对象的方法。如果不符合条件,则不调用pjp.proceed();
  8. 把切面配置在XMl文件中:如上配置。
  9. 在主函数中,创建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)/


总结:
  1. AOP概念:面向切面编程;能够在方法的前后执行某些代码,达到控制的目的。
  2. 实现方法有几种。1. Proxy 类方式(需要目标对象实现某一接口。目的就是 代理类也实现该接口,使得代理类能够完全代理该对象) 2. CGlib 方式(此时的代理方式为代理类继承代理对象,以此达到代理类和代理对象一致的目的) 3. Spring 注解方式 4. Spring XML 文件配置的方式。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值