Spring AOP在web應用中的使用方法例項
前言
之前的aop是通過手動建立代理類來進行通知的,但是在日常開發中,我們並不願意在程式碼中硬編碼這些代理類,我們更願意使用DI和IOC來管理aop代理類。Spring為我們提供了以下方式來使用aop框架
一、以宣告的方式配置AOP(就是使用xml配置檔案)
1.使用ProxyFactoryBean的方式:
ProxyFactoryBean類是FactoryBean的一個實現類,它允許指定一個bean作為目標,並且為該bean提供一組通知和顧問(這些通知和顧問最終會被合併到一個AOP代理中)它和我們之前的ProxyFactory都是Advised的實現。
以下是一個簡單的例子:一個學生和一個老師,老師會告訴學生應該做什麼。
public class Student { public void talk() { System.out.println("I am a boy"); } public void walk() { System.out.println("I am walking"); } public void sleep() { System.out.println("I want to sleep"); } }
老師類
public class Teacher { private Student student; public void tellStudent(){ student.sleep(); student.talk(); } public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } }
我們建立一個通知類,這個和之前是一樣的SpringAOP中的通知型別以及建立
package cn.lyn4ever.aop; import org.aspectj.lang.JoinPoint; public class AuditAdvice implements MethodBeforeAdvice { @Override public void before(Method method,Object[] objects,@Nullable Object o) throws Throwable { System.out.println("這個方法被通知了" + method.getName()); } }
然後就使用spring的IOC來管理這個通知類,在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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <!--注入student--> <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student"> </bean> <!--注入teacher--> <bean name="teacher" class="cn.lyn4ever.aop.aopconfig.Teacher"> <!--注意,這個student的屬性要是上邊的代理類,而不是能student--> <!--<property name="student" ref="student"/>--> <property name="student" ref="proxyOne"/> </bean> <!--注入我們建立的通知類--> <bean id="advice" class="cn.lyn4ever.aop.aopconfig.AuditAdvice"></bean> <!--建立代理類,使用前邊寫的通知進行通知,這樣會使這個類上的所有方法都被通知--> <bean name="proxyOne" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="student" p:interceptorNames-ref="interceptorNames"> <!--因為interceptorNames的屬性是一個可變引數,也就是一個list--> </bean> <!--在上邊引入了util的名稱空間,簡化了書寫--> <util:list id="interceptorNames"> <value>advice</value> </util:list> </beans>
測試類
public static void main(String[] args) { GenericXmlApplicationContext context = new GenericXmlApplicationContext(); context.load("application1.xml"); context.refresh(); Teacher teacher = (Teacher) context.getBean("teacherOne"); teacher.tellStudent(); }
執行結果沒有問題
以上是通過直接建立通知的方式,接下來我們試一個建立一個切入點(因為以上是對類中所有方法都進行通知,這時我們使用切入點只對其中部分方法進行通知),在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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <!--注入student--> <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student"> </bean> <!--注入teacher--> <bean name="teacherOne" class="cn.lyn4ever.aop.aopconfig.Teacher"> <!--注意,簡化了書寫--> <util:list id="interceptorNames"> <value>advice</value> </util:list> <!--以下是使用切入點的方式來進行通知,上邊的程式碼和上一個配置檔案一樣,沒有修改--> <!--sutdent基本bean,我們繼續使用--> <bean name="teacherTwo" p:student-ref="proxyTwo" class="cn.lyn4ever.aop.aopconfig.Teacher"/> <bean id="proxyTwo" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="student" p:interceptorNames-ref="interceptorAdvisorNames"> </bean> <util:list id="interceptorAdvisorNames"> <value>advisor</value> </util:list> <!--配置切入點bean--> <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" p:advice-ref="advice"> <property name="pointcut"> <!--這個切入點我們用了一個匿名bean來寫aspectJ的表示式,當然也可以用其他的型別切入點,這個在上邊連結中能看到--> <bean class="org.springframework.aop.aspectj.AspectJExpressionPointcut" p:expression="execution(* talk*(..))"/> </property> </bean> </beans>
上圖中的那個aspectj表示式寫錯了,在程式碼中有正確的
2.使用aop名稱空間
在xml中引入如下的名稱空間,為了不被影響,我冊了其他多餘的名稱空間。然後很普通地注入我們之前那三個bean
<?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--> <!--注入student--> <bean name="student" class="cn.lyn4ever.aop.aopconfig.Student"/> <!--注入teacher--> <bean name="teacherOne" class="cn.lyn4ever.aop.aopconfig.Teacher"> <property name="student" ref="student"/> </bean> <!--注入我們建立的通知類--> <bean id="advice" class="cn.lyn4ever.aop.proxyfactory.BeforeAdvice"/> <aop:config> <aop:pointcut id="talkExecution" expression="execution(* talk*(..))"/> <aop:aspect ref="advice"> <!--這個方法就是我們在自定義通知類中之寫的方法--> <aop:before method="beforeSaySomething" pointcut-ref="talkExecution"/> <!--當然,還可以配置其他通知型別--> </aop:aspect> </aop:config> </beans>
在這個配置中,我們還可以配置其他型別的通知,但是這個method屬性一定要寫我們自定義的那個通知類中的方法
在aop:pointcut中寫expression時還支援如下語法:
<aop:pointcut id="talkExecution" expression="execution(* talk*(..)) and args(String) and bean(stu*)"/> <!-- 中間的這個and表示和,也可以用or來表示或 args(String) 意思是引數型別是string,也可是自定義的類,這個後邊有例子 bean(stu*) 意思是bean的id是以stu開頭的,常用的就是用bean(*Service*)來表示服務層的bean -->
3.使用@AspectJ樣式註解方式
雖然是通過註解的方式來宣告註解類,但是還是需要在xml中配置一點點內容(通過註解的方式也可以配置,但是在springboot中要使用的話有更方便的方式)
為了方便,就只寫了一個HighStudent,而且直接呼叫它的方法,不依賴於外部的teacher例項來呼叫
package cn.lyn4ever.aop.aspectj; import cn.lyn4ever.aop.aopconfig.Teacher; import org.springframework.stereotype.Component; /** * 宣告這是一個SpringBean,由Spring來管理它 */ @Component public class HighStudent { public void talk() { System.out.println("I am a boy"); } public void walk() { System.out.println("I am walking"); } /** * 這個方法新增一個teacher來做為引數,為了配置後邊切入點中的args() * @param teacher */ public void sleep(Teacher teacher) { System.out.println("I want to sleep"); } }
建立切面類
package cn.lyn4ever.aop.aspectj; import cn.lyn4ever.aop.aopconfig.Teacher; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; 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 //宣告交由spring管理 @Aspect //表示這是一個切面類 public class AnnotatedAdvice { /* 建立切入點,當然也可以是多個 */ @Pointcut("execution(* talk*(..))") public void talkExecution(){} @Pointcut("bean(high*)")//這裡為什麼是high,因為我們這回測試bean是highStudent public void beanPoint(){} @Pointcut("args(value)") public void argsPoint(Teacher value){} /* 建立通知,當然也可以是多個 這個註解的引數就是上邊的切入點方法名,注意有的還帶引數 這個通知方法的引數和之前一樣,榀加JoinPoint,也可不加 */ @Before("talkExecution()") public void doSomethingBefore(JoinPoint joinPoint){ System.out.println("before: Do Something"+joinPoint.getSignature().getName()+"()"); } /** * 環繞通知請加上ProceedingJoinPoint引數,它是joinPoint的子類 * 因為你要放行方法的話,必須要加這個 * @param joinPoint * @param teacher */ @Around("argsPoint(teacher) && beanPoint()") public Object doSomethindAround(ProceedingJoinPoint joinPoint,Teacher teacher) throws Throwable { System.out.println("Around: Before Do Something"+joinPoint.getSignature().getName()+"()"); Object proceed = joinPoint.proceed(); System.out.println("Around: After Do Something"+joinPoint.getSignature().getName()+"()"); return proceed; } }
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" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--通知Spring掃描@Aspect註解--> <aop:aspectj-autoproxy/> <!--配置掃描包,掃描@Component--> <context:component-scan base-package="cn.lyn4ever.aop.aspectj"/> </beans>
使用Java註解配置的方式配置掃描註解
@Configuration //宣告這是一個配置類 @ComponentScan("cn.lyn4ever.aop.aspectj") @EnableAspectJAutoProxy(proxyTargetClass = true)//相當於xml中的<aop:aspectj-autoproxy/> public class BeanConfig { }
測試方法
package cn.lyn4ever.aop.aspectj; import cn.lyn4ever.aop.aopconfig.Teacher; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; public class AspectMain { public static void main(String[] args) { // xmlConfig(); javaConfig(); } private static void javaConfig() { GenericApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class); HighStudent student = (HighStudent) context.getBean("highStudent"); student.sleep(new Teacher());//應該被環繞通知 System.out.println(); student.talk();//前置通知 System.out.println(); student.walk();//不會被通知 System.out.println(); } private static void xmlConfig(){ GenericXmlApplicationContext context = new GenericXmlApplicationContext(); context.load("application_aspect.xml"); context.refresh(); HighStudent student = (HighStudent) context.getBean("highStudent"); student.sleep(new Teacher());//應該被環繞通知 System.out.println(); student.talk();//前置通知 System.out.println(); student.walk();//不會被通知 System.out.println(); } }
專案程式碼地址,如果覺得還不錯的話,給個star吧
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支援。