目录
学习Spring Aop,我们就需要先知道几个关键字。
连接点:就是在应用程序流程中的任何连接处。(比如调用方法时,抛出异常时等)
切点:就是我们通过一些条件筛选出的我们需要使用的连接点。
切面:就是我们找到切点后要做的事情。包括切面和通知。
通知:我们进入切点后具体要做的事情。
引入:就是通过切面 向现有的类(切点所匹配的类)做一下操作或引入新的方法或者属性。
织入:就是把切面应用到目标对象,并创建代理对象的过程。
可以看下面的图:见笑见笑。
用XMl来创建切面。
我们来模拟一个场景。进入鸟巢看演唱会。正常的流程是 买票进入鸟巢-------看演唱会--------结束出鸟巢。
现在我们想在看演唱会前后加入一些动作。我们现在不改变我们的流程。怎么办呢,通过切面。
首先我们来创建一个演唱会流程
Performance.java 观看演唱会的
package com.tzw.service;
/**
* 演唱会
*/
public class Performance {
/**
* 买票进入鸟巢
*/
public void inBirdN(){
System.out.println("买票进入鸟巢");
}
/**
* 看演唱会
*/
public void WatchConcert(){
System.out.println("观看演唱会");
}
/**
* 看完出鸟巢
*/
public void outBirdN(){
System.out.println("观看完退场");
}
}
创建一个类将流程串连起来。1.进入现场 2.观看演唱会 3.看完退场。
package com.tzw.service;
import org.springframework.beans.factory.annotation.Autowired;
public class PerformPlus {
@Autowired
public Performance performance;
public void getRun(){
performance.inBirdN();
performance.WatchConcert();
performance.outBirdN();
}
}
在这个流程中,我们需要用切面加一点东西。在第2步观看演唱会前后,加点动作。在看之前,需要先找到自己的作为,在看之后我们需要及时的鼓掌。
接下来们创建一个切面类,PerAspect.java 这个类中定义了以上的连个动作。
package com.tzw.service;
public class PerAspect {
public void lookSeat(){
System.out.println("寻找座位");
}
public void applaud(){
System.out.println("鼓掌");
}
}
那我们怎么把这个切面加入到我们之前的三步流程里呢。这里我们通过配置文件来配置。
<?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"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tzw.service"/>
<bean id="performance" class="com.tzw.service.Performance"></bean>
<bean id="performPlus" class="com.tzw.service.PerformPlus"></bean>
<!--定义切面bean-->
<bean id="perAspect" class="com.tzw.service.PerAspect"></bean>
<!--一般aop的配置都会放在aop:config标签里-->
<aop:config>
<!--这里是定义了一个切面类,用aop:aspect -->
<aop:aspect ref="perAspect">
<!--定义切点,告诉切面里的通知,在那个点进行通知。-->
<aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert())"/>
<!--定义前置通知-->
<aop:before method="lookSeat" pointcut-ref="tzwPointCut"/>
<!--定义后置通知-->
<aop:after method="applaud" pointcut-ref="tzwPointCut"/>
</aop:aspect>
</aop:config>
</beans>
在这里,我们定义了切面,切面里定义了两个通知,前置通知和后置通知,同时我们还定义了切点,所以在切点之前会执行前置通知,在切点之后会执行后置通知。
当然,这里出了前置通知,后置通知,还有其他的通知,比如<aop:after-returning>返回通知,<aop:after-throwing>抛出异常通知,<aop:around>环绕通知
所以在这个流程里,在观看演唱会之前会 执行 找座位, 在观看演唱会之后 会鼓掌。
我们进行一下测试
@Test
public void test2(){
ClassPathXmlApplicationContext xmlContext = new ClassPathXmlApplicationContext("springAop.xml");
PerformPlus performPlus = (PerformPlus) xmlContext.getBean("performPlus");
performPlus.getRun();
}
结果
用Aspect注解来创建切面
切面类。
package com.tzw.serviceone;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Aspect 注解用来声明这个类是一个切面类
*/
@Aspect
//@EnableAspectJAutoProxy
public class PerAspectOne {
/**
* @Pointcut 用来定义切点
*/
@Pointcut(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
public void pointCut(){
}
/**
* @Before 用来定义一个前置通知。同时引用 pointCut切点
*/
@Before(value = "pointCut()")
public void lookSeat(){
System.out.println("寻找座位");
}
/**
* @After 用来定义一个前置通知。同时引用 pointCut切点
* 当然也可以不引用,直接在方式上声明一个切点。
*/
// @After(value = "pointCut()")
@After(value="execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
public void applaud(){
System.out.println("鼓掌");
}
}
spring配置。如果用@Aspect注解定义切面的话,在spring中就不需要用<aop:config><aop:Aspect>等标签。只需要开启@Aspect注解就行,在xml中用 <aop:aspectj-autoproxy>,用来启动@Aspect注解,当然也可以直接在切面类,就是上面那个类,用该注解注释在类上,@EnableAspectJAutoProxy。这个两个选一种配置即可。
<?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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tzw.serviceone"/>
<bean id="performance" class="com.tzw.serviceone.PerformanceOne"></bean>
<bean id="performPlusOne" class="com.tzw.serviceone.PerformPlusOne"></bean>
<!--声明切面bean-->
<bean id="perAspect" class="com.tzw.serviceone.PerAspectOne"></bean>
<aop:aspectj-autoproxy/>
</beans>
值得注意的是,我们还是需要在spring中声明一下切面bean。
<!--声明切面bean-->
<bean id="perAspect" class="com.tzw.serviceone.PerAspectOne"></bean>
当然也可用javaConfig 或这注解的形式都行,但一定要加载到spring。否则切面类步器作用。
其他通知及环绕通知。
除了上述通知之外,还有,还有其他的通知,比如<aop:after-returning>返回通知,<aop:after-throwing>抛出异常通知,<aop:around>环绕通知。
<aop:after-returning>返回后通知,<aop:after-throwing>这两个通知呢,用法和前置后置一样,比较简单,
<aop:after-returning>返回后通知,从字面意思讲,就是方法方法成功retrun之后通知。
<aop:after-throwing>抛出异常通知,就是这抛出异常时进行通知。
上述两个就不说了,说一个环绕那个通知。这个通知也是通知的一种,只是说这个通知可以实现 以上的所以通知,包括前置,后置,返回,抛异常。所以可以说它一个通知可以全部搞定。
public void arountOne(ProceedingJoinPoint jp){
try {
System.out.println("前置通知");
jp.proceed();
System.out.println("后置通知");
System.out.println("返回通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}
}
这个就是一个环绕通知的一个方法,这里包含了所以的通知,前置后置等等。
这里引入了一个ProceedingJoinPoint 类,它的proceed()方法我们可以理解为切点所指向的具体方法,在这个方法执行前后,我们做了一些通知。
获取通知中的参数
在程序流程中,如果我们先在切面中获取正常流程中的参数,我们应该怎么做呢。
xml的配置
主要是用了args表达式。同时切点对应的通知方法参数名称也要和args表达式中的字段值相同。
expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"
<?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"
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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tzw.service"/>
<bean id="performance" class="com.tzw.service.Performance"></bean>
<bean id="performPlus" class="com.tzw.service.PerformPlus"></bean>
<!--定义切面bean-->
<bean id="perAspect" class="com.tzw.service.PerAspect"></bean>
<!--一般aop的配置都会放在aop:config标签里-->
<aop:config>
<!--这里是定义了一个切面类,用aop:aspect -->
<aop:aspect ref="perAspect">
<!--定义切点,告诉切面里的通知,在那个点进行通知。-->
<aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"/>
<!--定义前置通知-->
<aop:before method="lookSeat" pointcut-ref="tzwPointCut" arg-names="name,a"/>
<!--定义后置通知-->
<aop:after method="applaud" pointcut-ref="tzwPointCut" arg-names="name,a"/>
</aop:aspect>
</aop:config>
</beans>
package com.tzw.service;
public class PerAspect {
public void lookSeat(String name,int a){
System.out.println(name+"寻找座位");
}
public void applaud(String name,int a){
System.out.println("鼓掌");
}
}
注意:如果我们引用同一个切点,那么引用该切点的所有方法都需要配置该参数。否则无法执行。
例如:前置通知和后置通知都引用同一个切点,那么前置通知和后置通知都需要配置切点里的参数。如上。
如果只有前置通知需要,而后置通知不需要,那就不要引用同一个切点,可以新建一个,获者也可以不要引用pointcut-ref
,而是用pointcut表达式重新写一个。
<aop:config>
<!--这里是定义了一个切面类,用aop:aspect -->
<aop:aspect ref="perAspect">
<!--定义切点,告诉切面里的通知,在那个点进行通知。-->
<aop:pointcut id="tzwPointCut" expression="execution(* com.tzw.service.Performance.WatchConcert(..)) and args(name,a)"/>
<!--定义前置通知-->
<aop:before method="lookSeat" pointcut-ref="tzwPointCut" arg-names="name,a"/>
<!--定义后置通知-->
<aop:after method="applaud" pointcut="execution(* com.tzw.service.Performance.WatchConcert(..))"/>
</aop:aspect>
</aop:config>
利用@注解方式
package com.tzw.serviceone;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* @Aspect 注解用来声明这个类是一个切面类
*/
@Aspect
@EnableAspectJAutoProxy
public class PerAspectOne {
/**
* @Pointcut 用来定义切点
*/
@Pointcut(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert(..))")
public void pointCut(){
}
@Around(value = "execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
public void arountOne(ProceedingJoinPoint jp){
try {
System.out.println("前置通知");
jp.proceed();
System.out.println("后置通知");
System.out.println("返回通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}
}
/**
* @Before 用来定义一个前置通知。同时引用 pointCut切点
*/
@Before(value = "pointCut() && args(name,a)")
public void lookSeat(String name,int a){
System.out.println(name+"寻找座位"+a);
}
/**
* @After 用来定义一个前置通知。同时引用 pointCut切点
* 当然也可以不引用,直接在方式上声明一个切点。
*/
@After(value = "pointCut()&&args(name,a)")
// @After(value="execution(* com.tzw.serviceone.PerformanceOne.WatchConcert())")
public void applaud(String name ,int a){
System.out.println("鼓掌"+a);
}
}
这里注意,在xml文件中 用args表达式是要用 and 连接,因为在xml中 && 有特殊含义。
而在 java中就可以直接用&&。
通过切面引入新功能
映入新功能可以理解为为原对象新加了一个接口父类,所以我们在注入原对象的时候可以声明这个父类接口。而原对象做为这个接口的实现,然后调用这个接口父类的方法。而真正执行方法的行为是有 引入的接口的真正实现类来实现的。
原对象和原接口
package com.tzw.servicetwo;
/**
* 演唱会
*/
public class PerformanceTwo implements PerformanceInter{
/**
* 买票进入鸟巢
*/
public void inBirdN(){
System.out.println("买票进入鸟巢");
}
/**
* 看演唱会
*/
public void WatchConcert(String name,int a){
System.out.println("观看演唱会");
}
/**
* 看完出鸟巢
*/
public void outBirdN(){
System.out.println("观看完退场");
}
}
package com.tzw.servicetwo;
public interface PerformanceInter {
/**
* 买票进入鸟巢
*/
public void inBirdN();
/**
* 看演唱会
*/
public void WatchConcert(String name,int a);
/**
* 看完出鸟巢
*/
public void outBirdN();
}
需要引入的对象和接口
package com.tzw.servicetwo.add;
public interface EncoreableTwo {
public void test();
}
package com.tzw.servicetwo.add;
public class EncoreableTowImp implements EncoreableTwo {
public void test(){
System.out.println("引入新的方法");
}
}
1.通过注解切面类配置
主要是通过@DeclareParents注解。
/**
* 引入新功能
*/
@DeclareParents(value = "com.tzw.servicetwo.PerformanceInter+" ,defaultImpl = EncoreableTowImp.class)
public static EncoreableTwo encoreableTwo;
以下是完整的代码。
package com.tzw.servicetwo;
import com.tzw.servicetwo.add.EncoreableTowImp;
import com.tzw.servicetwo.add.EncoreableTwo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Aspect 注解用来声明这个类是一个切面类
*/
@Aspect
@EnableAspectJAutoProxy
public class PerAspectTwo {
/**
* 引入新功能
*/
@DeclareParents(value = "com.tzw.servicetwo.PerformanceInter+" ,defaultImpl = EncoreableTowImp.class)
public static EncoreableTwo encoreableTwo;
/**
* @Pointcut 用来定义切点
*/
@Pointcut(value = "execution(* com.tzw.servicetwo.PerformanceTwo.WatchConcert(..))")
public void pointCut(){
}
@Around(value = "execution(* com.tzw.servicetwo.PerformanceTwo.WatchConcert())")
public void arountOne(ProceedingJoinPoint jp){
try {
System.out.println("前置通知");
jp.proceed();
System.out.println("后置通知");
System.out.println("返回通知");
} catch (Throwable throwable) {
System.out.println("异常通知");
throwable.printStackTrace();
}
}
/**
* @Before 用来定义一个前置通知。同时引用 pointCut切点
*/
@Before(value = "pointCut() && args(name,a)")
public void lookSeat(String name,int a){
System.out.println(name+"寻找座位"+a);
}
/**
* @After 用来定义一个前置通知。同时引用 pointCut切点
* 当然也可以不引用,直接在方式上声明一个切点。
*/
@After(value = "pointCut()&&args(name,a)")
// @After(value="execution(* com.tzw.serviceone.PerformanceTwo.WatchConcert())")
public void applaud(String name ,int a){
System.out.println("鼓掌"+a);
}
}
2.通过xml配置。
通过<aop:declare-parents>标签。
<?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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tzw.servicetwo"/>
<bean id="performance" class="com.tzw.servicetwo.PerformanceTwo"></bean>
<bean id="performPlusTwo" class="com.tzw.servicetwo.PerformPlusTwo"></bean>
<!--声明切面bean-->
<bean id="perAspect" class="com.tzw.servicetwo.PerAspectTwo"></bean>
<!--<aop:aspectj-autoproxy/>-->
<aop:config>
<aop:aspect ref="perAspect">
<aop:declare-parents types-matching="com.tzw.servicetwo.PerformanceInter+"
implement-interface="com.tzw.servicetwo.add.EncoreableTwo" default-impl="com.tzw.servicetwo.add.EncoreableTowImp"/>
</aop:aspect>
</aop:config>
</beans>
这两种是一样的,只是实现方式不一样,一个是通过注解实现的,另一个是通过xml配置<aop>标签实现的。两种方式都可以。
使用引入的新功能。
package com.tzw.servicetwo;
import com.tzw.servicetwo.add.EncoreableTwo;
import org.springframework.beans.factory.annotation.Autowired;
public class PerformPlusTwo {
@Autowired
public PerformanceInter performance;
public void getRun(){
performance.inBirdN();
performance.WatchConcert("张三",1);
performance.outBirdN();
//加入新方法。将
EncoreableTwo encoreableTwo = (EncoreableTwo)performance;
encoreableTwo.test();
}
}
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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.tzw.servicetwo"/>
<bean id="performance" class="com.tzw.servicetwo.PerformanceTwo"></bean>
<bean id="performPlusTwo" class="com.tzw.servicetwo.PerformPlusTwo"></bean>
<!--声明切面bean-->
<bean id="perAspect" class="com.tzw.servicetwo.PerAspectTwo"></bean>
</beans>
测试类
@Test
public void test4(){
ClassPathXmlApplicationContext xmlContext = new ClassPathXmlApplicationContext("springAopTwo.xml");
PerformPlusTwo performPlusTwo = (PerformPlusTwo) xmlContext.getBean("performPlusTwo");
performPlusTwo.getRun();
}
运行结果