spring(三):springAOP的各种织入方式一览
前言:
上一篇简单使用反射和jdk的动态代理模拟了AOP工作原理。在这里将讲阐述AOP如何把代码织入到目标对象的方法上。
一:这里介绍一下AOP一些名词概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知。
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用。这就是目标对象的方法。
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,
afterThrowing,around。这些就是需要织入到连接点中的代码。
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,
也可以是CGLIB代理,前者基于接口,后者基于子类
(6)advisor:增强器,用来筛选类中的哪些方法是我们的连接点(哪些方法需要被拦截).
(7)wave:织入,把切面/切面类和目标类的动态接入。
二:Advice(通知)
AOP的通知有before前置通知。after后置通知,无论是否成功返回都会执行。afterReturning后置通知,只有成功返回后才会执行。
afterThrowing抛出异常通知,顾名思义,只有抛出异常的时候才会执行。around环绕通知,简单来说就是集合了before和after的
功能。
这里基于xml文件实现把切面类和目标类动态接入。
(1)pojo类:Account.java
package com.cnblogs.aop.pojo;
/**
* 简单模拟银行账户
*
*/
public class Account {
// 卡号
private Long id;
// 姓名
private String name;
//余额
private double balance;
public Account() {
super();
// TODO Auto-generated constructor stub
}
public Account(Long id, String name, double balance) {
super();
this.id = id;
this.name = name;
this.balance = balance;
}
// get和set方法
}
(2)目标类:这里使用的是JDK的接口代理模式,所以需要准备一个接口,和接口的实现类,即目标类。
package com.cnblogs.aop.service;
public interface BankService {
/**
* 存钱
* @param money
*/
public Boolean save(double money) throws Exception;
/**
* 取钱
* @param money
* @return
*/
public boolean withdraw(double money) throws Exception;
}
package com.cnblogs.aop.service.impl;
import com.cnblogs.aop.pojo.Account;
import com.cnblogs.aop.service.BankService;
public class BankServiceImpl implements BankService {
private Account account;
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
@Override
public Boolean save(double money) throws Exception {
System.out.println("存钱: " + money);
account.setBalance(account.getBalance() + money);
return true;
}
@Override
public boolean withdraw(double money) throws Exception{
System.out.println("取钱: " + money);
if(account.getBalance() < money) {
throw new Exception("余额不足");
}
account.setBalance(account.getBalance() - money);
return true;
}
}
(3):目标类准备好以后,就要准备切面类了。这个切面类直接集合了所有的通知类型。
package com.cnblogs.aop.aspect;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
/**
* 切面类
* 实现了前置通知 MethodBeforeAdvice
* 成功返回才执行的后置通知
* 环绕通知
* 抛出异常才会执行的通知
*
*
*/
public class MyLogger implements MethodBeforeAdvice,AfterReturningAdvice,
MethodInterceptor,ThrowsAdvice{
/**
* 前置通知
* @param method 连接点
* @param args 连接点参数
* @param target 目标对象
*/
@Override
public void before(Method mehtod, Object[] args, Object target) throws Throwable {
// 添加日志功能
System.out.println("前置通知: " + new SimpleDateFormat().format(new Date()));
}
/**
* 后置通知会接收到连接点的返回值,连接点返回值必须为引用类型,否则
* 报错:org.springframework.aop.AopInvocationException:
* Null return value from advice does not match primitive return type for
* @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("成功返回才执行的后置通知: " + new SimpleDateFormat().format(new Date()));
}
/**
* 环绕通知
* 在调用连接点前为前置通知
* 在调用连接点后为后置通知
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕通知之前置: " +
new SimpleDateFormat().format(new Date()));
// 调用连接点
invocation.proceed();
System.out.println("环绕通知之后置: " +
new SimpleDateFormat().format(new Date()));
return null;
}
/**
* 抛出异常才会执行的通知比较特殊,它的接口类体为空,
* 只起一个标记作用。
* 不过会默认调用afterThrowing方法作为通知。
* 该方法重载,有4个参数的和1个参数的
* 当两个重载方法都存在时优先调用4个参数的
*/
public void afterThrowing(Method method, Object[] args, Object target,Exception e) {
System.out.println("抛出异常才会执行的通知(4参): " +
e.toString());
}
public void afterThrowing(Exception e) {
System.out.println("抛出异常才会执行的通知(1参): " +
e.toString());
}
}
(4):织入
advice.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 银行账号 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目标对象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面对象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 生成代理对象
这里使用的是spring的一个代理对象工厂类产生的
-->
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="service"></property>
<!-- 注入目标对象所实现的接口 可以有多个接口,基于JDK的接口代理 -->
<property name="proxyInterfaces">
<list>
<value>com.cnblogs.aop.service.BankService</value>
</list>
</property>
<!-- 注入advice -->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
(5):测试
package com.cnblogs.aop.jtest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cnblogs.aop.service.BankService;
@SuppressWarnings("resource")
public class AOPTest {
@Test
public void advice(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/aspect/advice.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
// 从容器中获取代理对象
BankService proxy = (BankService) container.getBean("proxy");
// 执行目标对象中的方法
// proxy.save(1000.0);
// 测试通知会对那些方法起作用
// proxy.getClass(); // 无
// proxy.toString(); // 有
// 测试抛出异常通知
// proxy.withdraw(5000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.环绕通知的前置优先前置通知输出,后置通知优先环绕通知的后置输出。
环绕通知之前置: 19-5-28 下午7:29
前置通知: 19-5-28 下午7:29
存钱: 1000.0
成功返回才执行的后置通知: 19-5-28 下午7:29
环绕通知之后置: 19-5-28 下午7:29
2.通知对那些方法起作用?
单独调用getClass()方法时没有通知输出,toString()方法通知有输出。去java.lang.Objcet查找两个方法,
public final Class<?> getClass(),public String toString().简单对比发现只要是非final类型修饰的,通过代理对象调用
目标对象方法时都会把通知织入。
有时候我们希望目标对象的某些方法被织入,其它方法不想被织入,当时又不想用final修饰那些不想被织入的方法该
怎么办?增强器可以起到这个效果,筛选需要的方法织入。
三:Advisor增强器
增强器可以筛选目标类中那些方法被织入。
advisor.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 银行账号 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目标对象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面对象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置增强器的bean对象 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="advice"/>
<!-- 注入需要被拦截的目标对象中的方法(连接点) -->
<property name="patterns">
<list>
<value>.*save</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<!-- 生成代理对象
这里使用的是spring的一个代理对象工厂类产生的
-->
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="service"></property>
<!-- 注入目标对象所实现的接口 可以有多个接口,基于JDK的接口代理 -->
<property name="proxyInterfaces">
<list>
<value>com.cnblogs.aop.service.BankService</value>
</list>
</property>
<!-- 注入advice -->
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
</beans>
补充:与advice.xml文件对比,发现只是多了一个org.springframework.aop.support.RegexpMethodPointcutAdvisor类的bean对象,该类就是增强器,
里面需要注入切面类的bean对象,以及指定那些方法被织入。
其次在生成代理对象的bean标签中,注入advice是注入增强器。
测试:
@Test
public void advisor(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/advisor/advisor.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
// 从容器中获取代理对象
BankService proxy = (BankService) container.getBean("proxy");
// proxy.toString();
// proxy.save(1000);
proxy.getClass();
} catch (Exception e) {
e.printStackTrace();
}
}
四:AutoProxy,自动代理
如果目标类不止一个,为多个目标类生成代理对象需要配置多个
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">...... </bean>,
自动代理可以用很少的xml配置给目标对象生成代理对象。自动代理用到增强器的功能。
autoProxy.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 银行账号 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目标对象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面对象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置增强器的bean对象 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="log"/>
<!-- 注入需要被拦截的目标对象中的方法(连接点) -->
<property name="patterns">
<list>
<value>.*save</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<!-- 配置自动代理对象 -->
<bean name="proxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
</beans>
1.不能通过proxy拿代理对象,通过目标对象的名字拿代理对象。
2.当前xml文件中一定要有一个增强器advisor,配置自动代理对象不需要注入任何东西。
3.不管目标对象是否实现了一个或多接口,自动代理的方式都能够为它产生代理对象(CGLib的方式)。
4.如果目标对象有增强器中筛选的方法,将来调用方法的时候会动态织入。
测试:
@Test
public void autoProxy(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/autoProxy/autoProxy.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
BankService target = (BankService) container.getBean("service");
target.save(1000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
结果:
环绕通知之前置: 19-5-29 下午4:22
前置通知: 19-5-29 下午4:22
存钱: 1000.0
成功返回才执行的后置通知: 19-5-29 下午4:22
环绕通知之后置: 19-5-29 下午4:22
2.AutoProxyByName 通过名字进行自动代理
虽然自动代理可以很方便的给xml文件中的目标对象设置对应的代理对象,但是并不是xml文件中的所有对象都是我们的目标对象,
我们更想希望可以进一步筛选出某几个对象为我们的目标对象。
autoProxyByName.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 银行账号 --> <bean name="account" class="com.cnblogs.aop.pojo.Account"> <property name="id" value="6217" /> <property name="name" value="jack" /> <property name="balance" value="1000.00" /> </bean> <!-- 目标对象 --> <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl"> <property name="account" ref="account" /> </bean> <!-- 切面对象 --> <bean name="log" class="com.cnblogs.aop.aspect.MyLogger"> </bean> <!-- 配置增强器的bean对象 --> <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 注入advice --> <property name="advice" ref="log"/> <!-- 注入需要被拦截的目标对象中的方法(连接点) --> <property name="patterns"> <list> <value>.*save</value> <value>.*withdraw</value> </list> </property> </bean> <!-- 配置代理对象 --> <!-- 这里使用自动代理的方式 autoproxybyname --> <bean name="proxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <!-- 注入需要被代理的对象名字 --> <property name="beanNames"> <list> <!-- value中填写bean标签的id或name属性值 --> <value>service</value> </list> </property> <!-- 注入advice或者advisor --> <property name="interceptorNames"> <list> <value>advisor</value> </list> </property> </bean> </beans>
使用byName自动代理的时候需要注意的方面:
1.当前的配置里面"有没有"advisor的配置"都没关系"
2.不管目标对象是否实现了一个或多接口,自动代理的方式都能够为它产生代理对象.
3.从spring容器中拿代理对象的时候,需要通过目标对象的名字来拿。
五:使用<aop>标签完成织入功能
execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)
除了返回类型模式(ret-type-pattern),名字模式(name-pattern),参数模式(param-pattern)是必须的,
其它模式都是可选的
ret-type-pattern: \' * \'代表匹配任意返回类型,java.lang.String匹配String返回类型。
name-pattern: 名字模式匹配的是方法名。 你可以使用 * 通配符作为所有或者部分命名模式。
param-pattern:参数匹配, \' () \'匹配无参,\' (*) \'匹配一个参数,‘(..)’匹配任意参数(0或多个),
\' (*,String) \' 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个必须是String类型。
下面给出一些常见切入点表达式的例子。
1)任意包下的任意类中的公共方法的执行:
execution(public * *(..))
2)任何一个以“set”开始的方法的执行:
execution(* set*(..))
3)BankService接口的任意方法的执行:
execution(* com.cnblogs.service.BankService.*(..))
4)定义在service包里的任意方法的执行:
execution(* com.cnblogs.service.*.*(..))
5)定义在service包或者子包里的任意方法的执行:
execution(* com.cnblogs.service..*.*(..))
1.第一种方式使用<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: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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<!-- 银行账号 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目标对象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面对象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置aop的代理 也启用了自动代理的功能-->
<aop:config>
<!-- 定义一个切入点 -->
<aop:pointcut
expression="execution(public * com.cnblogs.aop.service.BankService.save(*))"
id="save"/>
<aop:pointcut
expression="execution(public * com.cnblogs.aop.service.BankService.withdraw(*))"
id="withdraw"/>
<!-- 定义哪一个advice在哪一个切入点上面起作用 -->
<aop:advisor advice-ref="log" pointcut-ref="save" />
<aop:advisor advice-ref="log" pointcut-ref="withdraw" />
</aop:config>
</beans>
注意:
1.从spring容器中拿代理对象的时候也是要用目标对象的名字来拿。
2.没有实现任何接口的目标对象也能产生代理对象。
测试:
@Test
public void aop1(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/aopConfig/config1.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
BankService target = (BankService) container.getBean("service");
target.save(1000.0);
target.withdraw(20000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
环绕通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
存钱: 1000.0
成功返回才执行的后置通知: 19-5-29 下午7:06
环绕通知之后置: 19-5-29 下午7:06
环绕通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
取钱: 20000.0
抛出异常才会执行的通知(1参): java.lang.Exception: 余额不足
2.第二种方式使用aop
在一个切面类中定个多个方法,根据xml文件的配置每个方法都可以织入到切入点的不同位置,并且advice是在aop的标签中进行配置,
不需要再写对应的advice类了。
切面类:
package com.cnblogs.aop.aopConfig;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类,这里包含了多个切面方法,可以把每个切面方法织入到不同切入点的不同位置
*
*/
public class XmlHandler {
public void beforeTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" before...");
}
public void afterTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" after...");
}
public void afterReturningTest(JoinPoint p){
System.out.println(p.getSignature().getName()+" afterReturning");
}
//在和aroundAdvice结合的时候,这个方法一定要加上这个ProceedingJoinPoint类型的参数
public Object aroundTest(ProceedingJoinPoint pjp)throws Throwable{
//JoinPoint对象不能调用连接点所表示的方法
//ProceedingJoinPoint能调用连接点所表示的方法 pjp.proceed()
System.out.println(pjp.getSignature().getName()+" is start..");
//调用目标对象中的方法
Object obj = pjp.proceed();
System.out.println(pjp.getSignature().getName()+" is end..");
return obj;
}
public void throwingTest(JoinPoint p,Exception ex){
System.out.println(p.getSignature().getName()+" is throwing..."+ex.getMessage());
}
}
<?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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目标对象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面对象 -->
<bean name="log" class="com.cnblogs.aop.aopConfig.XmlHandler">
</bean>
<!-- 配置aop的代理 -->
<aop:config>
<!-- 定义切入点名为myPointCut -->
<aop:pointcut expression="execution(public * com.cnblogs.aop.service.*.*(..))"
id="myPointCut"/>
<!-- 定义切面类 以及需要使用的advice -->
<aop:aspect id="aspect" ref="log">
<!-- before表示会把切面类log中的beforeTest方法织入到名字
叫myPointCut的切入点前面
-->
<aop:before method="beforeTest" pointcut-ref="myPointCut"/>
<!-- after表示不管方法是否正常结束都会起作用 -->
<aop:after method="afterTest" pointcut-ref="myPointCut"/>
<!-- after-returning表示方法正常结束才会起作用(抛异常时候不起作用) -->
<aop:after-returning method="afterReturningTest" pointcut-ref="myPointCut"/>
<aop:around method="aroundTest" pointcut-ref="myPointCut"/>
<!-- throwing="ex"表示throwingTest方法中接收异常对象的名字一定要是ex -->
<aop:after-throwing method="throwingTest" pointcut-ref="myPointCut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
@Test
public void aop2(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/aopConfig/config2.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
BankService target = (BankService) container.getBean("service");
target.save(1000.0);
// target.withdraw(20000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
save before...
save is start..
存钱: 1000.0
save is end..
save afterReturning
save after...
六:使用注解配置AOP
1.定义切面类:
package com.cnblogs.aop.annotation;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AnnotationHandler {
/**
* 在一个方法上面加上注解来定义切入点
* 这个切入点的名字就是这个方法的名字
* 这个方法本身不需要有什么作用
* 这个方法的意义就是:给这个 @Pointcut注解一个可以书写的地方
* 因为注解只能写在方法、属性、类的上面,并且方法名作为切入点的名字
*/
@Pointcut("execution(public * com.cnblogs.aop.service.impl.*.*(..))")
public void myPoint(){}
/**
* 前置通知
* 如果需要用到Joinpoint,可以在方法参数加上
* @param j
*/
@Before("myPoint()")
public void beforeTest(){
System.out.println("前置通知: " + new SimpleDateFormat().format(new Date()));
}
/**
* 最终通知
*/
@After("myPoint()")
public void afterTest(){
System.out.println("最终通知: " + new SimpleDateFormat().format(new Date()));
}
/**
* 环绕通知
* @throws Throwable
*/
@Around("myPoint()")
public void aroundTest(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知之前置: " +
new SimpleDateFormat().format(new Date()));
//调用连接点的方法去执行
Object obj = pjp.proceed();
System.out.println("环绕通知之后置: " +
new SimpleDateFormat().format(new Date()));
}
/**
* 成功返回才会执行的后置通知
*/
@AfterReturning("myPoint()")
public void afterReturningTest(){
System.out.println("成功返回才执行的后置通知: " + new SimpleDateFormat().format(new Date()));
}
/**
* 抛出异常通知
*/
@AfterThrowing(value="myPoint()",throwing="ex")
public void throwTest(Exception ex){
System.out.println("抛出异常才会执行的通知(1参): " +
ex.toString());
}
}
2.在BankServiceImpl上加 @Service("service") 注解,在account成员变量加 @Autowired注解。
在Account上加 @Component @Scope("prototype")。
3.在xml文件中加上
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.cnblogs.aop.pojo"/>
<context:component-scan base-package="com.cnblogs.aop.service.*"/>
<context:component-scan base-package="com.cnblogs.aop.annotation"/>
<context:component-scan base-package=" " />
表示SpringIOC容器会扫描此包下加了@Component,@Service注解的类,并放入IOC容器。在上上篇随便springIOC的三种注入方式
有介绍。
4.最后一步就是测试了
@Test
public void annotation(){
try {
// 获取springIOC容器
String path = "com/cnblogs/aop/annotation/annotation.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
BankService service = (BankService) container.getBean("service");
// service.getAccount().setBalance(10000.0);
service.save(3000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
环绕通知之前置: 19-5-29 下午8:41
前置通知: 19-5-29 下午8:41
存钱: 3000.0
环绕通知之后置: 19-5-29 下午8:41
最终通知: 19-5-29 下午8:41
成功返回才执行的后置通知: 19-5-29 下午8:41