Spring註解驅動:AOP與事務(二)

1.AOP

1.1 總結介紹

/**
 * AOP:【動態代理】
 * 		指在程序運行期間動態的將某段代碼切入到指定方法指定位置進行運行的編程方式;
 * 
 * 1、導入aop模塊;Spring AOP:(spring-aspects)
 * 2、定義一個業務邏輯類(MathCalculator);在業務邏輯運行的時候將日誌進行打印(方法之前、方法運行結束、方法出現異常,xxx)
 * 3、定義一個日誌切面類(LogAspects):切面類裏面的方法需要動態感知MathCalculator.div運行到哪裏然後執行;
 * 		通知方法:
 * 			前置通知(@Before):logStart:在目標方法(div)運行之前運行
 * 			後置通知(@After):logEnd:在目標方法(div)運行結束之後運行(無論方法正常結束還是異常結束)
 * 			返回通知(@AfterReturning):logReturn:在目標方法(div)正常返回之後運行
 * 			異常通知(@AfterThrowing):logException:在目標方法(div)出現異常以後運行
 * 			環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())
 * 4、給切面類的目標方法標註何時何地運行(通知註解);
 * 5、將切面類和業務邏輯類(目標方法所在類)都加入到容器中;
 * 6、必須告訴Spring哪個類是切面類(給切面類上加一個註解:@Aspect)
 * [7]、給配置類中加 @EnableAspectJAutoProxy 【開啓基於註解的aop模式】
 * 		在Spring中很多的 @EnableXXX;
 * 
 * 三步:
 * 	1)、將業務邏輯組件和切面類都加入到容器中;告訴Spring哪個是切面類(@Aspect)
 * 	2)、在切面類上的每一個通知方法上標註通知註解,告訴Spring何時何地運行(切入點表達式)
 *  3)、開啓基於註解的aop模式;@EnableAspectJAutoProxy
 *  
 * AOP原理:【看給容器中註冊了什麼組件,這個組件什麼時候工作,這個組件的功能是什麼?】
 * 		@EnableAspectJAutoProxy;
 * 1、@EnableAspectJAutoProxy是什麼?
 * 		@Import(AspectJAutoProxyRegistrar.class):給容器中導入AspectJAutoProxyRegistrar
 * 			利用AspectJAutoProxyRegistrar自定義給容器中註冊bean;BeanDefinetion
 * 			internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator
 * 
 * 		給容器中註冊一個AnnotationAwareAspectJAutoProxyCreator;
 * 
 * 2、 AnnotationAwareAspectJAutoProxyCreator:
 * 		AnnotationAwareAspectJAutoProxyCreator
 * 			->AspectJAwareAdvisorAutoProxyCreator
 * 				->AbstractAdvisorAutoProxyCreator
 * 					->AbstractAutoProxyCreator
 * 							implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
 * 						關注後置處理器(在bean初始化完成前後做事情)、自動裝配BeanFactory
 * 
 * AbstractAutoProxyCreator.setBeanFactory()
 * AbstractAutoProxyCreator.有後置處理器的邏輯;
 * 
 * AbstractAdvisorAutoProxyCreator.setBeanFactory()-》initBeanFactory()
 * 
 * AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()
 *
 *
 * 流程:
 * 		1)、傳入配置類,創建ioc容器
 * 		2)、註冊配置類,調用refresh()刷新容器;
 * 		3)、registerBeanPostProcessors(beanFactory);註冊bean的後置處理器來方便攔截bean的創建;
 * 			1)、先獲取ioc容器已經定義了的需要創建對象的所有BeanPostProcessor
 * 			2)、給容器中加別的BeanPostProcessor
 * 			3)、優先註冊實現了PriorityOrdered接口的BeanPostProcessor;
 * 			4)、再給容器中註冊實現了Ordered接口的BeanPostProcessor;
 * 			5)、註冊沒實現優先級接口的BeanPostProcessor;
 * 			6)、註冊BeanPostProcessor,實際上就是創建BeanPostProcessor對象,保存在容器中;
 * 				創建internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】
 * 				1)、創建Bean的實例
 * 				2)、populateBean;給bean的各種屬性賦值
 * 				3)、initializeBean:初始化bean;
 * 						1)、invokeAwareMethods():處理Aware接口的方法回調
 * 						2)、applyBeanPostProcessorsBeforeInitialization():應用後置處理器的postProcessBeforeInitialization()
 * 						3)、invokeInitMethods();執行自定義的初始化方法
 * 						4)、applyBeanPostProcessorsAfterInitialization();執行後置處理器的postProcessAfterInitialization();
 * 				4)、BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)創建成功;--》aspectJAdvisorsBuilder
 * 			7)、把BeanPostProcessor註冊到BeanFactory中;
 * 				beanFactory.addBeanPostProcessor(postProcessor);
 * =======以上是創建和註冊AnnotationAwareAspectJAutoProxyCreator的過程========
 * 
 * 			AnnotationAwareAspectJAutoProxyCreator => InstantiationAwareBeanPostProcessor
 * 		4)、finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;創建剩下的單實例bean
 * 			1)、遍歷獲取容器中所有的Bean,依次創建對象getBean(beanName);
 * 				getBean->doGetBean()->getSingleton()->
 * 			2)、創建bean
 * 				【AnnotationAwareAspectJAutoProxyCreator在所有bean創建之前會有一個攔截,InstantiationAwareBeanPostProcessor,會調用postProcessBeforeInstantiation()】
 * 				1)、先從緩存中獲取當前bean,如果能獲取到,說明bean是之前被創建過的,直接使用,否則再創建;
 * 					只要創建好的Bean都會被緩存起來
 * 				2)、createBean();創建bean;
 * 					AnnotationAwareAspectJAutoProxyCreator 會在任何bean創建之前先嚐試返回bean的實例
 * 					【BeanPostProcessor是在Bean對象創建完成初始化前後調用的】
 * 					【InstantiationAwareBeanPostProcessor是在創建Bean實例之前先嚐試用後置處理器返回對象的】
 * 					1)、resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation
 * 						希望後置處理器在此能返回一個代理對象;如果能返回代理對象就使用,如果不能就繼續
 * 						1)、後置處理器先嚐試返回對象;
 * 							bean = applyBeanPostProcessorsBeforeInstantiation():
 * 								拿到所有後置處理器,如果是InstantiationAwareBeanPostProcessor;
 * 								就執行postProcessBeforeInstantiation
 * 							if (bean != null) {
								bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
							}
 * 
 * 					2)、doCreateBean(beanName, mbdToUse, args);真正的去創建一個bean實例;和3.6流程一樣;
 * 					3)、
 * 			
 * 		
 * AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】	的作用:
 * 1)、每一個bean創建之前,調用postProcessBeforeInstantiation();
 * 		關心MathCalculator和LogAspect的創建
 * 		1)、判斷當前bean是否在advisedBeans中(保存了所有需要增強bean)
 * 		2)、判斷當前bean是否是基礎類型的Advice、Pointcut、Advisor、AopInfrastructureBean,
 * 			或者是否是切面(@Aspect)
 * 		3)、是否需要跳過
 * 			1)、獲取候選的增強器(切面裏面的通知方法)【List<Advisor> candidateAdvisors】
 * 				每一個封裝的通知方法的增強器是 InstantiationModelAwarePointcutAdvisor;
 * 				判斷每一個增強器是否是 AspectJPointcutAdvisor 類型的;返回true
 * 			2)、永遠返回false
 * 
 * 2)、創建對象
 * postProcessAfterInitialization;
 * 		return wrapIfNecessary(bean, beanName, cacheKey);//包裝如果需要的情況下
 * 		1)、獲取當前bean的所有增強器(通知方法)  Object[]  specificInterceptors
 * 			1、找到候選的所有的增強器(找哪些通知方法是需要切入當前bean方法的)
 * 			2、獲取到能在bean使用的增強器。
 * 			3、給增強器排序
 * 		2)、保存當前bean在advisedBeans中;
 * 		3)、如果當前bean需要增強,創建當前bean的代理對象;
 * 			1)、獲取所有增強器(通知方法)
 * 			2)、保存到proxyFactory
 * 			3)、創建代理對象:Spring自動決定
 * 				JdkDynamicAopProxy(config);jdk動態代理;
 * 				ObjenesisCglibAopProxy(config);cglib的動態代理;
 * 		4)、給容器中返回當前組件使用cglib增強了的代理對象;
 * 		5)、以後容器中獲取到的就是這個組件的代理對象,執行目標方法的時候,代理對象就會執行通知方法的流程;
 * 		
 * 	
 * 	3)、目標方法執行	;
 * 		容器中保存了組件的代理對象(cglib增強後的對象),這個對象裏面保存了詳細信息(比如增強器,目標對象,xxx);
 * 		1)、CglibAopProxy.intercept();攔截目標方法的執行
 * 		2)、根據ProxyFactory對象獲取將要執行的目標方法攔截器鏈;
 * 			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
 * 			1)、List<Object> interceptorList保存所有攔截器 5
 * 				一個默認的ExposeInvocationInterceptor 和 4個增強器;
 * 			2)、遍歷所有的增強器,將其轉爲Interceptor;
 * 				registry.getInterceptors(advisor);
 * 			3)、將增強器轉爲List<MethodInterceptor>;
 * 				如果是MethodInterceptor,直接加入到集合中
 * 				如果不是,使用AdvisorAdapter將增強器轉爲MethodInterceptor;
 * 				轉換完成返回MethodInterceptor數組;
 * 
 * 		3)、如果沒有攔截器鏈,直接執行目標方法;
 * 			攔截器鏈(每一個通知方法又被包裝爲方法攔截器,利用MethodInterceptor機制)
 * 		4)、如果有攔截器鏈,把需要執行的目標對象,目標方法,
 * 			攔截器鏈等信息傳入創建一個 CglibMethodInvocation 對象,
 * 			並調用 Object retVal =  mi.proceed();
 * 		5)、攔截器鏈的觸發過程;
 * 			1)、如果沒有攔截器執行執行目標方法,或者攔截器的索引和攔截器數組-1大小一樣(指定到了最後一個攔截器)執行目標方法;
 * 			2)、鏈式獲取每一個攔截器,攔截器執行invoke方法,每一個攔截器等待下一個攔截器執行完成返回以後再來執行;
 * 				攔截器鏈的機制,保證通知方法與目標方法的執行順序;
 * 		
 * 	總結:
 * 		1)、  @EnableAspectJAutoProxy 開啓AOP功能
 * 		2)、 @EnableAspectJAutoProxy 會給容器中註冊一個組件 AnnotationAwareAspectJAutoProxyCreator
 * 		3)、AnnotationAwareAspectJAutoProxyCreator是一個後置處理器;
 * 		4)、容器的創建流程:
 * 			1)、registerBeanPostProcessors()註冊後置處理器;創建AnnotationAwareAspectJAutoProxyCreator對象
 * 			2)、finishBeanFactoryInitialization()初始化剩下的單實例bean
 * 				1)、創建業務邏輯組件和切面組件
 * 				2)、AnnotationAwareAspectJAutoProxyCreator攔截組件的創建過程
 * 				3)、組件創建完之後,判斷組件是否需要增強
 * 					是:切面的通知方法,包裝成增強器(Advisor);給業務邏輯組件創建一個代理對象(cglib);
 * 		5)、執行目標方法:
 * 			1)、代理對象執行目標方法
 * 			2)、CglibAopProxy.intercept();
 * 				1)、得到目標方法的攔截器鏈(增強器包裝成攔截器MethodInterceptor)
 * 				2)、利用攔截器的鏈式機制,依次進入每一個攔截器進行執行;
 * 				3)、效果:
 * 					正常執行:前置通知-》目標方法-》後置通知-》返回通知
 * 					出現異常:前置通知-》目標方法-》後置通知-》異常通知
 * 		
 * 
 * 
 */

