您的位置:首页 > 编程语言 > Java开发

spring之Aop面向切面

2015-02-09 10:49 477 查看

面向切面编程中的一些概念

1.1 代理模式

代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

A. 抽象主题角色

声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题

B. 代理主题(Proxy)角色:

代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

C. 真实主题角色

定义了代理角色所代表地真实对象

1.1.1 JDK动态代理

JDK的动态代理必须具备四个条件:

目标接口

目标类

拦截器

代理类

总结:1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。

2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。

3、利用JDKProxy方式必须有接口的存在。

4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。

1.1.2 CGLIB做代理

1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。

2、 用CGlib生成代理类是目标类的子类。

3、 用CGlib生成 代理类不需要接口

4、 用CGLib生成的代理类重写了父类的各个方法。

5、 拦截器中的intercept方法内容正好就是代理类中的方法体

spring有两种代理方式:

1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

优点:因为有接口,所以使系统更加松耦合

缺点:为每一个目标类创建接口

2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。

缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

1.1.3 Spring的动态代理

1、 拦截器必须实现MethodInterceptor接口

2、 在spring中的配置

总结:不管采用JDK动态代理生成代理类还是采用CGLIB生成动态代理类。目标类中的所有方法都被拦截下来。而在哪个方法里做比如权限的判断、安全性的检查等一系列工做必须在拦截器中作相应的判断。但是这样的编程形式给程序的编写带来了一定的麻烦。

1、 在拦截器中控制哪些方法将被做权限判断、安全性检查等是一件比较困难的事情。

A. 采取这样的配置目标类只能是一个,所以如果用这种方法做权限控制,得写很多代理,这样给代码的书写造成了困难。

B. 每一个类中的每一个方法如果都有不同的权限(实际的系统往往都是这样的),在拦截器中的判断代码书写会很困难。

2、 这样的代码也会导致硬编码,也就是说我们必须在拦截器中写一些权限判断等事情,会导致拦截器中代码量的增大,造成维护的麻烦。

1.2 AOP编程

1.2.1概念:

A. Aspect(切面)

比如说事务、权限等,与业务逻辑没有关系的部分

B. joinpoint(连接点)

目标类的目标方法。(由客户端在调用的时候决定)

C. Pointcut(切入点)

所谓切入点是指我们要对那些拦截的方法的定义.

被纳入spring aop中的目标类的方法。

D. Advice(通知)

所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

E. Target(目标对象):

代理的目标对象

F. Weaving(织入)

是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象

JDKProxy代理

SpringAop

目标对象

目标对象

拦截器类

切面

拦截器类中的方法

通知

被拦截到的目标类中方法的集合

切入点

在客户端调用的方法(目标类目标方法)

连接点

代理类

AOP代理

代理类的代理方法生成的过程

织入

通知根据拦截目标类中的目标方法的位置不一样可以分为:前置通知、后置通知、最终通知、环绕通知、异常通知

1.2.2 AOP实现的两种模式

1.2.2.1 xml形式

A. 前置通知

在spring配置文件中声明切面

在spring配置文件中声明目标类

定义切面、切入点、通知

注:见6.2.3.4

说明:

1、在切面类中,没有必要实现接口,但方法名称要与<aop:before method=”checkSecurity” 中的checkSecurity一样。

2、checkSecurity方法中通过JoinPoint参数可以获得目标类的目标方法名称、参数值等信息。

B. 后置通知

1、 没有特殊说明的地方和前置通知是一样的。

2、 在spring配置文件中

3、 在拦截器中的方法要和checkSecurity方法一样,有两个参数

JoinPoint point 可以获得目标方法和参数值

Object val 这里的名字要和returning=”val”中保持一致,指的是方法的返回值。

4、 returning=”val”时,通知里可以有返回参数,这个参数只能决定通知里能不能拿到方法的返回值,和客户端没有关系。

5、 在执行目标类的目标方法中遇到异常,则不执行后置通知。

C. 异常通知

1、 没有特殊说明的地方和前置通知一样

2、 在spring配置文件中

其中throwing指定了传递异常的参数名称

3、 在异常通知中(拦截器)中,必须是checkSecurity方法。方法中有两个参数

JoinPoint point 可以获得方法的名称、参数

Throwable ex 利用ex.getMessage()可以获得异常信息

D. 最终通知

1、 没有特殊说明,和前置通知的配置一样。

2、 在spring配置文件里:

说明:在最终通知中不受异常的影响。也就是说不论目标方法执行的过程中是否抛出异常,最终通知都将执行。

E. 环绕通知

1、 没有特殊说明和前置通知的配置保持一致。

2、 在spring文件中

