一、什么是代理模式?
代理模式就是给某个对象提供一个代理对象,由代理对象控制对原对象的控制,访问原对象的方法。
二、Spring AOP的理解
AOP:Aspect Oriented Programing,面向切面编程,是Spring的两大核心思想之一,是一种编程的思想,也是对面向对象思想不足的补充和扩展,因为万物皆对象的这种思想,将现实世界所有的事物映射为Java世界中的一个个对象,可以看作是纵向在设计一个个类,而当这些类都需要一些公共的实现代码时,比如说日志、权限,抽象类或接口的设计已经不太能满足这个需求了,因为Java是单继承,而且使用抽象类或接口会使整个结构变得更复杂,所以出现了AOP,面向切面编程,横向去将这些类的公共代码(系统代码)提炼出来,使用代理和反射的机制交由代理对象去实现,保证了原业务代码的纯粹性,也提高了系统的可维护性、可扩展性。
三、Spring AOP中常见通知的类型
- Before:前置通知
- After:后置通知
- AfterReturning:有返回值的后置通知,当方法正常执行后,执行该通知;如果方法执行时出现异常,则不执行该通知
- AfterThrowing:异常通知,方法出现异常时,执行该通知
- Around:环绕通知
四、Spring AOP的实现
-
使用Spring AOP
📗 第一步,定义通知类,继承相关接口,例如:MethodBeforeAdvice,AfterAdvice,AfterReturningAdvice,ThrowsAdvice,MethodInterceptor,重写或自定义相关方法。
📘 第二步:配置XML:
<!-- 大前提 --> <!-- 通知类 --> <bean id="logAdviceBean" class="com.apesource.advice.LogAdvice"/> <!-- 目标类 --> <bean id="homeControllerBean" class="com.apesource.web.controller.HomeController"/>
形式1:手动代理:使用ProxyFactoryBean手动为目标类创建代理类。
缺点:每个目标类都要写一个Bean,很繁琐,在获取Bean的时候需要获取代理类Bean。
<!-- 手动代理 --> <bean id="homeControllerProxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 注入目标类 --> <property name="target" ref="homeControllerBean"/> <!-- 注入通知类 --> <property name="interceptorNames" value="logAdviceBean"/> </bean>
形式2:自动代理:使用BeanNameAutoProxyCreator自动为所匹配的Bean创建代理类。
优点:简洁,在获取Bean的时候直接获取目标类的Bean,Spring会自动地创建代理类对象并返回。
<!-- 自动代理(匿名Bean) --> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <!-- 注入符合表达式的目标类 --> <property name="beanNames" value="*ControllerBean"/> <!-- 注入通知类 --> <property name="interceptorNames" value="logAdviceBean"/> </bean>
📙 注入的通知类类型:
-
普通的通知类:类级别的通知,会使注入该通知的类的所有方法都执行该通知,颗粒度太大。
<bean id="logAdviceBean" class="com.apesource.advice.LogAdvice"/>
-
高级的通知类:Advisor切面,方法级别的通知,可以具体到某一个符合规则的方法,颗粒度小。
实现方式1:
<!-- 实现方式1 --> <!-- 1.1切入点(Pointcut):通过正则表达式描述指定切入点 --> <bean id="createMethodPointcutBean" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <!-- 描述符合表达式的方法 --> <property name="pattern" value=".*create.*"/> </bean> <!-- 和1.1类似的实现 --> <!-- <bean id="controllerLogMethodPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">--> <!-- 描述符合表达式的方法 --> <!--<property name="mappedName" value="*create*"/>--> <!--</bean>--> <!-- 1.2 --> <!-- Advisor = Advice(通知) + Pointcut(切入点) --> <bean id="performanceAdvisorBean" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <!--注入切入点--> <property name="pointcut" ref="createMethodPointcutBean"/> <!--注入通知--> <property name="advice" ref="performanceBean"/> </bean>
实现方式2:一步到位
<!-- 实现方式2 --> <bean class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <!-- 通过正则表达式描述切入点,并注入 --> <property name="mappedName" value="*create*"/> <!--注入通知--> <property name="advice" ref="logAdviceBean"/> </bean>
-
-
使用AspectJ框架:
方式1:使用POJO+XML的形式
📗 第一步,自定义通知类,自定义通知方法。
public class LogAdvice{ private Logger logger = Logger.getLogger(LogAdvice.class.getName()); public void beginLogging(JoinPoint jp){ logger.info("日志开始"); } public void endLogging(JoinPoint jp){ logger.info("日志结束"); } }
📘 第二步,进行XML配置
1.1 首先加入aop的命名空间配置
<beans xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
1.2 定义通知类
<bean id="logAdvice" class="com.apesource.advice.LogAdvice"/>
1.3 进行aop配置
<!-- 顶层aop配置 --> <aop:config> <!-- 定义切面,注入通知类 --> <aop:aspect ref="logAdvice"> <!--定义切点--> <aop:pointcut id="adminControllerPointcut" expression="execution(* com.apesource.web.controller.*.handler*(..))"/> <!-- 配置前置同通知 --> <aop:before method="beginLogging" pointcut-ref="adminControllerPointcut"/> <!-- 配置后置通知 --> <aop:after method="endLogging" pointcut-ref="adminControllerPointcut"/> </aop:aspect> </aop:config>
方式2:使用纯注解
📗 第一步,自定义通知类,使用相关注解,并将通知类交给容器管理
@Aspect // 声明为一个切面类 @Component public class LogAdvice2 { private Logger logger = Logger.getLogger(LogAdvice.class.getName()); @Before("execution(* com.apesource.web.controller.*.handler*(..))") public void beginLogging(JoinPoint jp){ logger.info("日志开始"); } @After("execution(* com.apesource.web.controller.*.handler*(..))") public void endLogging(JoinPoint jp){ logger.info("日志结束"); } }
📘 第二步,开启AspectJ自动代理
@ComponentScan // 默认扫描同包之下 @EnableAspectJAutoProxy public class SystemConfig { }
相关注解:
@Aspect
@EnableAspectJAutoProxy
@Pointcut
@Before
@After
@AfterReturning
@AfterThrowing
@Around