AOP: aspect oriented programming 面向切面編程
分布于應用中多處的功能稱為橫切關注點,通過這些橫切關注點在概念上是與應用的業務邏輯相分離的,但其代碼往往直接嵌入在應用的業務邏輯之中。將這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的。切面實現了橫切關注點的模塊化
一句話概括:切面是跟具體業務無關的一類共同功能。
案例一、表演
1. 編寫切面類
~~~
@Component()
public class Audience { // 表演之前
public void takeSeats() {
System.out.println("The audience is taking their seats.");
} // 表演之前
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
} // 表演成功之后
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
} // 表演失敗之后
public void demandRefund() {
System.out.println("Boo! We want our money back!");
}
//表演后(finally)
public void comment() {
System.out.println("the audience is making comments");
}
}
~~~
2. 編寫被代理類(方法一,被代理類必須實現接口)
~~~
public interface Performer {
void perform();
}
~~~
~~~
@Component()
public class Instrumentalist implements Performer {
private String song = "my love";
public void perform() {
System.out.print("Playing " + song + " : ");
}
}
~~~
XML配置
aspect: 切面
pointcut:切入點(切面和被代理類的結合)
advice:通知(前置通知,后置通知,例外通知,最終通知)
~~~
<aop:config>
<aop:aspect ref="audience"><!-- 引用audience Bean -->
<!-- 聲明切入點 -->
<aop:pointcut id="performance"
expression="execution(* com.neuedu.model.aop.*.*(..))" />
<!-- 表演之前 -->
<aop:before pointcut-ref="performance" method="takeSeats" />
<aop:before pointcut-ref="performance" method="turnOffCellPhones" />
<!-- 表演之后 -->
<aop:after-returning pointcut-ref="performance"
method="applaud" />
<!-- 表演失敗之后 -->
<aop:after-throwing pointcut-ref="performance"
method="demandRefund" />
<aop:after pointcut-ref="performance"
method="comment" />
</aop:aspect>
</aop:config>
~~~
編寫測試類
~~~
ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
Performer perfomer = (Performer)application.getBean("instrumentalist");
perfomer.perform();
~~~
編寫被代理類(方法二:被代理類不實現接口)
1)這種方法需要額外引入cglib jar(spring4.x,自帶cglib功能,不需要引入)
2)在xml中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
編寫測試類
~~~
ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
Instrumentalist perfomer = (Instrumentalist)application.getBean("instrumentalist");
perfomer.perform();
~~~
案例二、事務處理
使用切面之前
DBUtils類
~~~
public class DBUtils {
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static
{
//加載數據庫驅動
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void beginTransaction()
{
//1.得到數據庫連接
Connection conn = getConnection();
//2.設置自動提交為false
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection()
{
Connection conn = tl.get();
if(conn==null)
{
try {
conn = DriverManager.
getConnection("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8", "root", "root");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//放在本地線程
tl.set(conn);
}
return conn;
}
public static void commit()
{
//1.得到數據庫連接
Connection conn = getConnection();
//2. 提交
try {
conn.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void rollback()
{
//1.得到數據庫連接
Connection conn = getConnection();
//2. 提交
try {
conn.rollback();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void close()
{
//1.得到數據庫連接
Connection conn = getConnection();
//2. 提交
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3.把conn從tl中移除
tl.remove();
}
}
~~~
Service類
~~~
@Service
public class AccountService {
@Autowired
AccountDAO accountDAO;
public void transferMoney()
{
//1. 獲得數據庫連接,開啟事務
DBUtils.beginTransaction();
try
{
accountDAO.deduct();
accountDAO.add();
DBUtils.commit();
}
catch(Exception e)
{
DBUtils.rollback();
e.printStackTrace();
}
finally
{
DBUtils.close();
}
}
public static void main(String[] args) {
//1. 開啟spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService)context.getBean("accountService");
accountService.transferMoney();
}
}
~~~
DAO類
~~~
@Repository
public class AccountDAO {
public void deduct() throws SQLException
{
//1. 獲得連接
Connection conn = DBUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("update account set balance = balance-10 where accountid =2");
ps.executeUpdate();
}
public void add() throws SQLException
{
//1. 獲得連接
Connection conn = DBUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("update account set balance = balance+10 where accountid =1");
ps.executeUpdate();
}
}
~~~
使用切面之后:
切面類
~~~
public TransactionManagerAspect
{
public void before()
{
//1. 獲取數據庫連接
Connection conn = DriverManager.getConnection();
//把conn放在本地線程中
}
public void afterreturning()
{
//從本地線程獲取當前connection
//提交事務
conn.commit();
}
public void afterthrowing()
{
//從本地線程獲取當前connection
conn.rollback();
}
public void finally()
{
//從本地線程獲取當前connection
conn.close();
}
}
~~~
Service類
~~~
class MyService
{
public void test()
{
MyDAO myDAO = new MyDAO();
myDAO.deduct();
myDAO.add();
}
}
~~~
DAO類實現不變
XML AOP配置
~~~
<aop:config>
<aop:aspect ref="transactionAspect">
<aop:pointcut expression="execution(* com.neuedu.model.service.AccountService2.*(..))" id="transactionpointcut"/>
<aop:before method="before" pointcut-ref="transactionpointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="transactionpointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="transactionpointcut"/>
<aop:after method="after" pointcut-ref="transactionpointcut"/>
</aop:aspect>
</aop:config>
~~~
環繞通知
案例一、表演
~~~
@Component
public class AroundAudience {
public void watchPerformance(ProceedingJoinPoint joinpoint) {
try { // 表演之前
System.out.println("The audience is taking their seats.");
System.out.println("The audience is turning off their cellphones");
long start = System.currentTimeMillis(); // 執行被通知的方法
joinpoint.proceed();
// 表演之后
long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP CLAP");
System.out.println("The performance took " + (end - start) + " milliseconds.");
} catch (Throwable t) { // 表演失敗之后
System.out.println("Boo! We want our money back!");
}
finally
{
System.out.println("leave..");
}
}
}
~~~
~~~
<aop:config>
<aop:aspect ref="aroundAudience">
<aop:pointcut id="performance"
expression="execution(* com.neuedu.model.aop.*.*(..))" />
<aop:around method="watchPerformance" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
~~~
案例二、日志
~~~
@Component
public class LogAspect {
public void around(ProceedingJoinPoint joinpoint) {
try {
//前置通知
System.out.println(joinpoint.getTarget().getClass().getName()+" "+joinpoint.getSignature().getName()+"開始運行");
long start = System.currentTimeMillis();
// 執行被通知的方法
joinpoint.proceed();
// 后置通知
long end = System.currentTimeMillis();
System.out.println(joinpoint.getTarget().getClass().getName()+" "+joinpoint.getSignature().getName()+"方法運行了"+ (end - start) + " milliseconds.");
} catch (Throwable t) {
//例外通知
System.out.println(t.getMessage());
}
finally
{
//最終通知
System.out.println("方法運行結束");
}
}
}
~~~
~~~
@Service
public class MyService {
public void test1()
{
System.out.println("test1");
}
public void test2()
{
System.out.println("test2");
}
}
~~~
~~~
<aop:config>
<aop:aspect ref="logAspect">
<aop:pointcut expression="execution(* com.neuedu.model.service.*.*(..))" id="logpointcut"/>
<aop:around method="around" pointcut-ref="logpointcut"/>
</aop:aspect>
</aop:config>
~~~
<aop:advisor> 顧問
Advisor表示只有一個通知和一個切入點的切面,由于Spring AOP都是基于AOP的攔截器模型的環繞通知的,所以引入Advisor來支持各種通知類型(如前置通知等5種),Advisor概念來自于Spring1.2對AOP的支持
~~~
@Component
public class MyAdvice implements MethodInterceptor{
@Override
public void around(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("hahaha before advice");
}
}
~~~
AOP配置
~~~
<aop:config>
<aop:pointcut id="performance2"
expression="execution(* com.neuedu.model.aop.*.*(..))" />
<aop:advisor advice-ref="myAdvice" pointcut-ref="performance2"/>
</aop:config>
~~~
以上配置可以進一步簡化為:
~~~
<aop:config>
<aop:advisor advice-ref="myAdvice" pointcut="execution(* com.neuedu.model.aop.*.*(..))"/>
</aop:config>
~~~
5種通知實現的接口類型:
MethodBeforeAdvice(前置)
AfterReturningAdvice(后置)
MethodInterceptor(環繞)
ThrowsAdvice(例外)
AfterAdvice(最終)
除了在進行事務控制的情況下,其他情況一般不推薦使用該方式,該方式屬于侵入式設計,必須實現通知API