3、 在环绕通知中,方法名称为checkSecurity。参数 类型 为ProceedingJoinPoint。

ProceedingJoinPoint的proceed方法相当于invoke方法,调用目标类的目标方法。ProceedingJoinPoint继承了JoinPoint类

4、 能在方法执行的前后加入额外的代码。

说明:

1.2.2.2Aop注解形式

A. 前置通知

注意:@Aspectj是按照类型匹配的。

B. 后置通知

C. 异常通知

D. 最终通知

E. 环绕通知

spring中使用aop的实例和配置文件

1、proxyBeanFactory:代理类创建bean工厂,之前的beanFactory就是一个容器,创建bean,管理bean而proxyBeanFactory就是一个创建代理对象的工厂,和其他javabean一样也有一些属性控制他的行为,下面列出常用的一些属性。

2、proxybeanFactory配置文件常用的属性:现在站在代理类的角度看的话可能更清晰


(1)target: 代理目标(被代理者),这个属性指定了你这个代理对象要代理的目标对象即被代理对象(委托者)

<beanid ="" class="">

<propertyname="target">

<ref bean="被代理的对象的id">

</property>

</bean>

//这样的话我们知道有这个被代理对象暴露出来了,我们可以直接定义在内部

<beanid="" class="">

<propertyname="target">

<beanclass="" />

</property>

</bean>

// 这样就不会被外部使用了,因为隐藏在了内部



(2)、proxyInterface: 这个属性指定了,从工厂中创建的bean要实现的接口

<beanid="" class="">

<propertyname="proxyInterface">

<value>impinterface</value>

</property>

</bean>

// 这样proxyBeanFactory就知道了创建的bean对象要实现impinterface 这个接口

(3)、interceptorName: interceptor(拦截机,妨碍着) 定义了一个应用到目标对象上的advisor或列表

// 提供多个接口来实现,使用list

<beanid="" class="">

<property name="interceptorName">

<list>

<value>advisor1</value>

<value>advisor2</value>

<value>serviceTarget</value>

// 指定了被代理对象,但是还是使用 target属性来指定

</list>

</property>

</bean>



总结: 通过上面这三个属性的配置,很清晰的看到就是一个代理模式的实现,其中的target就是指定了被代理的对象,proxyInterface就是代理类要实现的接口,而interceptorName就是一个通知,或已被安全监测等拦截器,通过这三个属性的配置就可以实现代理,也就是 spring中说的aop

实例:实现前置通知,后置通知,以及环绕通知的实现


(1)、interface 代理类要实现的接口(proxyInterface)

package com.inspur.imp;

/**

*@author WHD

*2015-2-3

*/

public interface IStudent {

public void addStu(String name);

public void addStuName(String name);

public void delStu();

}

(2)、目标类(target)

package com.inspur.imp;

/**

*@author WHD

*2015-2-3

*/

public class IStudentImp implements IStudent{

@Override

public void addStu(String name) {

System.out.println("委托类执行 开始!");

// TODO Auto-generated method stub

if("".equals(name)){

System.out.println("名称为空");

}

if("whd".equals(name)){

System.out.println(" name is"+name);

}

System.out.println("名称"+name);

}

@Override

public void addStuName(String name) {

// TODO Auto-generated method stub

System.out.println("add name:"+name);

}

@Override

public void delStu() {

// TODO Auto-generated method stub

System.out.println("delete stu");

}



}

(3)、切面(切面中只定义了前置通知)

package com.inspur.imp;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**

*@author WHD

*2015-2-3

*/

// 和业务无关的这个类就是切面

public class MethodBeforeImp implements MethodBeforeAdvice {

@Override

// 这个方法就是通知

public void before(Method arg0, Object[] arg1, Object arg2)

throws Throwable {

// 获取目标类的名称,方法名称以及方法中的参数

// 方法名

String name1=arg0.getName();

//参数值

String name2=arg1[0].toString();

//类名

Object obj=arg2.getClass();

String name3=obj.toString();

if("whd".equals(name2)){

System.out.println("参数值"+name2);

}

System.out.println("before method"+" 方法名称:"+name1+" 参数值:"+name2+" 目标类:"+name3);

}

}

(4)、切面(切面中只定义了后置通知)

package com.inspur.imp;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

/**

*@author WHD

*2015-2-3

*/

// 和业务无关的这个类就是切面

public class AfterAdvice implements AfterReturningAdvice {

@Override

// 这个方法就是通知

public void afterReturning(Object arg0, Method arg1, Object[] arg2,

Object arg3) throws Throwable {

// TODO Auto-generated method stub

//参数值

String name=arg2[0].toString();

System.out.println("后置通知"+name);

}

}