1.2 舉例

比如,給一個類的所有方法,增加AOP切面

1.添加AOP依賴

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.3.12.RELEASE</version>
		</dependency>

2.創建業務類和需要綁定的切面類

//業務類
public class MathCalculator {
	public int div(int i,int j){
		System.out.println("MathCalculator...div...");
		return i/j;	
	}
}
/**
 * 切面類
 *
 * @Aspect: 告訴Spring當前類是一個切面類
 *
 */
@Aspect
public class LogAspects {

	//抽取公共的切入點表達式
	//1、本類引用
	//2、其他的切面引用
	@Pointcut("execution(public int com.zy.aop.MathCalculator.*(..))")
	public void pointCut(){};

	//@Before在目標方法之前切入;切入點表達式(指定在哪個方法切入)
	@Before("pointCut()")
	public void logStart(JoinPoint joinPoint){
		Object[] args = joinPoint.getArgs();
		System.out.println(""+joinPoint.getSignature().getName()+"運行。。。@Before:參數列表是:{"+Arrays.asList(args)+"}");
	}

	@After("com.zy.aop.LogAspects.pointCut()")
	public void logEnd(JoinPoint joinPoint){
		System.out.println(""+joinPoint.getSignature().getName()+"結束。。。@After");
	}

	//JoinPoint一定要出現在參數表的第一位
	@AfterReturning(value="pointCut()",returning="result")
	public void logReturn(JoinPoint joinPoint,Object result){
		System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:運行結果:{"+result+"}");
	}

