1. 程式人生 > >Spring學習,依賴注入(DI)、控制反轉(IoC)和麵向切面(AOP)

Spring學習,依賴注入(DI)、控制反轉(IoC)和麵向切面(AOP)

依賴注入(DI)和控制反轉(IoC)

在日常編碼中經常遇到在一個類中呼叫另一個類的多個方法,所以就在一個類中new出需要用到的類,也就是組合模式。比如說A類裡面需要用到很多B類裡面的方法,所以要在A類裡面new一個B。

public class A {

    private B b = new B();

    public void aa(){
        b.bb();
    }

}

但是這樣有個弊端,就是類一開始就定死了,萬一業務有變化需要別的實現呢?所以我們要面向介面程式設計。比如說A類裡面需要B類,我們可以將B設計成介面,然後根據業務需求建立不同的B介面實現類B1,B2。。。這樣需要什麼樣的實現就new什麼例項。

package com.jyh.test;

public class A {

    private B b = new B1();

    public void aa(){
        b.bb();
    }

public interface B {
    public void bb();
}

public class B1 implements B{

    public void bb() {

    }
}

public class B2 implements B{

    public void bb() {

    }
}

}

但是這樣還不好,這樣設計如果要改實現還要改A類裡面的程式碼替換B介面的實現類,我們可以將實現丟給呼叫者來實現,在A類裡面定義型別B的屬性,外部呼叫者可以通過setter方法和構造方法給該屬性賦值一個實現。

package com.jyh.test;

public class A {

    private B b;

    public A(B b) {
        super();
        this.b = b;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }

    public void aa(){
        b.bb();
    }

public static interface B {
public void bb(); } public static class B1 implements B{ public void bb() { System.out.println("b1"); } } public static class B2 implements B{ public void bb() { System.out.println("b2"); } } public static void main(String[] args) { A a = new A(new B2()); a.aa(); } }

這就是注入,將需要的實現通過某些方法給注入進去,依賴注入就是字面上的意思,因為A類的功能依賴B,所以注入B就稱作依賴注入,意思就是依賴的東西的注入。而IoC(控制反轉)的意思就是依賴的獲取被反轉了,本來A依賴B,所以A來建立一個例項,但是現在不是由A建立一個例項,而是別人建立好了例項傳遞給了A。
Spring容器中IoC部分就是做這種事,由Spring建立例項,由Spring進行注入。

Spring容器中的依賴注入(DI)和控制反轉(IoC)

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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 
        lazy-init,懶載入,在獲取bean的時候才建立例項 
        autowire:自動裝配,<property ref/>是指定裝配,autowire中byName值就是通過需要注入的屬性名字匹配對應id查詢bean
        比如說本類中有個名為userDao的屬性,那麼就在這裡查詢id為userDao的bean來例項化
    -->
    <bean id="userService" class="com.jyh.service.impl.UserServiceImpl" lazy-init="true" autowire="byName">
        <!-- property表示注入本類中的某個依賴,name為需要注入的依賴(屬性)的名字,
            ref指向另一個bean的id,也就是說對應屬性的型別,如果是基本型別則就用value直接賦值
            這個注入是setter方法注入 -->
        <property name="userDao" ref="userDao" /><!-- bean -->
    </bean>

     <!-- dao -->
    <bean id="userDao" class="com.jyh.dao.impl.UserDaoImpl"></bean>

    <bean id="user" class="com.jyh.domain.User">
        <!-- 基本型別用value直接賦值 -->
        <property name="name" value="無名"></property>
    </bean>

    <!-- 構造方法注入 -->
    <bean id="userServiceTwo" class="com.jyh.service.impl.UserServiceImpl">
        <!-- 指向引數所帶表示的例項bean,順序按照構造方法引數的順序 -->
        <constructor-arg ref="userDao"></constructor-arg>
        <!-- 按照引數型別賦值
        <constructor-arg type="java.lang.String" value="構造注入"/> -->
        <!-- 按照引數名賦值
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/> -->
        <!-- 按照引數順序賦值
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/> -->
    </bean>

</beans>

註解配置

編寫註解前需要在xml配置檔案中編寫如下配置

<!-- 將該類下的所有類配置成bean,只需要在該包下的類前面加上@Component(value = beanid)註解就可以不用再配置檔案中寫bean -->
<context:component-scan base-package="com.jyh.annotation"></context:component-scan>

<!-- 啟動註解 -->
<context:annotation-config></context:annotation-config>

註解

package com.jyh.annotation;

import javax.annotation.Resource;
/**
 * 註解可以在屬性上面也可以在setter方法上面,建議在setter上面
 */

import org.springframework.stereotype.Component;

@Component(value = "b") //有這個註解就代表著在配置檔案中註冊了bean(等同於bean標籤),value值為bean的id,預設為首字母小寫的類名
public class B {

    private String name;

    //這兩個加一起等同於@Resource(name = "aaa")
    //@Autowired                        //預設按照型別匹配
    //@Qualifier(value = "aaa")         //名稱匹配
    private A a;
    public String getName() {
        return name;
    }
    //這個註解代表著這個屬性是要通過spring注入來的,等同於bean標籤下面的property標籤
    //name屬性代表著該屬性在配置檔案中bean的id,也就是@Component()標籤中的value,沒寫預設為屬性名
    @Resource(name = "a")
    public void setName(String name) {
        this.name = name;
    }
    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }

}

面向切面(AOP)

砧板上放了一些面,一刀從中間切下,無論從面的這頭到那頭還是從那頭回到這頭都要經過刀面,刀面就是切面。
一個小區裡面的人出小區要經過保安,要進小區也要經過保安,保安可以檢視你是不是該小區的人,保安就是切面。

