AOP的概述
什麼是AOP
AOP基本概念ASpect Oriented Programming 意爲:面向切面編程
通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術,AOP是OOP,函數式編程的衍生範型,利用AOP可以對業務邏輯各個部分進行隔離,採用了橫向抽取機制,取代了傳統縱向繼承體系重複性代碼(性能監視、事務管理、安全檢查、緩存)
public class UserDaoImpl umplements UserDao{
public void save(User user){
checkPrivilege();
//保存用戶
}
public void update(User user){
//更新用戶
}
public void delete(User user){
//刪除用戶
}
public void find(User user){
//查詢用戶
}
public void checkPrivilege(){}
}
||
||
縱向繼承
public class BaseDaoImpl{
public void checkPrivilege(){}
}
public class UserDaoImpl extends BaseDaoImpl{
public void save(User user){
checkPrivilege();
//保存用戶
}
}
||
||
AOP採用橫向抽取機制——----代理機制
public class UserDaoImpl implements UserDao{
public void save(User user){
//保存用戶
}
}
Spring AOP 使用純Java實現,不需要專門的編譯過程和類加載器,在運行期間通過代理方式向目標類植入增強代碼
AOP的底層實現
Spring的傳統AOP
不帶切入點的切面
帶有切入點的切面
Spring的傳統AOP的自動代理
基於Bean名稱的自動代理
基於切面信息的自動代理
AOP相關術語
1、連接點joinpoint :所謂連接點是指那些被攔截到的點。
在Spring中,這些點指的是方法因爲Spring只支持方法類型的連接點
2、Pointcut切入點:所謂切入點是指我們要對哪些jointpoint進行攔截的定義
3、Advice(通知/增強):所謂通知是指攔截到joinpoint之後所要做的事情,就是通知,通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知(切面要完成的功能)
4、Introduction引介:引介是一種特殊的通知在不修改類代碼的前提下,Introduction可以在運行期間爲類動態的添加一些方法或Field(屬性)
5、Target(目標對象):代理的目標對象
6、Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程
7、Spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入
8、Proxy代理:一個類被AOP織入增強後,就會產生一個結果代理類
9、Aspect切面:是切入點和通知(引介)的結合
public class UserDaoImpl umplements UserDao{
public void save(User user){
checkPrivilege();
//保存用戶
}
public void update(User user){
//更新用戶
}
public void delete(User user){
//刪除用戶
}
public void find(User user){
//查詢用戶
}
public void checkPrivilege(){}
}
名詞都學完了是不是很無趣?
那如何纔會有趣呢?
我們開始實現上面的概念吧
首先
創建項目
項目名稱Spring-aop
具體創建流程
點擊詳見
接下來我們今天這篇文章會用到的包結構
如圖所示
ok,創建好對應的包後
pom文件和之前的pom文件類似,在此不再贅述
我們首先實現第一部分的代碼實現。
AOP底層實現原理
1、JDK動態代理
2、CGLIB生成代理
對於不使用接口的業務類,無法使用JDK動態代理
CGlib採用非常底層字節碼技術,可以作爲一個類創建子類,解決無接口代理問題
Spring在運行期,生成動態代理對象,不需要特殊的編譯期
SpringAOP的底層就是通過JDK動態代理或CGLib動態代理技術爲目標Bean執行橫向織入
1、若目標對象實現了若干接口,Spring使用JDK的java.lang.reflect.Proxy類代理
2、若目標對象沒有實現任何接口,Spring使用CGLIB庫生成目標對象的子類
程序中應優先對接口創建代理,便於程序解耦維護
標記爲final的方法,不能被代理因爲無法進行覆蓋
–JDK動態代理,是針對接口生成子類,接口中方法不能使用final修飾
–CGLib是針對目標類生產子類,因此類或方法不能使用final
Spring只支持方法連接點,不提供屬性連接點。
首先是1、JDK動態代理的實現
我們在demo1內創建如下的類
首先是MyJDKProxy這個類的詳細代碼
public class MyJdkProxy implements InvocationHandler{
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao=userDao;
}
public Object createProxy(){
Object proxy= Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("權限校驗");
return method.invoke(userDao,args);
}
return method.invoke(userDao,args);
}
}
接下來是UserDao和其實現
public interface UserDao{
public void save();
public void delete();
public void update();
public void find();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存用戶");
}
@Override
public void delete() {
System.out.println("刪除用戶");
}
@Override
public void update() {
System.out.println("更新用戶");
}
@Override
public void find() {
System.out.println("查詢用戶");
}
}
對應測試類的代碼
public class SpringDemo1 {
@Test
public void demo1(){
UserDao userDao=new UserDaoImpl();
UserDao proxy=(UserDao)(new MyJdkProxy(userDao).createProxy());
userDao.save();
userDao.delete();
userDao.find();
userDao.update();
System.out.println("++++++++==========");
proxy.delete();
proxy.save();
proxy.find();
proxy.update();
}
}
接下來是2、CGLIB生成代理
在demo2中的結構
首先是MyCglibProxy
public class MyCglibProxy implements MethodInterceptor {
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao){
this.productDao=productDao;
}
public Object createProxy(){
//1、創建核心類
Enhancer enhancer=new Enhancer();
//2、設置父類
enhancer.setSuperclass(productDao.getClass());
//3、設置回調
enhancer.setCallback(this);
//4、生成代理
Object proxy= enhancer.create();
return proxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("save增強");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
productDao
public class ProductDao {
public void save() {
System.out.println("保存用戶");
}
public void delete() {
System.out.println("刪除用戶");
}
public void update() {
System.out.println("更新用戶");
}
public void find() {
System.out.println("查詢用戶");
}
}
對應測試類
public class SpringDemo2 {
@Test
public void demo1(){
ProductDao productDao=new ProductDao();
productDao.delete();
productDao.save();
productDao.update();
productDao.find();
System.out.println("=================");
ProductDao proxy=(ProductDao)new MyCglibProxy(productDao).createProxy();
proxy.delete();
proxy.save();
proxy.find();
proxy.update();
}
}
接下來就是對AOP中各種類型的實現了,我們來接着看下
AOP增強類型Advice
前置通知org.springframework.aop.MethodBeforeAdvice
在目標方法執行之前實施增強
後置通知org.springframework.aop.AfterReturningAdvice
在目標方法執行後實施增強
環繞通知org.aopalliance.intercept.MethodInterceptor
在目標方法執行前後實施增強
異常拋出通知org.springframework.aop.ThrowsAdvice
在方法拋出異常後實施增強
引介通知org.springframework.aop.IntroductionInterceptor
在目標類中添加一些新的方法和屬性
各個對應的代碼實現,代碼較多,推薦仔細看完一個然後後面略看
前置通知org.springframework.aop.MethodBeforeAdvice
在目標方法執行之前實施增強
在demo3下和resource下都要建立文件啦
這次我們要配置Spring了
首先是studentDao的兩個的代碼
public interface StudentDao {
public void find();
public void delete();
public void update();
public void save();
}
public class StudentDaoImpl implements StudentDao {
@Override
public void find() {
System.out.println("學生查詢");
}
@Override
public void delete() {
System.out.println("學生刪除");
}
@Override
public void update() {
System.out.println("學生更新");
}
@Override
public void save() {
System.out.println("學生保存");
}
}
接下來是我們前置通知類
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置-----通知、增強");
}
}
對應Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目標類-->
<bean id="studentDao" class="com.jjyu.aop.demo3.StudentDaoImpl"/>
<!--前置通知類型-->
<bean id="myBeforeAdvice" class="com.jjyu.aop.demo3.MyBeforeAdvice"/>
<!--SpringAop產生代理對象-->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置目標類-->
<property name="target" ref="studentDao"/>
<!--實現的接口-->
<property name="proxyInterfaces" value="com.jjyu.aop.demo3.StudentDao"/>
<!--採用攔截的名稱-->
<property name="interceptorNames" value="myBeforeAdvice"/>
<property name="optimize" value="true"/>
</bean>
</beans>
然後是測試代碼和測試效果
我們使用註解的方式來配置測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
// @Resource(name="studentDao")
@Resource(name = "studentDaoProxy")
private StudentDao studentDao;
@Test
public void demo1(){
studentDao.delete();
studentDao.find();
studentDao.update();
studentDao.save();
}
}
環繞通知org.aopalliance.intercept.MethodInterceptor
在目標方法執行前後實施增強
建立第二個Spring配置文件
public class CustomerDao {
public void find(){
System.out.println("查詢客戶");
}
public void save(){
System.out.println("保存客戶");
}
public void update(){
System.out.println("修改客戶");
}
public void delete(){
System.out.println("刪除客戶");
}
}
//此爲環繞類
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("環繞前----------");
Object obj= methodInvocation.proceed();
System.out.println("環繞後----------");
return null;
}
}
Spring文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置目標類-->
<bean id="customerDao" class="com.jjyu.aop.demo4.CustomerDao"/>
<!--配置通知-->
<bean id="myAroundAdvice" class="com.jjyu.aop.demo4.MyAroundAdvice"/>
<!--一般的切面是使用通知作爲切面的,因爲要對目標類的某個方法進行增強,需要配置一個帶有切入點的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正則表達式-->
<!--<property name="pattern" value=".*save.*"/>-->
<property name="patterns" value=".*save.*,.*delete"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<!--配置產生代理-->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"/>
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="myAdvisor"/>
</bean>
</bean
對應的測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo4 {
// @Resource(name = "customerDao")
@Resource(name = "customerDaoProxy")
private CustomerDao customerDao;
@Test
public void demo1(){
customerDao.delete();
System.out.println();
customerDao.find();
System.out.println();
customerDao.save();
System.out.println();
customerDao.update();
}
}
Advisor:代表一般切面,Advice本身就是一個切面,對目標類所有方法進行攔截
PointcutAdvisor:代表具有切點的切面,可以指定攔截目標類哪些方法
IntroductionAdvisor:代表引介切面,針對引介通知而使用切面
ProxyFactoryBean常用可配置屬性:
–target:代理的目標對象
–ProxyInterface:代理要實現的接口
若是多個接口用
–proxyTargetClass:是否對類代理而不是接口,設置爲true時,使用CGLib代理
–InterceptorNames:需要織入目標的Advice
–singleton:返回代理是否爲單實例,默認爲單例
–optimize:當設置爲true,強制使用CGLib
PointcutAdvisor:代表具有切點的切面,可以指定攔截目標類哪些方法
其配置
DefaultPointcutAdisor最常用的切面類型,其可以通過任意Pointcut和Advice組合定義切面
jdkRegexpMethodPointcut構造正則表達式切點
自動創建代理
–BeanNameAutoProxyCreator根據Bean名稱創建代理
對所有以DAO結尾Bean的所有方法進行代理
–DefaultAdvisorAutoProxyCreator根據Advisor本身包含信息創建代理
對某些類裏面的某些方法進行代理
–AnnotationAwareAspectJAutoProxyCreator基於Bean中的AspectJ註解進行自動代理
接下來是自動創建代理了
我們demo5 和demo6就是將demo3+demo4的代碼copy一下,一樣的哈
一個前置通知一個環繞通知還有三個用於測試的類
除了測試類,
1、BeanNameAutoProxyCreator根據Bean名稱創建代理
對應的Spring核心配置文件applicationContext3.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentDao" class="com.jjyu.aop.demo5.StudentDaoImpl"/>
<bean id="customerDao" class="com.jjyu.aop.demo5.CustomerDao"/>
<!--配置增強-->
<bean id="myBeforeAdvice" class="com.jjyu.aop.demo5.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.jjyu.aop.demo5.MyAroundAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
</beans>
demo5的測試方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringDemo5 {
@Resource(name ="studentDao" )
private StudentDao studentDao;
@Resource(name ="customerDao" )
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.delete();
System.out.println();
studentDao.find();
System.out.println();
studentDao.save();
System.out.println();
studentDao.update();
System.out.println();
customerDao.delete();
System.out.println();
customerDao.find();
System.out.println();
customerDao.save();
System.out.println();
customerDao.update();
}
}
2、DefaultAdvisorAutoProxyCreator根據Advisor本身包含信息創建代理
對應的Spring核心配置文件applicationContext4.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentDao" class="com.jjyu.aop.demo6.StudentDaoImpl"/>
<bean id="customerDao" class="com.jjyu.aop.demo6.CustomerDao"/>
<!--配置增強-->
<bean id="myBeforeAdvice" class="com.jjyu.aop.demo6.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.jjyu.aop.demo6.MyAroundAdvice"/>
<!--配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com\.jjyu\.aop\.demo6\.CustomerDao\.save"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
</beans>
demo5的測試方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class SpringDemo6 {
@Resource(name ="studentDao" )
private StudentDao studentDao;
@Resource(name ="customerDao" )
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.delete();
System.out.println();
studentDao.find();
System.out.println();
studentDao.save();
System.out.println();
studentDao.update();
System.out.println();
customerDao.delete();
System.out.println();
customerDao.find();
System.out.println();
customerDao.save();
System.out.println();
customerDao.update();
}
}
AOP的第一part。
下一步分鏈接在—》總章中
總章在此。