(5)、环绕通知

package com.inspur.imp;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

/**

*@author WHD

*2015-2-3

*/

//和业务无关的这个类是切面

public class CompareInterceptor implements MethodInterceptor {

@Override

// 这个方法就是通知

public Object invoke(MethodInvocation arg0) throws Throwable {

// TODO Auto-generated method stub

Object result=null;

String obj=arg0.getArguments().toString();

if("whd".equals(obj)){

result=arg0.proceed();



System.out.println("环绕方法");

}else{

System.out.println("环绕方法");

}



return result;



}

}

(6)、配置文件

<?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:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

<!-- 目标对象 -->

<bean id="studentImp" class="com.inspur.imp.IStudentImp">

</bean>

<!-- 切面-前置通知(拦截器) -->

<bean id="beforeAdvice" class="com.inspur.imp.MethodBeforeImp"/>

<!-- 切面-后置通知(拦截器) -->

<bean id="afterAdvice" class="com.inspur.imp.AfterAdvice"/>

<!--
切面-环绕通知(拦截器) -->

<bean id="compareInterceptor" class="com.inspur.imp.CompareInterceptor" />

<!-- 代理类 -->

<bean id="proxystudent" class="org.springframework.aop.framework.ProxyFactoryBean">

<!-- 代理类要实现的接口 -->

<property name="proxyInterfaces">

<value>com.inspur.imp.IStudent</value>

</property>

<!--切面(拦截器) -->

<property name="interceptorNames">

<list>

<value>beforeAdvice</value>

<value>afterAdvice</value>

<value>compareInterceptor</value>

</list>

</property>

<!-- 目标类 -->

<property name="target">

<ref bean="studentImp"/>

</property>

</bean>

</beans>

(7)、测试类

/**

*

*/

package com.test;

import java.util.List;

import java.util.Map;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.inspur.imp.IStudent;

import junit.framework.TestCase;

/**

*@author WHD

*2014-10-4

*/

public class TestDisk extends TestCase{

public void test(){

ApplicationContext act= new ClassPathXmlApplicationContext("ApplicationContext.xml");

IStudent stu= (IStudent)act.getBean("proxystudent");

stu.addStu("whd");

}

}

实例:对指定的方法实现前置通知,后置通知,其实在实际中我们也是只对某一类进行方法进行拦截的,也就是静态切入点



(1)、抽象接口

package com.inspur.dao;

/**

*@author WHD

*2015-2-4

*/

public interface shopping {

public String buySomething(String type);

public String buyAnything(String type);

public String sellSomething(String type);

public String sellAnything(String type);

}

(2)、目标类

package com.inspur.dao;

/**

*@author WHD

*2015-2-4

*/

public class Shoppingimp implements shopping{

@Override

public String buySomething(String type) {

// TODO Auto-generated method stub

System.out.println("bysomethind"+ type);

return null;

}

@Override

public String buyAnything(String type) {

// TODO Auto-generated method stub

System.out.println("buyAnything"+ type);

return null;

}

@Override

public String sellSomething(String type) {

// TODO Auto-generated method stub

System.out.println("sellSomething"+ type);

return null;

}

@Override

public String sellAnything(String type) {

// TODO Auto-generated method stub

System.out.println("sellAnything"+ type);

return null;

}

}

(3)、切面(切面只定义了前置通知)

package com.inspur.dao;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**

*@author WHD

*2015-2-4

*/

//这个和业务无关的类就是一个切面

public class MethodBefore implements MethodBeforeAdvice {

@Override

// 切面中的方法,就是一个通知---前置通知

public void before(Method arg0, Object[] arg1, Object arg2)

throws Throwable {

// TODO Auto-generated method stub

String type=arg1[0].toString();

System.out.println("前置拦截器"+type);

}

}

(4)、配置文件

<?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:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">



<!--目标类-->

<bean id="shoppingImpl" class="com.inspur.dao.Shoppingimp">



</bean>

<!-- 切面(拦截器)--前置通知-->

<bean id="shoppingAdvise" class="com.inspur.dao.MethodBefore">

</bean>

<!-- 静态切入点 这里的class 说明是使用方法名称匹配来实现的,切入点就是一个规则 -->

<bean id="shoppingPointCutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">

<!-- 这个属性说明了对哪些方法进行拦截,也就是定义了拦截规则 -->

<property name="mappedNames">

<list>

<value>sellSomething</value>

<value>sellAnything</value>

</list>

</property>

<!-- 织入切面的通知 -->

<property name="advice">

<ref bean ="shoppingAdvise"/>

</property>

</bean>

<!-- 代理类 这个和上面一个不同,这里的interceptorNames这个属性的值为上面定义的切入点的id -->