面向切面的應用:動態代理

JDK自帶的動態代理(藉助介面實現的動態代理)

//JDK自帶的動態代理實現,利用介面
    @Test
    public void proxy(){
        final UserDao userDao2 = new UserDaoImpl();
        UserDao userDao = (UserDao)Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(), 
                userDao2.getClass().getInterfaces(), 
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass().getName());
                        System.out.println("動態代理");
                        method.invoke(userDao2,args);
                        return null;
                    }
        });
        userDao.say("nnn");
    }

cglib的動態代理(不需要介面的動態代理)

//cglib實現動態代理,不需要介面
    @Test
    public void cglibProxy(){
        final UserDao userDao2 = new UserDaoImpl();
        Enhancer enhancer = new Enhancer();
        //設定攔截器
        enhancer.setCallback(new MethodInterceptor() {

            public Object intercept(Object arg0, Method arg1, Object[] arg2,
                    MethodProxy arg3) throws Throwable {
                arg1.invoke(userDao2, arg2);
                return null;
            }
        });
        //設定傳入被代理的類進去作為父類
        enhancer.setSuperclass(userDao2.getClass());
        //獲取被代理類
        UserDao userDao = (UserDao)enhancer.create();

        userDao.say("cglib實現動態代理");
    }

Spring容器中的面向切面

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="com.jyh.annotation"></context:component-scan>

    <!-- 啟動註解 -->
    <context:annotation-config></context:annotation-config>
    <!-- 註解aop -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- dao -->
    <bean id="userDao" class="com.jyh.dao.impl.UserDaoImpl"></bean>

    <!-- 切面類 -->
    <bean id="myIntercept" class="com.jyh.aop.MyIntercept"></bean>

    <!-- aop, proxy-target-class="true"設定用cglib實現代理 -->
    <aop:config>
        <!-- 切入點表示式 ,確定目標類,被代理的類,意思就是呼叫該類中的方法會觸發切面 -->
        <aop:pointcut expression="execution(* com.jyh.dao.impl.UserDaoImpl.*(..))"
            id="myProxy" />
        <!-- ref指向的物件就是切面,也就是公共呼叫的處理方法所在的類 -->
        <aop:aspect ref="myIntercept">

            <!-- 方法呼叫前的處理,method指向方法,pointcut-ref指向代理物件,也就是上面pointcut元素的id -->
            <aop:before method="beforeMethod" pointcut-ref="myProxy" />

            <!-- 方法呼叫後的處理 -->
            <aop:after-returning method="afterMethod"
                pointcut-ref="myProxy" returning="val" />

            <!-- 無論有沒有成功執行,最終都會呼叫這個方法 -->
            <aop:after method="finallyMethod" pointcut-ref="myProxy" />

            <!-- 異常通知 -->
            <aop:after-throwing method="exceptionMethod"
                throwing="ex" pointcut-ref="myProxy" />

            <!-- 環繞通知,能控制目標方法執行不執行 -->
            <aop:around method="aroundMethod" pointcut-ref="myProxy" />
        </aop:aspect>
    </aop:config>
</beans>
package com.jyh.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class MyIntercept {
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("連線點的名稱(方法名稱):"+methodName);
        System.out.println("目標類:"+joinPoint.getTarget().getClass());
        System.out.println("方法呼叫前");
        System.out.println("-------------------");
    }

    public void afterMethod(JoinPoint joinPoint,Object val){
        System.out.println("目標方法的返回值:" + val);
        System.out.println("方法呼叫後");
        System.out.println("-------------------");
    }

    public void finallyMethod(){
        System.out.println("最後始終會執行的方法");
        System.out.println("-------------------");
    }

    public void exceptionMethod(JoinPoint joinPoint,Throwable ex){
        System.out.println("異常執行的方法");
        System.out.println(ex.getMessage());
        System.out.println("-------------------");
    }

    public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("環繞控制前");
        joinPoint.proceed();//呼叫目標方法
        System.out.println("環繞控制後");
        System.out.println("-------------------");
    }
}

註解配置

<context:component-scan base-package="com.jyh.annotation"></context:component-scan>

<!-- 啟動註解 -->
<context:annotation-config></context:annotation-config>
<!-- 註解aop -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
package com.jyh.annotation;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @Before方法執行前 @AfterReturning方法執行後 @After最終始終都會執行的方法 
 * @AfterThrowing異常執行的方法 @Around環繞
 * @author OverrideRe
 *
 */

@Component("D")
@Aspect                                                         //表示本類是切面
public class D {
    //切入點,由於切入點都是方法,所以這裡也定義一個方法作為切入點,沒有實際意義
    @Pointcut("execution(* com.jyh.annotation.C.*(..))")        
    private void cc(){};                                        

    @Before("cc()")                                             
    public void before(){
        System.out.println("方法前");
    }

    @AfterReturning("cc()")
    public void after(){
        System.out.println("方法後");
    }
}

Spring整合hibernate和AOP應用之宣告式事務

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>

    <session-factory>
        <!-- 連結資料庫的使用者名稱 -->
        <property name="connection.username">root</property>
        <!-- 連結資料庫的密碼 -->
        <property name="connection.password">ying1995520***</property>
        <!-- 連結資料庫的驅動 -->
        <property name="connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <!-- 連結資料庫的url -->
        <property name="connection.url">
            jdbc:mysql://localhost:3306/db3
        </property>

        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <property name="hbm2ddl.auto">update</property>
        <!-- 顯示sql語句 -->
        <property name="show_sql">true</property>
        <!-- 格式化的顯示sql語句 -->
        <property name="format_sql">true</property>

        <mapping resource="com/jyh/domain/Account.hbm.xml"/>

    </session-factory>
</hibernate-configuration>