Spring学习笔记IOC与AOP实例
Spring框架核心由两部分组成:
第一部分是反向控制(IOC),也叫依赖注入(DI);
控制反转(依赖注入)的主要内容是指:只描述程序中对象的被创建方式但不显示的创建对象。在以XML语言描述的配置文件中,声明web系统中哪个组件需要哪一种服务时,不是在程序中让对象和服务直接连接,具体负责连接工作的是Web容器,Spring中就是Ioc容器。
优点:因为把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以了,这样我们甚至可以实现对象的热插拔(有点象USB接口和SCIS硬盘了)。
缺点:(1)生成一个对象的步骤变复杂了(其实上操作上还是挺简单的),对于不习惯这种方式的人,会觉得有些别扭和不直观。(2)对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。(3)缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了,这似乎是所有XML方式的缺憾所在。
开发中具体实现方式:
SSH深度历险(八) 剖析SSH核心原理+Spring依赖注入的三种方式
1.JavaBean的Setter方法注入
如图:
接口 Logic.java
package DI; /** * 类Logic.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:02:35 */ public interface Logic { public String getName(); }
接口实现类 LogicImpl.java
package DI; /** * 类Logiclmpl.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:03:53 */ public class Logiclmpl implements Logic { public String getName() { return "xiong jinlian"; } }
一个处理类 LoginAction.java(LoginAction.java 会根据使用不同的注入方法而稍有不同)
package DI; /** * 类LoginAction.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:07:10 */ public class LoginAction { private Logic logic; /** * @return the logic */ public Logic getLogic() { return logic; } /** * @param logic the logic to set */ public void setLogic(Logic logic) { this.logic = logic; } public void execute() { String name = logic.getName(); System.out.println("My name is " + name); } }
还有一个测试类 TestMain.java(客户端测试类)
package DI; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * 类TestMain.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:12:19 */ public class TestMain { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method st ApplicationContext ac = new ClassPathXmlApplicationContext("appContext.xml"); LoginAction la = (LoginAction) ac.getBean("loginAction"); la.execute(); } }
定义了一个Logic 类型的变量 logic, 在LoginAction并没有对logic 进行实例化,而只有他对应的setter/getter方法,因为我们这里使用的是Spring的依赖注入的方式
appContext.xml
<?xml version="1.0"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--配置spring的bean--> <bean id="logiclmpl" class="DI.Logiclmpl"></bean> <bean id="loginAction" class="DI.LoginAction"> <property name="logic" ref="logiclmpl"></property> </bean> </beans>
运行截图:
2.构造方法注入
构造方法注入,就是我们依靠LoginAction的构造方法来达到DI的目的,如下所示,添加了一个LoginAction的构造方法:
LoginAction.java
package DI; /** * 类LoginAction.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:07:10 */ public class LoginAction { private Logic logic; public LoginAction(Logic logic){ this.logic = logic; } public void execute() { String name = logic.getName(); System.out.println("My name is " + name + "构造方法注入"); } }
appContext.xml
<?xml version="1.0"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--配置spring的bean--> <bean id="logiclmpl" class="DI.Logiclmpl"></bean> <bean id="loginAction" class="DI.LoginAction"> <constructor-arg index="0" ref="logiclmpl"></constructor-arg> </bean> </beans>
使用constructor-arg来进行配置, index属性是用来表示构造方法中参数的顺序的,如果有多个参数,则按照顺序,从 0,1...来配置。其中需要注意一点:构造函数有多个参数的话,如:参数1,参数2,而参数2依赖于参数1,这种情况则要注意构造函数的顺序,必须将参数1放在参数2之前。
我们现在可以运行testMain.java了,结果跟使用Setter方法注入完全一样.
3.接口注入
不常用到的接口注入,还是以LogicAction为例,我们对他进行了修改,如下所示:
LogicAction.java
package DI; /** * 类LoginAction.java的实现描述:TODO 类实现描述 * @author jinlian.xjl 2015年7月6日 下午2:07:10 */ public class LoginAction { private Logic logic; public void execute() { try { // forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象。 Class clazz = Class.forName("DI.Logiclmpl"); // newInstance() 创建此 Class 对象所表示的类的一个新实例。 logic = (Logic) clazz.newInstance(); String name = logic.getName(); System.out.println("My name is " + name + "测试接口注入"); } catch (Exception e) { e.printStackTrace(); } } }
appContext.xml
<?xml version="1.0"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!--配置spring的bean--> <bean id="logiclmpl" class="DI.Logiclmpl"></bean> <bean id="loginAction" class="DI.LoginAction"></bean> </beans>
运行:
小结:
对于Spring的依赖注入,最重要的就是理解它们,无非就是让容器来给我们实例化那些类,我们要做的就是给容器提供这个接口,这个接口就我们的set方法或者构造函数了。
第二部分面向方面的编程(AOP).
1.Aop简介
AOP(Aspect Oriented Programming):面向切面编程(面向方面编程),通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
用途:
AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理(日志记录,性能统计,安全控制,权限管理,事务处理,异常处理,资源池管理)等
AOP 的实现原理:
如图:AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
Spring 中对 AOP 的支持:
Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。Spring 默认使用 Java 动态代理来创建 AOP 代理, 这样就可以为任何接口实例创建代理了。当需要代理的类不是代理接口的时候, Spring 自动会切换为使用 CGLIB 代理,也可强制使用 CGLIB。
AOP 编程需要程序员参与的只有三个部分:
- 定义普通业务组件。
- 定义切入点,一个切入点可能横切多个业务组件。
- 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。
所以进行 AOP 编程的关键就是定义切入点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法。
Spring 中 AOP 的实现:
Spring 有如下两种选择来定义切入点和增强处理。
- 基于 Annotation注解的“零配置”方式:使用@Aspect、@Pointcut等 Annotation 来标注切入点和增强处理。
- 基于 XML 配置文件的管理方式:使用 Spring 配置文件来定义切入点和增强点。
1). 切面(Aspect)
官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如:AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
2). 连接点(Joinpoint)
程序执行过程中的某一行为,例如,AServiceImpl.barA()的调用或者BServiceImpl.barB(String _msg, int _type)抛出异常等行为。
3). 通知(Advice)
“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如TestAspect。Advice共有如下5种类型:
A 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。xml中在<aop:aspect>里面使用<aop:before>元素进行声明;例如,TestAspect中的doBefore方法。注解中使用@Before声明;例如,TestAnnotationAspect中的doBefore方法。
B 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。xml中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,TestAspect中的doAfter方法,所以AOPTest中调用BServiceImpl.barB抛出异常时,doAfter方法仍然执行。注解中使用@After声明。
C 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。xml中在<aop:aspect>里面使用<after-returning>元素进行声明。注解中使用@AfterReturning声明;
D 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。xml中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,TestAspect中的doAround方法。注解中使用@Around声明。
E 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。xml中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,TestAspect中的doThrowing方法。注解中使用@AfterThrowing声明。
通知执行顺序:前置通知→环绕通知连接点之前→连接点执行→环绕通知连接点之后→返回通知→后置通知→(如果发生异常)异常通知→后通知
4). 切入点(Pointcut)
匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定。
● 切入点表达式
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;注意this中使用的表达式必须是完整类名,不支持通配符;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是完整类名,不支持通配符;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;参数类型列表中的参数必须是完整类名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;
@within:用于匹配所以持有指定注解类型内的方法;注解类型也必须是完整类名;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是完整类名;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是完整类名;
@annotation:用于匹配当前执行方法持有指定注解的方法;注解类型也必须是完整类名;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
reference pointcut:表示引用其他命名切入点,只有注解风格支持,XML风格不支持。
● 匹配语法
- * 匹配任何数量字符;
- .. 匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
- + 匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
例如:
java.lang.String 匹配String类型;
java.*.String 匹配java包下的任何“一级子包”下的String类型;如匹配java.lang.String,但不匹配java.lang.ss.String。
java..* 匹配java包及任何子包下的任何类型;如匹配java.lang.String、java.lang.annotation.Annotation。
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
java.lang.Number+ 匹配java.lang包下的任何Number的自类型;如匹配java.lang.Integer,也匹配java.math.BigInteger。
● 匹配种类
A 类
- 注解 类名
注解:可选,类上持有的注解,如@Deprecated;
类名:必填,任何类的完整名称。
B 方法
- 注解 修饰符 返回值类型 类名 方法名(参数列表) 异常列表
注解:可选,方法上持有的注解,如@Deprecated;
修饰符:可选,如public、protected;
返回值类型:必填,可以是任何类型模式;“*”表示所有类型;
类名:可选,任何类的完整名称;
方法名:必填,可以使用“*”进行模式匹配;
参数列表:“()”表示方法没有任何参数;“(..)”表示匹配接受任意个参数的方法,“(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法;
异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。
C Bean
可以使用Bean的id或name进行匹配,并且可使用通配符“*”。
● 匹配逻辑运算
可以使用且(&&)、或(||)、非(!)来组合切入点表达式。由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!。
● 切入点表达式示例
A execution
模式 | 描述 |
public * *(..) | 任何公共方法的执行 |
* cn.javass..IPointcutService.*() | cn.javass包及所有子包下IPointcutService接口中的任何无参方法 |
* cn.javass..*.*(..) | cn.javass包及所有子包下任何类的任何方法 |
* cn.javass..IPointcutService.*(*) | cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法 |
* (!cn.javass..IPointcutService+).*(..) | 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法 |
* cn.javass..IPointcutService+.*() | cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法 |
* cn.javass..IPointcut*.test*(java.util.Date) | cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
* cn.javass..IPointcut*.test*(..) throws IllegalArgumentException, ArrayIndexOutOfBoundsException | cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常 |
* (cn.javass..IPointcutService+&& java.io.Serializable+).*(..) | 任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法 |
@java.lang.Deprecated * *(..) | 任何持有@java.lang.Deprecated注解的方法 |
@java.lang.Deprecated @cn.javass..Secure * *(..) | 任何持有@java.lang.Deprecated和@cn.javass..Secure注解的方法 |
@(java.lang.Deprecated || cn.javass..Secure) * *(..) | 任何持有@java.lang.Deprecated或@ cn.javass..Secure注解的方法 |
(@cn.javass..Secure *) *(..) | 任何返回值类型持有@cn.javass..Secure的方法 |
* (@cn.javass..Secure *).*(..) | 任何定义方法的类型持有@cn.javass..Secure的方法 |
* *(@cn.javass..Secure (*) , @cn.javass..Secure (*)) | 任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,如public void test(@Secure String str1,@Secure String str1); |
* *((@ cn.javass..Secure *))或* *(@ cn.javass..Secure *) | 任何带有一个参数的方法,且该参数类型持有@ cn.javass..Secure;如public void test(Model model);且Model类上持有@Secure注解 |
* *(@cn.javass..Secure (@cn.javass..Secure *) ,@ cn.javass..Secure (@cn.javass..Secure *)) | 任何带有两个参数的方法,且这两个参数都被@ cn.javass..Secure标记了;且这两个参数的类型上都持有@ cn.javass..Secure; |
* *(java.util.Map<cn.javass..Model, cn.javass..Model>, ..) | 任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass..Model, cn.javass..Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(java.util.HashMap<cn.javass..Model,cn.javass..Model>, ..)”进行匹配;而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配 |
* *(java.util.Collection<@cn.javass..Secure *>) | 任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass..Secure注解;如public void test(Collection<Model> collection);Model类型上持有@cn.javass..Secure |
B within
模式 | 描述 |
within(cn.javass..*) | cn.javass包及子包下的任何方法执行 |
within(cn.javass..IPointcutService+) | cn.javass包或所有子包下IPointcutService类型及子类型的任何方法 |
within(@cn.javass..Secure *) | 持有cn.javass..Secure注解的任何类型的任何方法必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
C this
模式 | 描述 |
this(cn.javass.spring.chapter6.service.IPointcutService) | 当前AOP对象实现了 IPointcutService接口的任何方法 |
this(cn.javass.spring.chapter6.service.IIntroductionService) |
当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口 |
D target
模式 | 描述 |
target(cn.javass.spring.chapter6.service.IPointcutService) | 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法 |
target(cn.javass.spring.chapter6.service.IIntroductionService) | 当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口 |
模式 | 描述 |
args (java.io.Serializable,..) | 任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的 |
F @within
模式 | 描述 |
@within cn.javass.spring.chapter6.Secure) | 任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
G @target
模式 | 描述 |
@target (cn.javass.spring.chapter6.Secure) | 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
H @args
模式 | 描述 |
@args (cn.javass.spring.chapter6.Secure) | 任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符; |
I @annotation
模式 | 描述 |
@annotation(cn.javass.spring.chapter6.Secure ) | 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配 |
J bean
模式 | 描述 |
bean(*Service) |
匹配所有以Service命名(id或name)结尾的Bean |
K reference pointcut
- @Pointcut(value="bean(*Service)")
- private void pointcut1(){}
- @Pointcut(value="@args(cn.javass.spring.chapter6.Secure)")
- private void pointcut2(){}
- @Pointcut(value="pointcut1()&&pointcut2()")
- private void pointcut3(){}
● 通知方法参数注入
在Spring AOP中,除了execution和bean指示符不能传递参数给通知方法,其他指示符都可以将匹配的相应参数或对象自动传递给通知方法。例如:
- @Before(value="execution(* test(*)) && args(param)", argNames="param")
- public void before1(String param) {
- System.out.println("===param:" + param);
- }
首先execution(* test(*))匹配任何方法名为test,且有一个任何类型的参数;
args(param)将首先查找通知方法上同名的参数,并在方法执行时(运行时)匹配传入的参数是使用该同名参数类型,即java.lang.String;如果匹配将把该被通知参数传递给通知方法上同名参数。
其他指示符(除了execution和bean指示符)都可以使用这种方式进行参数绑定。
● 综合示例
- @Before(value="args(param) && target(bean) && @annotation(secure)", argNames="jp,param,bean,secure")
- public void before5(JoinPoint jp, String param,
- IPointcutService pointcutService, Secure secure) {
- ……
- }
除了上边介绍的普通方式,也可以对使用命名切入点自动获取参数:
- @Pointcut(value="args(param)", argNames="param")
- private void pointcut1(String param){}
- @Pointcut(value="@annotation(secure)", argNames="secure")
- private void pointcut2(Secure secure){}
- @Before(value = "pointcut1(param) && pointcut2(secure)", argNames="param, secure")
- public void before6(JoinPoint jp, String param, Secure secure) {
- ……
- }
5). 目标对象(Target Object)
被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
6). AOP代理(AOP Proxy)
在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如:AServiceImpl;反之,采用CGLIB代理,例如:BServiceImpl。强制使用CGLIB代理需要将 <aop:config>的 proxy-target-class属性设为true。
XML方式实例:
文件目录路径如下:
切面类TestAspect.java:
package AOP; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class TestAspect { public void doBefore(JoinPoint jp) { System.out.println("Before :" + jp.getTarget().getClass().getName() + "……" + jp.getSignature().getName()); } public void doAfter(JoinPoint jp) { System.out.println("After :" + jp.getTarget().getClass().getName() + "……" + jp.getSignature().getName()); } public Object doAround(ProceedingJoinPoint pjp) throws Throwable { long time = System.currentTimeMillis(); Object retVal = pjp.proceed(); time = System.currentTimeMillis() - time; System.out.println("Around : process time:" + "……" + time + " ms"); return retVal; } public void doThrowing(JoinPoint jp, Throwable ex) { System.out.println("Throwing :" + jp.getTarget().getClass().getName() + "……" + jp.getSignature().getName()); System.out.println("Throwing : " + ex.getMessage()); } }
目标对象类:
AServiceImpl.java实现接口类AService.java(实现了接口,使用jdk动态代理方式)
package AOP; public interface AService { public void barA(); public void fooA(String _msg); public void mayThrow(int _type); }
package AOP; //jdk动态代理方式 public class AServiceImpl implements AService { public void barA() { System.out.println("AServiceImpl.barA()"); } public void fooA(String _msg) { System.out.println("AServiceImpl.fooA(_msg:)" + _msg + ")"); } public void mayThrow(int _type) { if (_type == 1) { throw new IllegalArgumentException("这是AServiceImpl异常测试"); } } }
目标对象类:
BServiceImpl.java(并未实现任何借口,默认cglib方式)
package AOP; //cglib public class BServiceImpl { public void barB(String _msg, int _type) { System.out.println("BServiceImpl.barB(msg:" + _msg + ")"); if (_type == 1) { throw new IllegalArgumentException("这是BServiceImpl异常测试"); } } public void fooB() { System.out.println("BServiceImpl.fooB()"); } }
appContext.xml文件:Spring配置文件
<?xml version="1.0"?> <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" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <!-- <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> --> <!--配置spring的bean--> <bean id="testAspect" class="AOP.TestAspect"></bean> <bean id="aServiceImpl" class="AOP.AServiceImpl"></bean> <!-- <bean id="bServiceImpl" class="AOP.BServiceImpl"></bean> --> <aop:config> <aop:aspect id="asTestAspect" ref="testAspect"> <aop:pointcut id="pcService" expression="execution(* AOP.*.*(..))"></aop:pointcut> <aop:before pointcut-ref="pcService" method="doBefore"/> <aop:after pointcut-ref="pcService" method="doAfter"/> <aop:around pointcut-ref="pcService" method="doAround"/> <aop:after-throwing pointcut-ref="pcService" method="doThrowing" throwing="ex"/> </aop:aspect> </aop:config> </beans>
客户端测试:ClientMain.java
package AOP; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ClientMain { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("appContext.xml"); AService aSerImpl = (AService) ac.getBean("aServiceImpl"); aSerImpl.barA(); System.out.println("---------------------------------------"); aSerImpl.fooA("Client main 测试 AServiceImpl Jdk动态代理"); System.out.println("---------------------------------------"); aSerImpl.mayThrow(1); System.out.println("*******************************************************"); /* * BServiceImpl bSerImpl = (BServiceImpl) ac.getBean("bServiceImpl"); * bSerImpl.barB("Client main 测试 BServiceImpl cglib", 1); System.out.println("----------------------"); * bSerImpl.fooB(); */ } }
运行结果:
============友情链接============
Spring Aop实例 http://blog.csdn.net/wangpeng047/article/details/8560694
Spring AOP 的简单例子
Spring AOP 实现了AOP联盟(Alliance)的制定的接口规范,它基于java的代理机制实现。AOP作为Spring的核心技术之一. 更多关于Spring AOP介绍 可参考:http://oss.org.cn/ossdocs/framework/spring/zh-cn/aop.html 下面给出一个例子来简单介绍Spring AOP具体实现过程
现假设用户通过login.jsp页面输入相应的用户名和密码之后,首先Spring AOP的环绕通知验证该用户名和密码是否符合要求,若符合要求,则到数据库中查找该用户,若用户存在,将该用户相关的信息写入日志。
1.BaseLoginAdvice 类实现了 前置通知接口(MethodBeforeAdvice)、环绕通知接口(MethodInterceptor)、后置通知接口(AfterReturningAdvice) 这三个接口
BaseLoginAdvice.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package com.laoyangx.Aop.chapter0; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; public abstract class BaseLoginAdvice implements MethodBeforeAdvice, MethodInterceptor, AfterReturningAdvice { /** * @param returnValue 目标方法返回值 * @param method 目标方法 * @param args 方法参数 * @param target 目标对象 * */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { throw new UnsupportedOperationException( "abstract class CBaseLoginAdvice not implement this method" ); } /** * @param invocation 目标对象的方法 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { throw new UnsupportedOperationException( "abstract class CBaseLoginAdvice not implement this method" ); } /** * @param method 将要执行的目标对象方法 * @param args 方法的参数 * @param target 目标对象 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { throw new UnsupportedOperationException( "abstract class CBaseLoginAdvice not implement this method" ); } } |
2.LoginAdviceSupport 类继承 BaseLoginAdvice 类,并重写 BaseLoginAdvice 类的三个方法
LoginAdviceSupport.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
package com.laoyangx.Aop.chapter0; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInvocation; public class LoginAdviceSupport extends BaseLoginAdvice { /** * 若在数据库中存在指定的用户,将用户登录信息写入日志文件 * @param returnValue 目标方法返回值 * @param method 目标方法 * @param args 方法参数 * @param target 目标对象 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println( "---------- 程序正在执行 类名: com.laoyangx.Aop.chapter0.LoginAdviceSupport 方法名:afterReturning ----------------" ); //将用户登录信息写入日志文件 } /** * 验证用户输入是否符合要求 * @param invocation 目标对象的方法 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println( "---------- 程序正在执行 类名: com.laoyangx.Aop.chapter0.LoginAdviceSupport 方法名:invoke ----------------" ); String username=invocation.getArguments()[ 0 ].toString(); String password=invocation.getArguments()[ 1 ].toString(); //在这里进行相关的验证操作 //假设验证通过 return invocation.proceed(); } /** * 在数据库中查找指定的用户是否存在 * @param method 将要执行的目标对象方法 * @param args 方法的参数 * @param target 目标对象 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println( "---------- 程序正在执行 类名: com.laoyangx.Aop.chapter0.LoginAdviceSupport 方法名:before ----------------" ); String username=(String)args[ 0 ]; String passowrd=(String)args[ 1 ]; //在这里进行数据库查找操作 } } |
3. IUser.java
1
2
3
4
5
6
7
8
9
10
|
package com.laoyangx.Aop.chapter0; public interface IUser { /** * 用户登录 * @param username 用户名 * @param password 密码 */ public void Login(String username,String password); } |
4.UserImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.laoyangx.Aop.chapter0; public class UserImpl implements IUser { /** * 用户登录 * @param username 用户名 * @param password 密码 */ @Override public void Login(String username, String password) { System.out.println( "---------- 程序正在执行 类名: com.laoyangx.Aop.chapter0.UserImpl 方法名:Login ----------------" ); } } |
5. Spring 的配置文件 login.bean.xml
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="loginAdvice" class="com.laoyangx.Aop.chapter0.LoginAdviceSupport"></bean>
<bean id="userTarget" class="com.laoyangx.Aop.chapter0.UserImpl"></bean>
<bean id="user" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.laoyangx.Aop.chapter0.IUser</value>
</property>
<property name="interceptorNames">
<list>
<value>loginAdvice</value>
</list>
</property>
<property name="target">
<ref bean="userTarget"/>
</property>
</bean>
</beans>
6.主程序文件 ConsoleApp.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.laoyangx.Aop.chapter0; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ConsoleApp { public static void main(String[] args) { ApplicationContext ctx= new ClassPathXmlApplicationContext( "com/laoyangx/Aop/chapter0/login.bean.xml" ); IUser user=(IUser)ctx.getBean( "user" ); user.Login( "username" , "123456" ); } } |
运行程序之后,控制台上输出的结果
通过结果可以看到每个函数的执行顺序。关于 前置通知、环绕通知、后置通知 这些术语 网上都有相关的介绍
Spring组成的整体架构:
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如图 1 所示。
图 1. Spring 框架的 7 个模块
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP: 通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写 的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用