AOP 全程Aspect Oriented Programming,直譯就是面向切面編程。和POP、OOP類似,它也是一種編程思想。OOP強調的是封裝、繼承、多態,也就是功能的模塊化。而AOP則是OOP的補充,它強調的是切面,在運行時動態地將代碼切入到類的指定方法、指定位置上的編程思想,也就是將業務代碼和業務先後的代碼分離出來(解耦),將日誌、權限驗證等功能抽取出來而後重用。
在Spring中,採用動態代理的方式來表達AOP。(並不是全部的AOP都是使用動態代理來,好比AspectJ採用編譯時建立代理對象,比運行時建立效率更高)
動態代理通常有兩種實現方式,一種是JDK原生動態代理,要求被代理對象必須實現接口,而且只能代理接口中的方法(本質是建立一個實現接口的代理對象)。另外一種是CGlib,能夠代理全部的方法(本質是建立一個代理對象,繼承被代理對象)。Spring中使用的是CGlib的方式。java
廢話很少說,直接介紹Spring中的AOP。mysql
Joinpoint
(鏈接點):任何能夠被加強的方法,都稱爲鏈接點。在spring中,這些點指的是方法,由於spring只支持方法類型的鏈接點。Pointcut
(切入點):將要被加強的方法。即咱們要對哪些Joinpoint進行攔截的定義。Advice
(通知/加強):所謂通知是指攔截到Joinpoint以後所要作的事情就是通知。通知的類型:前置通知,後置通知,異常通知,最終通知,環繞通知。Introduction
(引介):Adivice是對方法進行加強的,而Introdution是針對類進行加強的Target
(目標對象):被代理的目標對象。Weaving
(織入):是指把加強應用到目標對象來建立新的代理對象的過程。spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。Proxy
(代理):一個對象被jdk代理或者cglib代理後的對象,稱爲代理Aspect
(切面):多個通知和切入點的配置關係。首先是依賴:
web
<!--IOC相關依賴--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.6.RELEASE</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.12</version> </dependency> <!-- AOP相關依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> </dependency> <!-- spring集合junit的依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency>
而後配置xml主配置文件: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" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 待加強的bean --> <bean id="aspectService" class="com.bilibili.service.impl.AspectServiceImpl"></bean> <!-- 加強的功能bean --> <bean id="logger" class="com.bilibili.common.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 id:惟一標識 ref:引用的通知類(bean id) --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知 method:配置通知的方法(即加強的功能) pointcut:配置切面,也就是對哪一個方法進行加強(使用AspectJ表達式) execution:使用AspectJ切入點表達式 --> <aop:before method="printLog" pointcut="execution(public void com.bilibili.service.impl.AspectServiceImpl.update())"></aop:before> </aop:aspect> </aop:config> </beans>
execution表達式的匹配方式:sql
execution:匹配方法的執行(經常使用) execution(表達式) 表達式語法:execution([修飾符] 返回值類型 包名.類名.方法名(參數)) 寫法說明: 全匹配方式: public void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 訪問修飾符能夠省略 void cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 返回值可使用*號,表示任意返回值 * cn.bilibili.service.impl.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 包名可使用*號,表示任意包,可是有幾級包,須要寫幾個* * *.*.*.*.AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 使用..來表示當前包,及其子包 * cn..AccountServiceImpl.saveAccount(cn.bilibili.domain.Account) 類名可使用*號,表示任意類 * cn..*.saveAccount(cn.bilibili.domain.Account) 方法名可使用*號,表示任意方法 * cn..*.*( cn.bilibili.domain.Account) 參數列表可使用*,表示參數能夠是任意數據類型,可是必須有參數 * cn..*.*(*) 參數列表可使用..表示有無參數都可,有參數能夠是任意類型 * cn..*.*(..) 全通配方式: * *..*.*(..) 注: 一般狀況下,咱們都是對業務層的方法進行加強,因此切入點表達式都是切到業務層實現類。 execution(* cn.bilibili.service.impl.*.*(..)) 注意:多個execution可使用 || && 鏈接
<aop:config>
:用於聲明開始aop配置<aop:aspect>
:切面id
:給切面提供一個惟一標識。ref
:引用配置好的通知類bean的id<aop:point>
:切點,方便一個切點屢次使用id
:切點的惟一標識expression
:定義切點的表達式<aop:aspect>
:前置通知method
:通知的方法(即加強的功能)pointcut
:AspectJ表達式pointcut-ref
:引用切點(和pointcut不可同時使用)<aop:after-returning>
:後置通知<aop:after-throwing>
:異常通知<aop:after>
:最終通知(至關於finally)<aop:around>
:環繞通知,通常單獨使用,該通知(加強的方法)接收一個類型爲ProceedingJoinPoint
的參數,該類型有一個proceed()
方法,用來調用被代理方法。在主配置文件中開啓包掃描和註解AOP:數據庫
<!-- 開啓註解掃描的包 --> <context:component-scan base-package="com.bilibili"></context:component-scan> <!-- 開啓註解AOP --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
把被代理對象註冊到容器中:
express
@Service("accountService") public class AccountServiceImpl implements AccountService { public void update() { System.out.println("更新操做"); } public void save() { System.out.println("保存操做"); } public void delete() { System.out.println("刪除操做"); } }
在通知類上添加@Component()
進行註冊,@Aspect
表示切面類。方法上添加@Before()
前置通知、@AfterReturning()
後置通知、@AfterThrowing()
異常通知、@After()
最終通知。@Pointcut()
註解空方法表示切面。
編程
@Component("logger") @Aspect//聲明當前是一個切面類(通知類) public class Logger { //註解前置通知,value屬性就是切點的AspectJ表達式 @Before("execution(* com.bilibili.service.impl.AccountServiceImpl.update())") public void beforePrintLog(){ System.out.println("<aop:before>標籤配置前置通知,即加強的功能在目標方法以前"); } //切面 @Pointcut("execution(* com.bilibili.service.impl.AspectServiceImpl.update())") public void pt1() { } //註解後置通知,引用切面 @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("<aop:after-returning>標籤配置後置通知,即加強的功能在目標方法以後"); } //下面就不一個一個標註了。 public void afterThrowingPrintLog(){ System.out.println("<aop:after-throwing>標籤配置異常通知,即目標方法出現異常的時候執行"); } public void afterPrintLog(){ System.out.println("<aop:after>標籤配置最終通知。即不論是否出現異常,都會執行,相似finally"); } public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object obj = null; try { System.out.println("環繞通知,手動在代碼中定義什麼時候執行"); obj = pjp.proceed();//目標方法執行 System.out.println("環繞通知,手動在代碼中定義什麼時候執行"); } catch (Throwable throwable) { System.out.println("環繞通知,手動在代碼中定義什麼時候執行"); throwable.printStackTrace(); }finally { System.out.println("環繞通知,手動在代碼中定義什麼時候執行"); } return obj; } }
只需在IoC的純註解配置類上添加@EnableAspectJAutoProxy()
開啓AOP便可。
tomcat
//聲明當前類是一個spring的配置類,用來替代xml配置文件 //獲取容器時須要使用AnnotationApplicationContext(@Configuration標註的類.class) @Configuration //用於配置容器初始化時須要掃描的包 //和xml配置中<context:component-scan base-package="com.bilibili"/>做用一致 @ComponentScan("com.bilibili") //導入其餘配置類 @Import(JdbcConfig.class) //開啓AOP @EnableAspectJAutoProxy public class SpringConfig { }
繼承該類後能夠不用手動獲取JdbcTemplate對象。mybatis
使用這種方式沒法用註解注入DataSource,只能經過xml注入(注入給子類也能夠)
事務處理位於業務層,Spring提供了一個spring-tx包來進行控制事務,事務是基於AOP,原理也比較好理解。
PlatformTransactionManager:平臺事務管理器,是Spring真正管理事務的對象,是一個接口,經常使用實現類有以下兩個:
Spring主要經過兩個重要的接口來描述一個事務:
Spring框架進行事務的管理,首先使用TransactionDefinition對事務進行定義。經過PlatformTransactionManager根據TransactionDefinition的定義信息進行事務的管理。在事務管理過程當中產生一系列的狀態:保存到TransactionStatus中。
TransactionDefinition接口具備如下經常使用方法:
String getName()
:獲取事務對象名稱int getIsolationLevel()
:獲取事務隔離級別int getPropagationBehavior()
:獲取事務傳播行爲int getTimeout()
:獲取事務超時時間boolean isReadOnly()
獲取事務是否只讀事務隔離級別:
ISOLATION_DEFAULT
:默認級別,會根據不一樣數據庫自動變動(MySQL爲可重複讀,Oracle和Access爲讀已提交)ISOLATION_READ_UNCOMMITTED
:讀未提交(會產生髒讀)ISOLATION_READ_COMMITTED
:讀已提交(解決髒讀)ISOLATION_REPEATABLE_READ
:可重複讀ISOLATION_SERIALIZABLE
:串行化事務的傳播行爲
傳播行爲解決的問題: 一個業務層事務 調用 另外一個業務層事務時,事務之間關係如何處理
事務傳播行爲PROPAGATION的取值: REQUIRED 支持當前事務,若是不存在,就新建一個(默認的傳播行爲) * 刪除客戶 刪除訂單, 處於同一個事務,若是 刪除訂單失敗,刪除客戶也要回滾 SUPPORTS 支持當前事務,若是不存在,就不使用事務 MANDATORY 支持當前事務,若是不存在,拋出異常 REQUIRES_NEW 若是有事務存在,掛起當前事務,建立一個新的事務 * 生成訂單, 發送通知郵件, 通知郵件會建立一個新的事務,若是郵件失敗, 不影響訂單生成 NOT_SUPPORTED 以非事務方式運行,若是有事務存在,掛起當前事務 NEVER 以非事務方式運行,若是有事務存在,拋出異常 NESTED 若是當前事務存在,則嵌套事務執行 * 依賴於 JDBC3.0 提供 SavePoint 技術 * 刪除客戶 刪除訂單, 在刪除客戶後, 設置SavePoint, 執行刪除訂單,刪除訂單和刪除客戶在同一個事務 ,刪除部分訂單失敗, 事務回滾 SavePoint , 由用戶控制是事務提交 仍是 回滾 三個表明: REQUIRED 一個事務, 要麼都成功,要麼都失敗 REQUIRES_NEW 兩個不一樣事務,彼此之間沒有關係 一個事務失敗了 不影響另外一個事務 NESTED 一個事務, 在A事務 調用 B過程當中, B失敗了, 回滾事務到 以前SavePoint , 用戶能夠選擇提交事務或者回滾事務
超時時間:默認值是-1,沒有超時限制。若是有,以秒爲單位進行設置。
是不是隻讀事務:建議查詢時設置爲只讀。
TransactionStatus:事務的運行狀態,經常使用方法以下:
void flush()
:刷新事務boolean hasSavePoint()
:是否存在存儲點boolean idComplated()
:事務是否完成boolean isNewTransaction()
:是否爲新事物boolean isRollbackOnly()
:事務是否回滾void setRollbackOnly()
設置事務回滾首先添加依賴:
主要是spring-tx、spring-aspects這兩個不要漏了。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> </dependency>
配置事務管理器的bean
<!-- bean的名字叫作transactionManager,由於在配置事務策略的時候須要指定的事務管理器的默認名字就是transactionManager,若是是其餘名字,在配置事務策略的時候,須要手動指定。 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
配置事務策略:
<!-- 配置事務策略 --> <tx:advice id="tx"> <tx:attributes> <!-- 指定對那些方法使用事務 name:須要進行事務管理的方法名 *表明全部方法,這裏須要填方法的名字便可,不是aspectj那種包名加類名方法名。 isolation:事務隔離級別 propagation:事務傳播行爲 timeout:超時時間 ready-only:設置事務是否只讀 rollback-for:指定對哪一種異常進行回滾 no-rollback-for:指定對那種異常不進行回滾 --> <tx:method name="*" /> </tx:attributes> </tx:advice>
配置事務AOP:
<!-- 配置aop --> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.bilibili.service.impl.*.*(..))"></aop:pointcut> <!-- 配置事務策略運用到事務管理器 --> <aop:advisor advice-ref="tx" pointcut-ref="pt1"></aop:advisor> </aop:config>
在spring主配置文件中:
<!-- 開啓spring的註解掃描 --> <context:component-scan base-package="com.bilibili"></context:component-scan> <!-- 開啓事務的註解掃描 --> <tx:annotation-driven></tx:annotation-driven>
而後只在方法或者類或者接口上配置@Transactional
便可開啓事務
在配置類上添加@EnableTransactionManagement
開啓註解事務管理:
@Configuration //聲明當前是一個配置類,用來代替applicationContext.xml文件 @ComponentScan("com.bilibili") //開啓註解包掃描 @PropertySource("classpath:jdbc.properties") // 加載外部配置文件 @EnableTransactionManagement // 開啓註解事務管理 public class SpringConfig { @Value("${jdbc.url}")//引入外部配置文件中的資源 private String url; @Value(("${jdbc.driverClass}")) private String driverClass; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean("dataSource")//將bean裝配到spring容器中 public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setDriverClassName(driverClass); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean("jdbcTemplate") public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return jdbcTemplate; } @Bean("transactionManager") public DataSourceTransactionManager getDataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); return dataSourceTransactionManager; } }
在Tomcat中,因爲Servlet是Tomcat建立的,沒法放入Spring中,當Servlet須要使用Service的時候是不太方便的,此時就可使用監聽器來自動建立ApplicationContext。
spring監聽器原理:監聽servletContext建立,建立ApplicationContext並將其放入上下文域。
本身實現:
建立一個servletContext監聽器
@WebListener() public class MyListener implements ServletContextListener { /** * 在ServletContext對象建立的時候,建立spring容器。 * 1.建立spring容器,須要配置文件的名字,名字並非固定的,因此能夠配置在web.xml中 * 2.spring容器建立以後,須要可以被全部的servlet來使用,那麼須要將spring容器保存起來,保存到哪裏?ServletContext域對象中 * */ @Override public void contextInitialized(ServletContextEvent servletContextEvent) { //獲取servletContext域對象 ServletContext servletContext = servletContextEvent.getServletContext(); //讀取web.xml中的配置參數 -- 即spring的核心配置文件的名字 String contextConfig = servletContext.getInitParameter("contextConfig"); //建立spring容器 ApplicationContext ac = new ClassPathXmlApplicationContext(contextConfig); //將spring容器保存到servletContext對象中 servletContext.setAttribute("ac",ac); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
web.xml中配置spring配置文件位置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!-- 配置spring核心配置文件的名字 --> <context-param> <param-name>contextConfig</param-name> <param-value>applicationContext.xml</param-value> </context-param> </web-app>
而後在servlet中就能夠獲取servletContext中保存的ApplicationContext了。
使用Spring的監聽器:
引入依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.6.RELEASE</version> </dependency>
web.xml中配置spring主文件位置和監聽器:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!-- spring核心配置文件的位置 key:是固定的 value:格式固定,classpath:文件名 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 告訴tomcat 用於建立spring容器的監聽器的位置 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
而後就能夠在servlet中使用下面的方式獲取ApplicationContext:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); userService = (UserService ) ac.getBean("userService"); userService.register(); }
其實用了SpringMVC以後不會這麼麻煩23333🤣