一:帝國之軍-AOP
1:案例分析
![事務案例](https://img-blog.csdn.net/2018040518063398?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
上訴問題:在我們的業務層中每一個業務方法都的處理事務(頻繁的try-cache),在設計上存在兩個很嚴重問題:
1):責任不分離,業務方法只需要關係如何完成業務功能,不需要去關心事務管理/日誌管理/權限管理等.
2):代碼結構重複,在開發中不要重複代碼,重複就意味着維護成本增大.
![房租租賃案例](https://img-blog.csdn.net/20180405180731550?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
2.靜態代理
代理模式:客戶端直接使用的都是代理對象,不知道真實對象是誰,此時代理對象可以在客戶端和真是對象之間起到中介的作用
1:代理對象完全包含真是對象,客戶端使用的都是代理對象的方法,和真是對象沒有直接關係;
2:代理模式的職責:把不是真是對象該做的事情從真實對象上撇開--職責清晰;
靜態代理:在程序運行前就已經存在代理類的字節碼文件,代理對象和真是對象的關係在運行前就確定了.
![靜態代理](https://img-blog.csdn.net/20180405181345791?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![靜態代理的優缺點](https://img-blog.csdn.net/20180405190832266?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDE2MTcwOA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
//靜態代理類
public class EmployeeServiceProxy implements IEmployeeService{
private IEmployeeService target;//真是對象/委託對象
private TransactionManager txManager;//事務管理器
public void setTarget(IEmployeeService target) {
this.target = target;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void save(Employee emp) {
txManager.begin();
try {
target.save(emp);
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
}
public void update(Employee emp) {
txManager.begin();
try {
target.update(emp);
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
//@Qualifier("employeeServiceProxy")
//因爲代理類實現於接口,而接口還有一個imp類,xml配置文件的時候必須要標明,可以使用Qualifier標註,也可以跟下面一樣內部<bean>表示
private IEmployeeService service;
@Test
public void testSave() throws Exception {
System.out.println(service.getClass());//查看對象的真實類型
service.save(new Employee());
}
@Test
public void testUpdate() throws Exception {
service.update(new Employee());
}
}
App-Context.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"
xmlns:context="http://www.springframework.org/schema/context"
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
" >
<bean id="employeeDAO" class="cn.wolfcode.dao.imp.IEmployeeDAOImpl"/>
<bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager"/>
<!-- 代理對象 -->
<bean id="employeeServiceProxy" class="cn.wolfcode.proxy.EmployeeServiceProxy">
<property name="txManager" ref="transactionManager"/>
<property name="target" >
<bean class="cn.wolfcode.service.IEmployeeServiceImpl">
<property name="dao" ref="employeeDAO"></property>
</bean>
</property>
</bean>
</beans>
3.動態代理
動態代理:動態代理是在程勳運行期間有JVM通過反射等機制動態的生成的,所以不存在代理類的字節碼文件,代理
對象和真實對象的關係是在程序運行時期才確定的.
如何實現動態代理:
1):針對有接口:使用JDK動態代理
2):針對無接口:使用CGLIB或Javassist組件
①JDK動態代理
domain
public class Employee {
}
dao
public interface IEmployeeDAO {
void save(Employee emp);
void update(Employee emp);
}
impl
public class IEmployeeDAOImpl implements IEmployeeDAO{
public void save(Employee emp) {
System.out.println("保存員工");
System.out.println("保存成功");
}
public void update(Employee emp) {
System.out.println("修改員工");
throw new RuntimeException("故意錯誤");
}
}
service
public interface IEmployeeService {
void save(Employee emp);
void update(Employee emp);
}
impl
public class IEmployeeServiceImpl implements IEmployeeService{
private IEmployeeDAO dao;
public void setDao(IEmployeeDAO dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
}
public void update(Employee emp) {
dao.update(emp);
}
}
tx
//模擬事務管理器:
public class TransactionManager {
public void begin(){
System.out.println("開啓事務");
}
public void commit(){
System.out.println("提交事務");
}
public void rollback(){
System.out.println("回滾事務");
}
}
//事務的增強操作
@SuppressWarnings("all")
public class TransactionManagerAdvice implements InvocationHandler{
private Object targer;//真實對象(對誰做增強)
private TransactionManager txManager;//事務管理器(模擬)
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setTarger(Object targer) {
this.targer = targer;
}
//創建一個代理對象
public <T> T getProxyObject(){
return (T)Proxy.newProxyInstance(targer.getClass().getClassLoader(),//類加載器,一般跟上真實對象的類加載器S
targer.getClass().getInterfaces(),//真實對象實現的接口(JDK動態代理必須要求真是對象有接口)
this);//如何做事務增強的對象
}
//如何爲真實對象的方法做增強的具體操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().startsWith("get")|| method.getName().startsWith("list")){
return method.invoke(targer, args);//調用真實對象的方法
}
Object obj = null;
txManager.begin();
try {
//-----------------------------------------------------
obj = method.invoke(targer, args);//調用真實對象的方法
//-----------------------------------------------------
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
return obj;
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
private TransactionManagerAdvice advice;
//代理對象:com.sun.proxy.$Proxy19
@Test
public void testSave() throws Exception {
//獲取代理對象
IEmployeeService proxy = advice.getProxyObject();
proxy.save(new Employee());
}
@Test
public void testUpdate() throws Exception {
//獲取代理對象
IEmployeeService proxy = advice.getProxyObject();
proxy.update(new Employee());
}
}
App-Context.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"
xmlns:context="http://www.springframework.org/schema/context"
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
" >
<bean id="employeeDAO" class="cn.wolfcode.dao.imp.IEmployeeDAOImpl"/>
<bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager"/>
<bean id="employeeService" class="cn.wolfcode.service.impl.IEmployeeServiceImpl">
<property name="dao" ref="employeeDAO"></property>
</bean>
<!-- 代理對象 -->
<!-- 配置一個事務增強的類 -->
<bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice">
<property name="targer" ref="employeeService"></property>
<property name="txManager" ref="transactionManager" />
</bean>
</beans>
②:CGLIB動態代理
domain
public class Employee {
}
dao
public interface IEmployeeDAO {
void save(Employee emp);
void update(Employee emp);
}
impl
public class IEmployeeDAOImpl implements IEmployeeDAO{
public void save(Employee emp) {
System.out.println("保存員工");
System.out.println("保存成功");
}
public void update(Employee emp) {
System.out.println("修改員工");
throw new RuntimeException("故意錯誤");
}
}
service.impl
public class EmployeeServiceImpl{
private IEmployeeDAO dao;
public void setDao(IEmployeeDAO dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
}
public void update(Employee emp) {
dao.update(emp);
}
}
tx
public class TransactionManager {
public void begin(){
System.out.println("開啓事務");
}
public void commit(){
System.out.println("提交事務");
}
public void rollback(){
System.out.println("回滾事務");
}
}
@SuppressWarnings("all")
public class TransactionManagerAdvice implements org.springframework.cglib.proxy.InvocationHandler{
private Object target;//真實對象(對誰做增強)
private TransactionManager tx;//事務管理器(模擬)
public void setTarget(Object target) {
this.target = target;
}
public void setTx(TransactionManager tx) {
this.tx = tx;
}
//創建一個代理對象
public <T> T getProxyObject(){
Enhancer enhencer = new Enhancer();
enhencer.setSuperclass(target.getClass());//將繼承與哪一個類,去做增強
enhencer.setCallback(this);//創建增強的對象
return (T) enhencer.create();//創建代理對象
}
//如何爲真實對象的方法做增強的具體操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
tx.begin();
try {
obj = method.invoke(target, args);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}
return obj;
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
private TransactionManagerAdvice advice;
//代理對象:com.sun.proxy.$Proxy19
//CGLIB代理對象:class cn.wolfcode.service.impl.IEmployeeServiceImpl$$EnhancerByCGLIB$$8db50f30
@Test
public void testSave() throws Exception {
EmployeeServiceImpl proxy = advice.getProxyObject();
proxy.save(new Employee());
}
@Test
public void testUpdate() throws Exception {
EmployeeServiceImpl proxy = advice.getProxyObject();
proxy.update(new Employee());
}
}
App-Context.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"
xmlns:context="http://www.springframework.org/schema/context"
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
" >
<bean id="employeeDAO" class="cn.wolfcode.dao.imp.IEmployeeDAOImpl"/>
<bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl">
<property name="dao" ref="employeeDAO"></property>
</bean>
<bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager"></bean>
<!-- 代理對象 -->
<!-- 配置一個事務增強的類 -->
<bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice">
<property name="target" ref="employeeService"></property>
<property name="tx" ref="transactionManager"></property>
</bean>
</beans>
③:攔截器的原理和日誌記錄
攔截器跟Filter過濾器很相似:
Filter就是對請求和響應做攔截.
Filter: WEB領域的概念,只能針對請求和響應做增強,離不開servlet-api.jar
Integerceptor: 整個Java領域的概念,不僅可以運用到service層,還可以用到Web層.
domain
public class Employee {
}
dao
public interface IEmployeeDAO {
void save(Employee emp);
void update(Employee emp);
}
imp
public class IEmployeeDAOImpl implements IEmployeeDAO{
public void save(Employee emp) {
System.out.println("保存員工");
System.out.println("保存成功");
}
public void update(Employee emp) {
System.out.println("修改員工");
throw new RuntimeException("故意錯誤");
}
}
service
public class IEmployeeServiceImpl{
private IEmployeeDAO dao;
public void setDao(IEmployeeDAO dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
}
public void update(Employee emp) {
dao.update(emp);
}
}
log
public class LogUtil {
public void writeLog(String className,String methodName){
System.out.println(new Date().toLocaleString()+"調用了"+className+"類中的"+methodName+"方法");
}
}
//日誌增強
public class LogAdvice implements org.springframework.cglib.proxy.MethodInterceptor{
private Object target;//真實對象
private LogUtil logUtil;
public void setTarget(Object target) {
this.target = target;
}
public void setLogUtil(LogUtil logUtil) {
this.logUtil = logUtil;
}
//創建代理對象
public <T> T getProxyObject(){
/*Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return (T) enhancer.create();*/
return (T) Enhancer.create(target.getClass(),this);
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
logUtil.writeLog(method.getDeclaringClass().getName(),method.getName());
Object ret = method.invoke(target,args);//調用真實對象的方法
return ret;
}
}
App
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
@Autowired
private LogAdvice advice;
//代理對象:com.sun.proxy.$Proxy19
//CGLIB代理對象:class cn.wolfcode.service.impl.IEmployeeServiceImpl$$EnhancerByCGLIB$$8db50f30
@Test
public void testSave() throws Exception {
IEmployeeServiceImpl proxy = advice.getProxyObject();
proxy.save(new Employee());
}
@Test
public void testUpdate() throws Exception {
IEmployeeServiceImpl proxy = advice.getProxyObject();
proxy.update(new Employee());
}
}
App-context.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"
xmlns:context="http://www.springframework.org/schema/context"
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
" >
<bean id="employeeDAO" class="cn.wolfcode.dao.imp.IEmployeeDAOImpl"/>
<bean id="logUtil" class="cn.wolfcode.log.LogUtil"/>
<bean id="employeeService" class="cn.wolfcode.service.impl.IEmployeeServiceImpl">
<property name="dao" ref="employeeDAO"></property>
</bean>
<!-- 代理對象 -->
<!-- 配置一個事務增強的類 -->
<bean id="logAdvice" class="cn.wolfcode.log.LogAdvice">
<property name="target" ref="employeeService"></property>
<property name="logUtil" ref="logUtil" />
</bean>
</beans>
4.代理總結
JDK動態代理總結:
1:Java動態代理是使用java.lang.reflect包中的Proxy類與InvocationHandler接口這兩個來完成的.
2:要使用JDK動態代理,委託必須要定義接口.
3:JDK動態代理將會攔截所有public的方法(因爲只能調用接口中定義的方法),這樣即使在接口中定義了方法,不用修改代碼也會被攔截.
4:動態代理的最小單位是類(所有類中的方法都會被處理),如果只想攔截一部分方法,可以在invoke方法中對要執行的方法名進行判斷.
CGLIB代理總結:
1:CGLIB可以生成委託類的子類,並重寫父類非final修飾的方法.
2:要求類不能是final,要攔截的方法要是非final,非static, 非private的.
3:動態代理的最小單位是類(所有類中的方法都會被處理).
關於性能:
JDK動態代理是基於實現接口的,CGLIB和Javassit是基於繼承委託類的.
從性能上考慮: Javassit > CGLIB > JDK
Struts2的攔截器和Hibemate延遲加載對象,採用的是Javassit的方式.
對接口創建代理優於對類創建代理,因爲會產生更加鬆耦合的系統,也更符合面向接口編程規範.
若委託對象實現了接口,優先選用JDK動態代理.
若委託對象沒有實現任何接口,使用Javassit和CGLIB動態代理.
5.AOP思想
1.AOP思想和重要術語
AOP(Aspect oritention Programming):
把一個個的橫切關注點放到某個模塊中去,稱之爲切面.那麼每一個的切面都能影響業務的某一種功能,切面的目的就是功能增強,
如日誌切面就是一個橫切關注點,應用中許多方法需要做日誌記錄,只需要插入日誌的切面即可.
Joinpoint: 連接點,被攔截到需要被增強的方法 WHERE 去哪裏做增強操作
Pointcut: 切入點,需要爲哪些包中的哪些類中的哪些方法.Joinpoint的集合. WHERE:去哪些地方做增強.
Advice: 增強(通知),當攔截到Joinpoint之後,在方法執行的某一個時機(when),做怎麼樣的增強操作(what).
Aspect: 切面 Pointcut + Advice 去哪些地方+在什麼時候,做說明增強
Target: 目標對象,被代理的目標對象
Weaving: 織入,把 Advice 加到 Target 上之後,創建出Proxy對象的過程.
Proxy: 一個類被AOP織入增強後,產生的代理類.
方法執行的時機:
前置增強, 後置增強, 異常增強, 最終增強, 環繞增強
①:Pointcut表達式
AOP的規範本應該由SUN公司提出,但是被AOP聯盟捷足先登,AOP聯盟制定AOP規範,首先就要解決一個問題,怎麼表示切入點,也就是在哪些方法上增強(where).
AspectJ是一個面向切面的框架,Aspect切入點語法如下(表示在哪些包下的哪些類的哪些方法上做切入增強):
翻譯成中文:execution(<修飾符>? <返回類型> <聲明類型>? <方法名>(<參數>) <異常>?)
舉例 :public static Class java.Class.forName(String className)throws ClassNotFoundException
通配符:
* :匹配任何部分,只能表示一個單詞
.. :可用於全限定名中和方法參數中,分別表示子包和0到N個參數
2.AOP開發
①:依賴
依賴的jar:
spring-aop-版本.RELEASE.jar
com.springsource.org-aopalliance-1.0.0.jar Spring5以後不需要拷貝,納入了aop包中
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
②:AOP各種增強
before:前置增強 被增強方法執行之前執行 權限控制日誌記錄等
after-returning:後置增強 正常執行完畢後執行 提交事務/統計分析數據結果等
after-throwing:異常增強 被增強方法出現異常時執行 回滾事務/記錄日誌的日常信息
after:最終增強 無論是否有異常,最終都要執行的增強操作 釋放資源等
around:環繞增強 可以自定義在被增強方法的什麼時機執行(返回一個Object,參數ProceedingJoinPoint) 緩存,性能日誌,權限,事務管理
獲取被增強方法的信息,並可以傳遞給增強方法:
Spring AOP: Joinpoint類 連接點, 訪問被增強方法的真是對象,代理對象,方法參數等等
可以做前置,後置,異常,最終增強方法的參數,第一個參數
ProceedingJoinpoint:是Joinipoint的子類,只用於環繞增強,作爲一個參數,
還可以調用真實對象中被增強的方法:
如何獲取增強方法裏的異常信息, 增強方法裏的參數JoinPoint,以及如何在環繞增強裏面調用真實方法?
//模擬事務管理器:
public class TransactionManager {
public void begin(JoinPoint po){
System.out.println("代理對象: "+po.getThis().getClass());
System.out.println("目標對象: "+po.getTarget().getClass());
System.out.println("被增強方法參數: "+Arrays.toString(po.getArgs()));
System.out.println("當前連接點簽名: "+po.getSignature());
System.out.println("當前連接點類型: "+po.getKind());
System.out.println("開啓事務");
}
public void commit(JoinPoint po){
System.out.println("提交事務");
}
public void rollback(JoinPoint po,Throwable tx){
System.out.println("回滾事務"+" 方法的錯誤信息: "+tx.getMessage());
}
public void after(JoinPoint po){
System.out.println("釋放資源");
}
public Object aroundMethod(ProceedingJoinPoint po){
Object ret = null;
System.out.println("開啓事務");
try {
ret = po.proceed();//調用真實對象的方法,取的一個返回值,查詢有返回值
System.out.println("提交事務");
} catch (Throwable e) {
System.out.println("回滾事務 錯誤信息:"+e.getMessage());
} finally {
System.out.println("釋放資源");
}
return ret;
}
}
<!-- 1:what,做說明增強 -->
<bean id="txManager" class="cn.wolfcode.tx.TransactionManager" />
<aop:config proxy-target-class="false">
<aop:aspect ref="txManager"> <!-- 關聯what -->
<!-- 2:where:在哪些語句中的哪些類中的哪些方法上做增強 -->
<aop:pointcut id="txPoint" expression="execution( * cn.wolfcode.service.*Service.*(..))"/>
<!-- 3:when:在方法執行的什麼時機做增強 -->
<aop:before method="begin" pointcut-ref="txPoint"/>
<aop:after-returning method="commit" pointcut-ref="txPoint"/>
<aop:after-throwing method="rollback" pointcut-ref="txPoint" throwing="tx"/>
<aop:after method="after" pointcut-ref="txPoint"/>
<!-- <aop:around method="aroundMethod" pointcut-ref="txPoint"/> -->
</aop:aspect>
</aop:config>
③:使用註解開發AOP
@Component
@Aspect //配置一個切面
public class TransactionManager {
@Pointcut("execution( * cn.wolfcode.service.impl.*.*(..))")
public void txPoint(){
}
//@Before("txPoint()")
public void begin(JoinPoint po){
System.out.println("開啓事務");
}
//@AfterReturning("txPoint()")
public void commit(JoinPoint po){
System.out.println("提交事務");
}
//@AfterThrowing(value="txPoint()",throwing="tx")
public void rollback(JoinPoint po,Throwable tx){
System.out.println("回滾事務"+" 方法的錯誤信息: "+tx.getMessage());
}
//@After("txPoint()")
public void after(JoinPoint po){
System.out.println("釋放資源");
}
@Around("txPoint()")
public Object aroundMethod(ProceedingJoinPoint po){
Object ret = null;
System.out.println("開啓事務");
try {
ret = po.proceed();//調用真實對象的方法,取的一個返回值,查詢有返回值
System.out.println("提交事務");
} catch (Throwable e) {
System.out.println("回滾事務 錯誤信息:"+e.getMessage());
} finally {
System.out.println("釋放資源");
}
return ret;
}
}
<!-- DI註解解析器 -->
<context:annotation-config/>
<!-- IoC註解解析器-->
<context:component-scan base-package="cn.wolfcode"/>
<!-- AOP註解解析器 -->
<!-- 當有接口的時候自動使用JDK代理, 沒有接口的時候自動使用CGLIB代理 AOP的本質是動態代理 -->
<aop:aspectj-autoproxy proxy-target-class="false"/>