Spring对AOP切面支持实现及示例demo(代理模式实现、注解驱动、注入式)
[email protected] 注解驱动的切面
定义切点
Spring支持通过AspectJ的切点表达式语言来定义 Spring 切面,同时增加通过bean的id指定bean的写法。
如:execution(* com.my.spring.bean..(…)) 指定com.my.spring.bean包下所有类的所有方法作为切点
其结构解析如下:
AspectJ切点表达式的指示器不只有execution:
- arg() :限制连接点匹配参数为指定类型的执行方法
- execution() :用于匹配是连接点的执行方法
- this() :限制连接点匹配 AOP 代理的 bean 引用为指定类型的类
- target :限制连接点匹配目标对象为指定类型的类
- within() :限制连接点匹配指定的类型
各指示器之间可以通过&&(与),||(或),!(非)连接符进行连接实现多条件查询定义节点
如:execution(* com.my.spring.bean..(…))&&arg(java.lang.Integer)
示例Demo
导包
Spring AOP的实现依赖于spring-aop包和aspectjweaver包,多加一个log4j日志打印包并在根目录一般是src/main/resources这个创建log4j.properties,需在pom文件引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
log4j.properties内容
### 设置###
log4j.rootLogger = debug,stdout,D,E
### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
通过实例demo引入概念:
定义一个基础接口类BaseInterface
public interface BaseInterface {
/**
* 新增歌曲
*
* @param author
* 作者
* @param songTitle
* 歌曲名
*
* @return java.lang.Integer 返回当前歌曲总数
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer addSong(String author, String songTitle);
/**
* 删除歌曲
*
* @param author
* 作者
* @param songTitle
* 歌曲名
*
* @return java.lang.Integer 返回当前歌曲总数
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer delSong(String author, String songTitle);
}
创建实现类BaseBean
@Component
public class BaseBean implements BaseInterface{
private String author;
private String songTitle;
private Integer count=0;
public Integer addSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("新增了一首歌:"+author+"-"+songTitle);
count++;
return count;
}
public Integer delSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("删除了一首歌:"+author+"-"+songTitle);
count--;
return count;
}
}
创建一个切面类BaseBeanAspect(这里将引用的包一起贴出来防止错用)
package aspectj;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class BaseBeanAspect {
private Logger logger = Logger.getLogger(BaseBean.class);
/**
* 方法执行前的通知
*/
@Before("execution(* aspectj.*.*(..))")
public void beforeInvoke(){
logger.debug("方法执行前");
}
/**
* 方法执行后的通知
*/
@After("execution(* aspectj.*.*(..))")
public void afterInvoke(){
logger.debug("方法执行后");
}
/**
* 方法执行返回后的通知
*/
@AfterReturning("execution(* aspectj.*.*(..))")
public void afterReturning(){
logger.debug("==================方法执行完成");
}
/**
* 方法抛出异常的通知
*/
@AfterThrowing("execution(* aspectj.*.*(..))")
public void afterThrowing(){
logger.debug("==================方法执行报错");
}
}
创建自动化装配的配置类
@Configuration
@EnableAspectJAutoProxy//开启自动代理开关,启用切面
@ComponentScan(basePackages = {"aspectj"})
public class ComponentConfig {
}
创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {ComponentConfig.class})
public class AppTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("myBean","mySong");
baseInterface.delSong("myBean","mySong");
}
}
打印结果为
[DEBUG] 2019-03-27 11:55:28,075 method:aspectj.BaseBeanAspect.beforeInvoke(BaseBeanAspect.java:22)
方法执行前
新增了一首歌:myBean-mySong
[DEBUG] 2019-03-27 11:55:28,075 method:aspectj.BaseBeanAspect.afterInvoke(BaseBeanAspect.java:30)
方法执行后
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.afterReturning(BaseBeanAspect.java:38)
==================方法执行完成
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.beforeInvoke(BaseBeanAspect.java:22)
方法执行前
删除了一首歌:myBean-mySong
[DEBUG] 2019-03-27 11:55:28,076 method:aspectj.BaseBeanAspect.afterInvoke(BaseBeanAspect.java:30)
方法执行后
[DEBUG] 2019-03-27 11:55:28,077 method:aspectj.BaseBeanAspect.afterReturning(BaseBeanAspect.java:38)
==================方法执行完成
[DEBUG] 2019-03-27 11:55:28,077 method:org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener.beforeOrAfterTestMethod(AbstractDirtiesContextTestExecutionListener.java:106)
定义环绕通知:@Around
环绕通知是从方法执行前一直包裹直到方法执行完成后的一个通知,用的比较多,其中被定义的方法需要引入参数ProceedingJoinPoint,ProceedingJoinPoint对象封装了当前运行对象的具体信息,简单实现如下:
@Around("execution(* aspectj.*.*(..))")
public void aroundInvoke(ProceedingJoinPoint jp){
try {
logger.debug("=====================环绕执行方法开始");
Signature signature = jp.getSignature();
String methodName = signature.getName();
MethodSignature methodSignature = (MethodSignature) signature;
logger.debug("方法名:{}"+methodName);
List<Object> args = Arrays.asList(jp.getArgs());
logger.debug("参数列表:{}"+args);
Class<?> returnType = methodSignature.getMethod().getReturnType();
logger.debug("方法返回类型:{}"+returnType);
Object proceed = jp.proceed();
logger.debug("======================环绕执行方法结束,方法执行结果:{}"+proceed);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
2.注入式 AspectJ 切面(适用于 Spring 各版本)
场景与节点定义
我们知道使用注解实现切面编程是很方便直接的,但是有时候我们并不一定拥有通知类的源码,也就无法给对应的方法添加注解,这时候就需要使用XML配置实现了。XML配置实现与注解实现十分类似,我们可以看一下基本的实现节点:
①定义切面:<aop:aspect ref=“切面类在xml文件中对应bean的id”>
②定义切点:<aop:pointcut id=“切点id” expression=“切点表达式”/>
③定义通知:
<aop:beafore method=“通知方法名” pointcut-ref=“切点id”/>-------------定义方法执行前的通知
<aop:after method=“通知方法名” pointcut-ref=“切点id”/>-------------定义方法执行后的通知
<aop:afterReturning method=“通知方法名” pointcut-ref=“切点id”/>-------------定义方法执行返回后的通知
<aop:afterThrowing method=“通知方法名” pointcut-ref=“切点id”/>-------------定义方法执行抛出异常后的通知
<aop:around method=“通知方法名” pointcut-ref=“切点id”/>-------------定义方法环绕通知
④开启自动代理:aop:aspectj-autoproxy/
⑤表示aop配置:aop:config</aop:config>
除了开启自动代理,aop的所有节点都需要包含在aop:config</aop:config>节点中。
示例demo
延用上面demo的基础接口类BaseInterface、实体类BaseBean。最好将@Component,虽然放在那儿我也没发现会出什么问题,不过基础接口中重复声明通知标签会出问题(不会报错)。
新建一个基础接口类NewBaseInterface
public class NewBaseBeanAspect {
private Logger logger = Logger.getLogger(BaseBean.class);
public void pointCut(){//被用于标识的空方法
System.out.println(">>>>>>>>>>>>pointcut");
}
public void beforeInvoke(){
logger.debug("pointcut方法执行前");
}
public void aroundInvoke(ProceedingJoinPoint jp){
try {
logger.debug("=====================环绕执行方法开始");
Signature signature = jp.getSignature();
String methodName = signature.getName();
MethodSignature methodSignature = (MethodSignature) signature;
logger.debug("方法名:{}"+methodName);
List<Object> args = Arrays.asList(jp.getArgs());
logger.debug("参数列表:{}"+args);
Class<?> returnType = methodSignature.getMethod().getReturnType();
logger.debug("方法返回类型:{}"+returnType);
Object proceed = jp.proceed();
logger.debug("======================环绕执行方法结束,方法执行结果:{}"+proceed);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
/**
* 方法执行前的通知
*/
/*@Before("execution(* aspectj.*.*(..))")
public void beforeInvoke1(){
logger.debug("方法执行前");
}*/
/**
* 方法执行后的通知
*/
public void afterInvoke(){
logger.debug("方法执行后");
}
/**
* 方法执行返回后的通知
*/
public void afterReturning(){
logger.debug("==================方法执行完成");
}
/**
* 方法抛出异常的通知
*/
public void afterThrowing(){
logger.debug("==================方法执行报错");
}
}
创建配置文件aspectxml.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启自动代理-->
<aop:aspectj-autoproxy/>
<!--装配基本类-->
<bean class="aspectxml.BaseBean" id="baseBean" name="baseBean"/>
<!--装配切面类-->
<bean class="aspectxml.NewBaseBeanAspect" id="newBaseBeanAspect"/>
<!--aop配置-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="newBaseBeanAspect">
<!--定义切点-->
<aop:pointcut id="pointCut" expression="execution(* aspectxml.*.*(..))"/>
<!--定义前置通知-->
<aop:before method="beforeInvoke" pointcut-ref="pointCut"/>
<!--定义后置通知-->
<aop:after method="afterInvoke" pointcut-ref="pointCut"/>
<!--定义方法执行返回后通知-->
<aop:after-returning method="afterReturning" pointcut-ref="pointCut"/>
<!--定义方法异常后通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut"/>
<!--定义方法环绕通知通知-->
<!-- <aop:around method="aroundInvoke" pointcut-ref="pointCut"/> -->
</aop:aspect>
</aop:config>
</beans>
创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:aspectxml.xml"})//将配置文件作为装配环境
public class AppXMLTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("Mr D","The World!!");
baseInterface.delSong("Mr D","The World!!");
}
}
打印结果
[DEBUG] 2019-03-27 18:14:34,702 method:aspectxml.NewBaseBeanAspect.beforeInvoke(NewBaseBeanAspect.java:20)
pointcut方法执行前
新增了一首歌:Mr D-The World!!
[DEBUG] 2019-03-27 18:14:34,702 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
Returning cached instance of singleton bean 'newBaseBeanAspect'
[DEBUG] 2019-03-27 18:14:34,702 method:aspectxml.NewBaseBeanAspect.afterInvoke(NewBaseBeanAspect.java:53)
方法执行后
[DEBUG] 2019-03-27 18:14:34,702 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
Returning cached instance of singleton bean 'newBaseBeanAspect'
[DEBUG] 2019-03-27 18:14:34,703 method:aspectxml.NewBaseBeanAspect.afterReturning(NewBaseBeanAspect.java:60)
==================方法执行完成
[DEBUG] 2019-03-27 18:14:34,703 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
Returning cached instance of singleton bean 'newBaseBeanAspect'
[DEBUG] 2019-03-27 18:14:34,703 method:aspectxml.NewBaseBeanAspect.beforeInvoke(NewBaseBeanAspect.java:20)
pointcut方法执行前
删除了一首歌:Mr D-The World!!
[DEBUG] 2019-03-27 18:14:34,703 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
Returning cached instance of singleton bean 'newBaseBeanAspect'
[DEBUG] 2019-03-27 18:14:34,704 method:aspectxml.NewBaseBeanAspect.afterInvoke(NewBaseBeanAspect.java:53)
方法执行后
[DEBUG] 2019-03-27 18:14:34,704 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
Returning cached instance of singleton bean 'newBaseBeanAspect'
[DEBUG] 2019-03-27 18:14:34,704 method:aspectxml.NewBaseBeanAspect.afterReturning(NewBaseBeanAspect.java:60)
==================方法执行完成
[DEBUG] 2019-03-27 18:14:34,704 method:org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener.beforeOrAfterTestMethod(AbstractDirtiesContextTestExecutionListener.java:106)
3.基于代理的经典 Spring AOP
静态代理demo
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
比如,有两个有钱人张三和李四在杭州有一套房子要租出去,只有找到租客才能收租金,李四找了中介帮他,张三自己找人,其中的中介就起到代理的作用,李四可以不再去管租房的事情而是每个月收收房租做个快乐的房东,张三就不一样了,他必须自己找到租客。
创建一个业务接口RichPeople
public interface RichPeople {
/**
* 招人租房
*/
public void zufang();
/**
* 收租
*/
public void getmoney();
}
创建一个实现类People实现业务接口
public class People implements RichPeople{
private String name;
public People(){}
public People(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void zufang() {
System.out.println(this.name+"自己招租客。。。好累。。。");
}
public void getmoney() {
System.out.println(this.name+"自己收租金。。。有钱拿累一点不是事儿。。。");
}
}
创建一个代理类ZhongjieProxy,相当于一个中介公司
//某租房中介
public class ZhongjieProxy implements RichPeople{
//客户
private RichPeople richPeople;
public ZhongjieProxy(){}
public ZhongjieProxy(RichPeople richPeople){
this.richPeople = richPeople;
}
/**
* 中介公司帮忙找租客
*/
public void zufang() {
System.out.println("帮客户找租客。。。顾客就是上帝。。。");
}
/**
* 租金让客户自己去收
*/
public void getmoney() {
richPeople.getmoney();
}
}
创建测试类ProxyTest
public class ProxyTest {
@Test
public void method(){
//有钱人张三
RichPeople zhangsan = new People("张三");
//有钱人李四
RichPeople lisi = new People("李四");
//张三自己找租客收租金
zhangsan.zufang();
zhangsan.getmoney();
//李四找中介公司收租金
ZhongjieProxy zhongjie = new ZhongjieProxy(lisi);
zhongjie.zufang();
zhongjie.getmoney();
}
}
打印结果
张三自己招租客。。。好累。。。
张三自己收租金。。。有钱拿累一点不是事儿。。。
帮客户找租客。。。顾客就是上帝。。。
李四自己收租金。。。有钱拿累一点不是事儿。。。
JDK动态代理demo
面向接口生成代理,原理就是类加载器根据接口,在虚拟机内部创建接口实现类
Proxy.newProxyInstance(classloader,interfaces[], invocationhandler );
invocationHandler 通过invoke()方法反射来调用目标类中的代码。
创建一个业务接口Fly
// 被代理接口
public interface Fly {
public void gotoFly();
public void stopFly();
}
创建一个实体类Bird实现Fly接口
// 委托类
public class Bird implements Fly {
public void gotoFly() {
System.out.println("鸟儿张开翅膀要飞起来了。。。。");
}
public void stopFly() {
System.out.println("准备降落。。。。");
}
public void eatBug(){
System.out.println("鸟要吃虫子,补充体力。。。");
}
}
创建一个代理类
// 拦截器
public class InvocationHandlerProxy implements InvocationHandler{
// 委托类
private Object obj;
public InvocationHandlerProxy(){}
// 初始化委托类
public InvocationHandlerProxy(Object obj){
this.obj = obj;
}
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* rgs:指代代理对象方法传递的参数
* 代理类执行代理方法时,回调此方法,并将自己this作为实参传给proxy
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 拦截方法
if (method.getName().equals("gotoFly")) {
System.out.println("被拦截了,鸟飞不走了。。。");
return null;
}
// 以反射的形式,调用委托类的方法
return method.invoke(obj, args);
}
}
创建测试类ProxyTest
public class ProxyTest {
@Test
public void demo1() {
// JDK自动代理的原理是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口)
// 1、 创建目标业务对象的引用
Fly fly = new Bird();
// 2、创建一个代理类对象
InvocationHandler handler = new InvocationHandlerProxy(fly);
// 3、使用目标业务对象类加载器和接口,在内存中创建代理对象
Fly proxy = (Fly) Proxy.newProxyInstance(fly.getClass().getClassLoader(), fly.getClass().getInterfaces(),
handler);
//这里可以通过运行结果证明proxy是Proxy的一个实例,这个实例实现了Fly接口
System.out.println(proxy instanceof Proxy);
//这里可以看出proxy的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了Fly接口
System.out.println("proxy的Class类是:"+proxy.getClass().toString());
System.out.print("proxy中的属性有:");
Field[] field=proxy.getClass().getDeclaredFields();
for(Field f:field){
System.out.print(f.getName()+", ");
}
System.out.print("\n"+"proxy中的方法有:");
Method[] method=proxy.getClass().getDeclaredMethods();
for(Method m:method){
System.out.print(m.getName()+", ");
}
System.out.println("\n"+"proxy的父类是:"+proxy.getClass().getSuperclass());
System.out.print("\n"+"proxy实现的接口是:");
Class<?>[] interfaces=proxy.getClass().getInterfaces();
for(Class<?> i:interfaces){
System.out.print(i.getName()+", ");
}
System.out.println("\n\n"+"运行结果为:");
proxy.gotoFly();
}
}
打印结果:
被拦截了,鸟飞不走了。。。
若调用不拦截的方法,则会调用真实要调用的方法。
InvocationHandler中invoke()方法的调用问题
CGLIB动态代理demo
创建一个委托类Cat
//委托类
public class Cat {
public void run(){
System.out.println("猫可以跑。。。捉老鼠");
}
}
创建一个拦截器MethodInterceptorHandler
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class MethodInterceptorHandler implements MethodInterceptor{
/**
* 前三个参数同jdk方式
* methodProxy,委托类中的每一个被代理方法都对应一个MethodProxy对象
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//拦截方法
if (method.getName().equals("run1")) {
System.out.println("cat的run方法被拦截了。。。。");
return null;
}
// MethodProxy为cgli生成的对象,性能更高也体现在此
return methodProxy.invokeSuper(proxy, args);
}
}
测试类ProxyTest
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class ProxyTest {
@Test
public void demo2() {
// cglib 动态代理在目标业务类没有实现接口的情况下
// 1、创建真实业务类的子类
// cglib自带的字节码增强器
Enhancer enhancer = new Enhancer();
// 2、将委托类设置成父类
enhancer.setSuperclass(Cat.class);
MethodInterceptor methodInterceptor = new MethodInterceptorHandler();
// 3、设置回调函数、拦截器
enhancer.setCallback(methodInterceptor);
Cat proxy = (Cat) enhancer.create();
proxy.run();
}
}
打印
cat的run方法被拦截了。。。。