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