	@AfterThrowing(value="pointCut()",throwing="exception")
	public void logException(JoinPoint joinPoint,Exception exception){
		System.out.println(""+joinPoint.getSignature().getName()+"異常。。。異常信息:{"+exception+"}");
	}

}

3.在配置類裏注入到Spring容器中

@EnableAspectJAutoProxy
@Configuration
public class AopConfig {
    //業務邏輯類加入容器中
    @Bean
    public MathCalculator calculator(){
        return new MathCalculator();
    }

    //切面類加入到容器中
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

4.測試

    @Test
    public void testAop(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        mathCalculator.div(3, 2);
        applicationContext.close();
    }

在這裏插入圖片描述

2.聲明式事務

2.1 總結介紹

/**
 * 聲明式事務:
 * 
 * 環境搭建:
 * 1、導入相關依賴
 * 		數據源、數據庫驅動、Spring-jdbc模塊
 * 2、配置數據源、JdbcTemplate(Spring提供的簡化數據庫操作的工具)操作數據
 * 3、給方法上標註 @Transactional 表示當前方法是一個事務方法;
 * 4、 @EnableTransactionManagement 開啓基於註解的事務管理功能;
 * 		@EnableXXX
 * 5、配置事務管理器來控制事務;
 * 		@Bean
 * 		public PlatformTransactionManager transactionManager()
 * 
 * 
 * 原理:
 * 1)、@EnableTransactionManagement
 * 			利用TransactionManagementConfigurationSelector給容器中會導入組件
 * 			導入兩個組件
 * 			AutoProxyRegistrar
 * 			ProxyTransactionManagementConfiguration
 * 2)、AutoProxyRegistrar:
 * 			給容器中註冊一個 InfrastructureAdvisorAutoProxyCreator 組件;
 * 			InfrastructureAdvisorAutoProxyCreator:?
 * 			利用後置處理器機制在對象創建以後,包裝對象,返回一個代理對象(增強器),代理對象執行方法利用攔截器鏈進行調用;
 * 
 * 3)、ProxyTransactionManagementConfiguration 做了什麼?
 * 			1、給容器中註冊事務增強器;
 * 				1)、事務增強器要用事務註解的信息,AnnotationTransactionAttributeSource解析事務註解
 * 				2)、事務攔截器:
 * 					TransactionInterceptor;保存了事務屬性信息,事務管理器;
 * 					他是一個 MethodInterceptor;
 * 					在目標方法執行的時候;
 * 						執行攔截器鏈;
 * 						事務攔截器:
 * 							1)、先獲取事務相關的屬性
 * 							2)、再獲取PlatformTransactionManager,如果事先沒有添加指定任何transactionmanger
 * 								最終會從容器中按照類型獲取一個PlatformTransactionManager;
 * 							3)、執行目標方法
 * 								如果異常,獲取到事務管理器,利用事務管理回滾操作;
 * 								如果正常,利用事務管理器,提交事務
 * 			
 */

2.2 舉例

模擬沒事務和有事務控制情況下,異常報錯後,操作是否會回滾

1.本次使用Spring的一個操作類JdbcTemplate,以及需要用到的第三方數據源,需要添加依賴,如圖:

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.3.12.RELEASE</version>
		</dependency>

