aop原理是spring帮我们封装了动态代理,然后我们只管写具体的业务,我们将公共业务也写到具体的一个类中并实现spring为我们提供的对应要连接切入哪个位置的接口,然后再xml中配置它们的关系即可。
优点:和动态代理一样,具体实现只管具体实现使的代码更加纯粹,公共业务只需实现自己对应的接口,然后编码即可,有了动态代理的好处,又没有手写动态代理那么复杂,这就是aop(被分装后的动态代理)。
一:实现spring接口的方式
1.UserService接口:
package mr.li.service; public interface UserService { void add(); void remove(); }
2.UserServiceImpl实现类:
package mr.li.service.impl; import mr.li.service.UserService; public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("添加一条数据"); } @Override public void remove() { System.out.println("删除一条数据"); } }
3.前置通知:Log日志打印
package mr.li.log; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; /** * 前置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ public class Log implements MethodBeforeAdvice{ @Override public void before(Method method, Object[] arg1, Object target) throws Throwable { System.out.println("aop前置:"+target.getClass().getName()+"类的"+ method + "方法执行了~~"); } }
4后置通知:AfterLog 日志打印
package mr.li.log; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; /** * 后置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:44 */ public class AfterLog implements AfterReturningAdvice{ @Override public void afterReturning(Object result, Method method, Object[] arg2, Object target) throws Throwable { System.out.println("aop后置:"+target.getClass().getName()+"类的"+ method + "方法执行了,返回结果为"+result); } }
5.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"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <bean id = "afterLog" class="mr.li.log.AfterLog"/> <aop:config> <!-- 配置被切入的哪个类使用 execution表达式第一个:“*”表示返回值 第二个*表示所有的 ..标识所有的参数 --> <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="aoop"/> <!-- 设置要切入的类:--> <aop:advisor advice-ref="log" pointcut-ref="aoop"/> <aop:advisor advice-ref="afterLog" pointcut-ref="aoop"/> </aop:config> </beans>
6.测试类
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印结果:
aop前置:mr.li.service.impl.UserServiceImpl类的public abstract void mr.li.service.UserService.remove()方法执行了~~
删除一条数据
aop后置:mr.li.service.impl.UserServiceImpl类的public abstract void mr.li.service.UserService.remove()方法执行了,返回结果为null
aop前置:mr.li.service.impl.UserServiceImpl类的public abstract void mr.li.service.UserService.add()方法执行了~~
添加一条数据
aop后置:mr.li.service.impl.UserServiceImpl类的public abstract void mr.li.service.UserService.add()方法执行了,返回结果为null
二:自定义aop,切面不在需要实现接口,自己写即可,只是调整下配置
1.UserService:和上面一样
2.UserServiceImpl:和上面一样
3.Log切面
package mr.li.log; /** * 前置切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ public class Log { public void before() { System.out.println("前置通知"); } public void after() { System.out.println("后置通知"); } }
4.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"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <aop:config> <aop:aspect ref="log"> <!-- 配置被切入的哪个类使用 execution表达式 *表示所有的 ..标识所有的参数 --> <aop:pointcut expression="execution(* mr.li.service.impl.UserServiceImpl.*(..))" id="pointcut"/> <!-- 配置前置通知 --> <aop:before method="before" pointcut-ref="pointcut"/> <!-- 配置后置通知 --> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
5.测试
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印结果:
前置通知
删除一条数据
后置通知
前置通知
添加一条数据
后置通知
三:注解的方式:注意下环绕通知,参数说明,以及使用(proceedingJoinpoint在下面有额外解释)
1.UserService:和最上面一样
2.UserServiceImpl:和最上面一样
3.切面:Log
package mr.li.log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * 切面:日志打印 * @author yanLong.Li * @date 2019年3月16日 下午10:33:22 */ @Aspect public class Log { /** * 前置通知:execution表示切入点 */ @Before("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public void before() { System.out.println("注解:前置通知"); } /** * 后置通知 */ @After("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public void after() { System.out.println("注解:后置通知"); } /** * 环绕通知 * @param jp 此参数是spring给我们的,它里面可以干很多事,包括继续执行被切面的方法,拿到方法签名,他会在被切面的方法之前运作 * @throws Throwable */ @Around("execution(* mr.li.service.impl.UserServiceImpl.*(..))") public Object aroud(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("方法签名:"+jp.getSignature()); //执行此方法 Object result = jp.proceed(); System.out.println("环绕后"); return result; } }
4.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"> <bean id="userService" class="mr.li.service.impl.UserServiceImpl"/> <bean id = "log" class="mr.li.log.Log"/> <!-- 此配置会自动去找aop配置 --> <aop:aspectj-autoproxy/> </beans>
5.测试:
package mr.li.client; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import mr.li.service.UserService; public class Client { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); userService.remove(); userService.add(); } }
打印结果:
注解:前置通知
删除一条数据
环绕后
注解:后置通知
环绕前
方法签名:void mr.li.service.UserService.add()
注解:前置通知
添加一条数据
环绕后
注解:后置通知
额外对ProceedingJoinPoint参数的描述
package com.qty.arena.util; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import com.qty.arena.helper.MessagePushHelper; import com.qty.database.dto.arena.match.SyncAll; /** * 在被@NotDuplicate注解所标注的方法上,过滤请求者重复请求的消息 * @author yanLong.Li * @date 2019年2月11日 上午11:11:01 */ @Aspect @Component public class NotDuplicateAop { // private static final Set<String> KEY = new ConcurrentSkipListSet<>(); private static final Set<Long> KEY = new ConcurrentSkipListSet<>(); @Pointcut("@annotation(com.qty.arena.util.NotDuplicate)") public void duplicate() {} /** * 对方法拦截后进行参数验证 * @param pjp * @return * @throws Throwable */ @Around("duplicate()") public Object duplicate(ProceedingJoinPoint pjp) throws Throwable{ Object[] objs = pjp.getArgs(); Long dbId = null; if(objs[0] instanceof Long) { dbId = (Long)objs[0]; } if(dbId == null) { throw new NumberFormatException("在解析请求的dbId时发生异常,数字不能解析,值:"+objs[0]); } boolean success = KEY.add(dbId); if(!success){ MessagePushHelper.getInstance().pushSingleMessage(dbId, SyncAll.valueOf("频繁操作,请稍后再试")); return null; } try { //方法执行前 return pjp.proceed(); } finally { //方法执行后 KEY.remove(dbId); } }