<bean id="StaticAdvisorTest" class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="proxyInterfaces">

<value>com.inspur.dao.shopping</value>

</property>

<property name="interceptorNames">

<list>

<value>shoppingPointCutAdvisor</value>

</list>

</property>

<property name="target">

<ref bean="shoppingImpl"/>

</property>

</bean>



</beans>

(5)、测试类

/**

*

*/

package com.test;

import java.util.List;

import java.util.Map;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.inspur.dao.shopping;

import junit.framework.TestCase;

/**

*@author WHD

*2014-10-4

*/

public class TestDisk extends TestCase{

public void testStatic(){

ApplicationContext act= new ClassPathXmlApplicationContext("ApplicationContext.xml");

shopping stu= (shopping)act.getBean("StaticAdvisorTest");

stu.buyAnything("to buy antthing");

stu.sellAnything("want sell angthing");

stu.sellSomething("sellsomething");

}

}

动态切入点

(1)、接口

package com.inspur.service;

/**

*@author WHD

*2015-2-4

*/

public interface Implement {

void test();

}

(2)、目标类

package com.inspur.service;

/**

*@author WHD

*2015-2-4

*/

public class Target implements Implement{

@Override

public void test() {

// TODO Auto-generated method stub

System.out.println("目标类");

}

}

(3)、切面的前置通知

package com.inspur.service;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

/**

*@author WHD

*2015-2-4

*/

public class Before implements MethodBeforeAdvice {

@Override

public void before(Method arg0, Object[] arg1, Object arg2)

throws Throwable {

// TODO Auto-generated method stub

System.out.println("前置通知");

}

}

(4)、动态切入点配置类,只有通过这个类调用目标类的方法拦截器才起作用

package com.inspur.service;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

/**

*@author WHD

*2015-2-4

*/

// 当一个类实现了这个接口(ApplicationContextAware)之后,这个类就可以方便获得ApplicationContext中的所有bean。

//换句话说,就是这个类可以直接获取spring配置文件中,所有有引用到的bean对象。

public class Argument implements ApplicationContextAware {

// 代理类型

private Implement impl;

@Override

public void setApplicationContext(ApplicationContext context)

throws BeansException {

// TODO Auto-generated method stub

impl = (Implement)context.getBean( "aop");

}

public void test() {

System.out.println( "Argument.test()");

impl.test();

}



public void test2(Implement i) {

i.test();

}

}

(5)、配置文件

<?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:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">





<!-- 动态切入点 -->

<bean id="arg" class="com.inspur.service.Argument" />

<!-- 这个类中配置切入点,也就是调用这个的构造函数中的那个类的方法拦截器才会起作用 -->

<bean id="dponintcut" class="org.springframework.aop.support.ControlFlowPointcut">

<constructor-arg>

<value>com.inspur.service.Argument</value>

</constructor-arg>

</bean>

<!-- 和之前一样 定义通知(拦截器),不同的是这里是给属性赋值-->

<bean id="advice" class="org.springframework.aop.support.DefaultPointcutAdvisor">

<property name="advice">

<bean class="com.inspur.service.Before" />

</property>

<property name="pointcut">

<ref bean="dponintcut"/>

</property>

</bean>



<!-- 代理类 -->

<bean id="aop" class="org.springframework.aop.framework.ProxyFactoryBean">

<!-- 要实现的接口-->

<property name="proxyInterfaces">

<list>

<value>com.inspur.service.Implement</value>

</list>

</property>

<!-- 通知-->

<property name="interceptorNames">

<list>

<value>advice</value>

</list>

</property>

<!-- 目标类 -->

<property name="target">

<bean class="com.inspur.service.Target" />

</property>

</bean>



</beans>

(6)、测试类

/**

*

*/

package com.test;

import java.util.List;

import java.util.Map;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.inspur.service.Argument;

import com.inspur.service.Implement;

import junit.framework.TestCase;

public void test(){

ApplicationContext context = new ClassPathXmlApplicationContext( "ApplicationContext.xml");

// 获取动态切入点类

Argument arg = (Argument)context.getBean( "arg");

// 对于这个调用前置通起作用

arg.test();

System.out.println( "----------------");

// 代理类

Implement impl = (Implement)context.getBean( "aop");

// 对于这个调用不起作用

impl.test();

System.out.println( "----------------");

// 对于这个调用前置通起作用

arg.test2( impl);



}



}

测试结果:



(7)、从这个测试结果我们看到,通过Argument 类调用的方法都调用前置通知,而没有通过他的则不会调用前置通知,所以这个Argument 就是一个切入点。

对有些内容还是有点模糊,如果有写错的希望各位指正,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: