面向切面编程中的一些概念
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 就是一个切入点。
对有些内容还是有点模糊,如果有写错的希望各位指正,谢谢!