	    <dependency>
	      <groupId>c3p0</groupId>
	      <artifactId>c3p0</artifactId>
	      <version>0.9.1.2</version>
	    </dependency>
	
	    <dependency>
	      <groupId>mysql</groupId>
	      <artifactId>mysql-connector-java</artifactId>
	      <version>5.1.44</version>
	    </dependency>


create table tb_person
(
    pid   varchar(32) not null
        primary key,
    pname varchar(64) null,
    page  int         null
);

2.編寫操作數據庫類

//名字默認是類名首字母小寫
@Repository
public class PersonDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void insert(){
        String sql = "INSERT INTO `tb_person`(pid,pname,page) VALUES(?,?,?)";
        String pid = UUID.randomUUID().toString().substring(0, 5);
        String pname = "p"+pid;
        jdbcTemplate.update(sql, pid,pname,19);

    }
}

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;
    //註解添加事務
    @Transactional
    public void insertUser(){
        personDao.insert();
        //otherDao.other();xxx
        System.out.println("插入完成...");
        int i = 10/1;
    }
}

3.添加數據源配置

@EnableTransactionManagement
@ComponentScan("com.zy")
@Configuration
public class TxConfig {

    //數據源
    @Bean
    public DataSource dataSource() throws Exception{
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://192.168.10.130:12345/mysql");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception{
        //Spring對@Configuration類會特殊處理;給容器中加組件的方法,多次調用都只是從容器中找組件
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    //註冊事務管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager() throws Exception{
        return new DataSourceTransactionManager(dataSource());
    }

}

4.測試

    @Test
    public void testTx(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
        PersonService personService = applicationContext.getBean(PersonService.class);
        personService.insertUser();
        applicationContext.close();
    }

在這裏插入圖片描述

5.現在我們在插入的時候,把事務給去掉,然後使插入中出現異常報錯,看看數據是否會提交

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;
    //註解添加事務
//    @Transactional
    public void insertUser(){
        personDao.insert();
        System.out.println("插入完成...");
        //產生異常
        int i = 10/0;
    }
}

在這裏插入圖片描述

6.當我們增加事務,然後再執行一遍,則:

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;
    //註解添加事務
    @Transactional
    public void insertUser(){
        personDao.insert();
        System.out.println("插入完成...");
        int i = 10/0;
    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章