spring上 -基於註解配置bean,動態代理,AOP筆記

银河小船儿發表於2024-10-16

用的是jdk8,spring框架裡jar包的下載可以自己搜到

註解用到的jar包。

60,註解配置Bean快速入門

基本介紹

程式碼結構:

UserDao.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Repository;

/*
* 使用 @Repository 標識該類是一個Repository,是一個持久層的類/物件
*/
@Repository
public class UserDao {
}

UserService.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Service;

/*
 * 使用 @Service 標識該類是一個Service類/物件
 */
@Service
public class UserService {
}

UserAction.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Controller;

/*
 * 使用 @Controller 標識該類是一個控制器Controller,通常這個類是一個Servlet
 */
@Controller
public class UserAction {
}

MyComponent.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Component;

/*
 * 使用 @Component 標識該類是一個元件,是一個通用的註解
 */
@Component
public class MyComponent {
}

beans.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:util="http://www.springframework.org/schema/util"
       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/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置容器要掃描的包
    老師解讀
    1. component-scan 要對指定包下的類進行掃描, 並建立物件到容器
    2. base-package 指定要掃描的包
    3. 含義是當spring容器建立/初始化時,就會掃描com.hspedu.spring.component包
       下的所有的 有註解 @Controller / @Service / @Respository / @Component類
       將其例項化,生成物件,放入到ioc容器
    4. resource-pattern="User*.class" 表示只掃描com.hspedu.spring.component 和它的子包下的User打頭的類

    -->
    <context:component-scan base-package="com.hspedu.spring.component" />
</beans>

SpringBeanTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.*;
import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import com.hspedu.spring.factory.MyStaticFactory;
import com.hspedu.spring.service.MemberServiceImpl;
import com.hspedu.spring.web.OrderAction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.awt.print.Book;

public class SpringBeanTest {

    //透過註解來配置Bean
    @Test
    public void setBeanByAnnotation() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        UserDao userDao = ioc.getBean(UserDao.class);
        UserService userService = ioc.getBean(UserService.class);
        UserAction userAction = ioc.getBean(UserAction.class);
        MyComponent myComponent = ioc.getBean(MyComponent.class);

        System.out.println("userDao=" + userDao);
        System.out.println("userService=" + userService);
        System.out.println("userAction=" + userAction);
        System.out.println("myComponent=" + myComponent);

        System.out.println("ok");

    }
}

執行結果:

63,注意事項和細節

4,resource-pattern="User*.class" 表示只掃描com.hspedu.spring.component 和它的子包下的User打頭的類,,這個和第3點自己記住就行。

還有其他3個注意事項都放在程式碼裡了,就是 排除哪些註解 ,指定掃描哪些註解,標記註解後,可以使用什麼來指定id值。

程式碼結構不變,beans.xml,UserDao.java,SpringBeanTest.java

beans.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:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:contect="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置容器要掃描的包
    老師解讀
    1. component-scan 要對指定包下的類進行掃描, 並建立物件到容器
    2. base-package 指定要掃描的包
    3. 含義是當spring容器建立/初始化時,就會掃描com.hspedu.spring.component包
       下的所有的 有註解 @Controller / @Service / @Respository / @Component類
       將其例項化,生成物件,放入到ioc容器
    4. resource-pattern="User*.class" 表示只掃描com.hspedu.spring.component 和它的子包下的User打頭的類
    -->
<!--    <context:component-scan base-package="com.hspedu.spring.component"/>-->

    <!--
        需求:如果我們希望排除某個包/子包下的某種型別的註解,可以透過exclude-filter來指定
        1. context:exclude-filter 指定要排除哪些類
        2. type 指定排除方式 annotation表示按照註解來排除
        3. expression="org.springframework.stereotype.Service" 指定要排除的註解的全路徑
    -->
<!--    <context:component-scan base-package="com.hspedu.spring.component">-->
<!--        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>-->
<!--    </context:component-scan>-->

    <!--
        需求:如果我們希望按照自己的規則,來掃描包/子包下的某些註解, 可以透過 include-filter
        1. use-default-filters="false" 表示不使用預設的過濾機制/掃描機制
        2. context:include-filter 表示要去掃描哪些類
        3. type="annotation" 按照註解方式來掃描/過濾
        4. expression="org.springframework.stereotype.Service" 指定要掃描的註解的全路徑
    -->
    <context:component-scan base-package="com.hspedu.spring.component" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

UserDao.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Repository;

/*
* 使用 @Repository 標識該類是一個Repository,是一個持久層的類/物件
* 1,標記註解後,類名首字母小寫作為id的值(預設)
* 2,value = "hspUserDao" 使用指定的 hspUserDao 作為 UserDao物件的 id
*/
@Repository(value = "hspUserDao")
public class UserDao {
}

SpringBeanTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.*;
import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import com.hspedu.spring.component.t.Pig;
import com.hspedu.spring.factory.MyStaticFactory;
import com.hspedu.spring.service.MemberServiceImpl;
import com.hspedu.spring.web.OrderAction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.awt.print.Book;

public class SpringBeanTest {

    //透過註解來配置Bean
    @Test
    public void setBeanByAnnotation() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        UserDao userDao = ioc.getBean(UserDao.class);
        //預設情況:標記註解後,類名首字母小寫作為 id 的值。也可以使用註解的 value 屬性指定 id 值,並且 value 可以省略。
        System.out.println("userDao=" + userDao);

        UserService userService = ioc.getBean(UserService.class);
        UserAction userAction = ioc.getBean(UserAction.class);
        MyComponent myComponent = ioc.getBean(MyComponent.class);

        System.out.println("userService=" + userService);
        System.out.println("userAction=" + userAction);
        System.out.println("myComponent=" + myComponent);

        System.out.println("ok");

    }
}

Debug結果:

透過下斷點,然後Debug來測試最後一個注意事項。 點 ioc -- beanFactory -- singletonObjects 就能看到

68,自己實現Spring註解配置Bean機制

思路分析:

程式碼結構:

其中 component 包裡的4個java檔案都是 上一篇筆記的程式碼,沒有改變。

ComponentScan.java

package com.hspedu.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Target 用於修飾 Annotation定義,用於指定被修飾的 Annotation 能用於修飾哪些程式元素
 * @Retention 只能用於修飾一個 Annotation定義,用於指定該 Annotation可以保留多長時間
 * 1. @Target(ElementType.TYPE)指定我們的ComponentScan註解可以修飾 Type程式元素
 * 2. @Retention(RetentionPolicy.RUNTIME) 指定ComponentScan註解 保留範圍:
      表示:編譯器將把註解記錄在class檔案中,當執行Java程式時,透過反射獲取該註解
 * 3. String value() default ""; 表示ComponentScan 可以傳入 value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value() default "";
}

HspSpringConfig.java

package com.hspedu.spring.annotation;

/**
 * 這是一個配置類, 作用類似我們原生spring的 beans.xml 容器配置檔案
 */
@ComponentScan(value = "com.hspedu.spring.component")
public class HspSpringConfig {
}

HspSpringApplicationContext.java

package com.hspedu.spring.annotation;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * HspSpringApplicationContext 類的作用類似Spring原生ioc容器
 */

public class HspSpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是透過反射建立的物件(基於註解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //構造器
    public HspSpringApplicationContext(Class configClass) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //獲取要掃描的包
        //1. 先得到HspSpringConfig配置的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 透過componentScan的value=> 即要掃描的包
        String path = componentScan.value();
        System.out.println("要掃描的包= " + path);

        //得到要掃描的包下的所有資源(類 .class)
        //1.得到類的載入器
        ClassLoader classLoader =
                HspSpringApplicationContext.class.getClassLoader();
        //2. 透過類的載入器獲取到要掃描的包的資源 url=》類似一個路徑
        //一定要把. 替換成 /
        path = path.replace(".","/");
        URL resource =
                classLoader.getResource("com/hspedu/spring/component");
        System.out.println("resource=" + resource);

        //3. 將要載入的資源(.class) 路徑下的檔案進行遍歷=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=============");
                System.out.println("=" + f.getAbsolutePath());
                //D:\javaProjects\Spring\spring5\out\production\spring5\com\hspedu\spring\component\UserService.class
                //獲取到 com.hspedu.spring.component.UserService
                String fileAbsolutePath = f.getAbsolutePath();
                //這裡我們只處理.class檔案
                if (fileAbsolutePath.endsWith(".class")) {
                    //1. 獲取到類名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //System.out.println("className=" + className);
                    //2. 獲取類的完整的路徑(全類名)
                    //老師解讀 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;
                    System.out.println("classFullName=" + classFullName);

                    //3. 判斷該類是不是需要注入容器, 就看該類是不是有註解 @Component @Service..
                    //這時,我們就得到老該類的Class物件
                    //Class clazz = Class.forName(classFullName)
                    //老師說一下
                    //1. Class clazz = Class.forName(classFullName) 可以反射載入類
                    //2. classLoader.loadClass(classFullName); 可以反射類的Class
                    //3. 區別是 : 上面方式後呼叫來類的靜態方法, 下面方法不會
                    Class<?> aClass = classLoader.loadClass(classFullName);
                    //4. aClass.isAnnotationPresent(Component.class) 判斷該類是否有 @Component
                    if (aClass.isAnnotationPresent(Component.class) ||
                            aClass.isAnnotationPresent(Controller.class) ||
                            aClass.isAnnotationPresent(Service.class) ||
                            aClass.isAnnotationPresent(Repository.class)) {

                        //這裡老師演示一個Component註解指定value,分配id
                        //老師就是演示了一下機制.

                        //這時就可以反射物件,並放入到容器中
                        Class<?> clazz = Class.forName(classFullName);
                        Object instance = clazz.newInstance();
                        //放入到容器中, 將類名的首字母小寫作為id
                        //StringUtils
                        ioc.put(StringUtils.uncapitalize(className) , instance);
                    }
                }
            }
        }
    }

    //編寫方法返回對容器中物件
    public Object getBean(String name) {
        return ioc.get(name);
    }
}

HspSpringApplicationContextTest.java

package com.hspedu.spring.annotation;

import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;


public class HspSpringApplicationContextTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        HspSpringApplicationContext ioc =
                new HspSpringApplicationContext(HspSpringConfig.class);

        UserAction userAction = (UserAction) ioc.getBean("userAction");
        System.out.println("userAction" + userAction);

        MyComponent myComponent = (MyComponent) ioc.getBean("myComponent");
        System.out.println("myComponent" + myComponent);

        UserService userService = (UserService) ioc.getBean("userService");
        System.out.println("userService=" + userService);

        UserDao userDao = (UserDao) ioc.getBean("userDao");
        System.out.println("userDao=" + userDao);

        System.out.println("ok");
    }
}

Debug結果:

執行結果:

76,自動裝配@Autowired

程式碼結構:

UserService.java

package com.hspedu.spring.component;

import org.springframework.stereotype.Service;

/*
 * 使用 @Service 標識該類是一個Service類/物件
 */
@Service
public class UserService {
    public void hi() {
        System.out.println("UserService hi()~");
    }
}

UserAction.java

package com.hspedu.spring.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import sun.java2d.pipe.SpanIterator;

/*
 * 使用 @Controller 標識該類是一個控制器Controller,通常這個類是一個Servlet
 */
@Controller()
public class UserAction {
    //xml配置 ref
    //老師說明 @Autowired
    //1)在IOC容器中查詢待裝配的元件的型別,如果有唯一的bean匹配(按照型別),則使用該bean裝配
    //2)如待裝配的型別對應的bean在IOC容器中有多個,則使用待裝配的屬性的屬性名作為id值再進行查詢,
    //  找到就裝配,找不到就拋異常
    @Autowired
    //private UserService userService;
    private UserService userService200;

    public void sayOk() {
//        System.out.println("UserAction.userService=" + userService);
//        System.out.println("userAction 裝配的 userService屬性=" + userService);
        System.out.println("userAction 裝配的 userService200屬性=" + userService200);
        //userService.hi();
    }
}

beans.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:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:contect="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
   <context:component-scan
           base-package="com.hspedu.spring.component"/>

   <!--配置兩個UserService物件-->
   <bean class="com.hspedu.spring.component.UserService" id="userService200"/>
   <bean class="com.hspedu.spring.component.UserService" id="userService300"/>
</beans>

SpringBeanTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.*;
import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import com.hspedu.spring.factory.MyStaticFactory;
import com.hspedu.spring.service.MemberServiceImpl;
import com.hspedu.spring.web.OrderAction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.awt.print.Book;

public class SpringBeanTest {

    //透過註解來配置Bean
    @Test
    public void setProByAutowired() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        UserService userService = ioc.getBean("userService", UserService.class);
        System.out.println("ioc容器中的userService=" + userService);

        UserService userService200 = ioc.getBean("userService200", UserService.class);
        System.out.println("ioc容器中的userService200=" + userService200);

        UserAction userAction = ioc.getBean("userAction", UserAction.class);
        //System.out.println("ioc容器中的userAction=" + userAction);
        userAction.sayOk();

    }
}

執行結果:

80,自動裝配@Resource

程式碼結構和上一節一樣,UserAction.java, SpringBeanTest.java 改變了

UserAction.java

package com.hspedu.spring.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import sun.java2d.pipe.SpanIterator;

import javax.annotation.Resource;

/*
 * 使用 @Controller 標識該類是一個控制器Controller,通常這個類是一個Servlet
 */
@Controller()
public class UserAction {
    ///老師說明 @Resource
    //1) @Resource有兩個屬性是比較重要的,分是name和type,Spring將@Resource註解的name屬性解析為bean的名字,
    //  而type屬性則解析為bean的型別.所以如果使用name屬性,則使用byName的自動注入策略,
    //  而使用type屬性時則使用byType自動注入策略
    //  比如@Resource(name = "userService") 表示裝配 id=userService物件
    //  比如@Resource(type = UserService.class) 表示按照UserService.class型別進行裝配, 這時要求容器中,只能有一個這樣型別的物件
    //2) 如果@Resource 沒有指定 name 和 type ,則先使用byName注入策略,
    //   如果匹配不上, 再使用byType策略, 如果都不成功,就會報錯

    //=================================
    //老師說明: @Autowired + @Qualifier(value = "userService02") 組合也可以完成指定 name/id 來進行自動裝配
    //指定id進行組裝, 也可以使用@Autowired 和 @Qualifier(value = "userService02")
    // 這時,是裝配的 id=userService02 , 需要兩個註解都需要寫上,用@Resource(name="userSerice200") 更簡單
//@Resource(name="userService200") @Resource(type = UserService.class) private UserService userService400; public void sayOk() { // System.out.println("UserAction.userService=" + userService); // System.out.println("userAction 裝配的 userService屬性=" + userService); System.out.println("userAction 裝配的 userService屬性=" + userService400); userService400.hi(); } }

SpringBeanTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.*;
import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import com.hspedu.spring.factory.MyStaticFactory;
import com.hspedu.spring.service.MemberServiceImpl;
import com.hspedu.spring.web.OrderAction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.awt.print.Book;

public class SpringBeanTest {

    //透過註解來配置Bean
    @Test
    public void setProByAutowired() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        UserService userService = ioc.getBean("userService", UserService.class);
        System.out.println("ioc容器中的userService=" + userService);

//        UserService userService200 = ioc.getBean("userService200", UserService.class);
//        System.out.println("ioc容器中的userService200=" + userService200);

        UserAction userAction = ioc.getBean("userAction", UserAction.class);
        //System.out.println("ioc容器中的userAction=" + userAction);
        userAction.sayOk();

    }
}

執行結果:

82,泛型依賴注入

各個類的關係圖:

類關係圖解釋:

如果 BookService 想用到 BookDao,可以把BookDao屬性裝配到BookService上去,但問題是 BaseService 和 BaseDao 下有很多子類,寫起來就很麻煩,所以spring提供了泛型依賴注入,就是把 BaseDao裝配到BaseService上去,只是形式上的裝配,不會例項化他們的物件,真正實現的是子類物件,在得到BookService的時候,傳入泛型<Book>,根據泛型依賴裝配,會自動把BookDao物件裝配到 BookService上去

底層機制 是 : 反射+註解+IO+String+泛型.

程式碼結構:

Book.java

package com.hspedu.spring.depinjection;

public class Book {
}

Phone.java

package com.hspedu.spring.depinjection;

public class Phone {
}

BaseDao.java

package com.hspedu.spring.depinjection;

//自定義泛型類
public abstract class BaseDao<T> {
    public abstract void save();
}

BookDao.java

package com.hspedu.spring.depinjection;

import org.springframework.stereotype.Repository;

@Repository
public class BookDao extends BaseDao<Book>{
    @Override
    public void save() {
        System.out.println("BookDao 的 save()..");
    }
}

PhoneDao.java

package com.hspedu.spring.depinjection;

import org.springframework.stereotype.Repository;

@Repository
public class PhoneDao extends BaseDao<Phone>{
    @Override
    public void save() {
        System.out.println("PhoneDao save()");
    }
}

BaseService.java

package com.hspedu.spring.depinjection;

import org.springframework.beans.factory.annotation.Autowired;

public class BaseService<T> {

    @Autowired
    private BaseDao<T> baseDao;

    public void save(){
        baseDao.save();
    }
}

BookService.java

package com.hspedu.spring.depinjection;

import org.springframework.stereotype.Service;

@Service
public class BookService extends BaseService<Book>{
    //並沒有寫屬性
}

PhoneService.java

package com.hspedu.spring.depinjection;

import org.springframework.stereotype.Service;

@Service
public class PhoneService extends BaseService<Phone>{
    //是把PhoneDao物件裝配到PhoneService,spring底層支援的
}

beans.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:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:contect="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

   <context:component-scan
           base-package="com.hspedu.spring.depinjection"/>
</beans>

SpringBeanTest.java

package com.hspedu.spring.test;

import com.hspedu.spring.bean.*;
import com.hspedu.spring.component.MyComponent;
import com.hspedu.spring.component.UserAction;
import com.hspedu.spring.component.UserDao;
import com.hspedu.spring.component.UserService;
import com.hspedu.spring.depinjection.PhoneService;
import com.hspedu.spring.factory.MyStaticFactory;
import com.hspedu.spring.service.MemberServiceImpl;
import com.hspedu.spring.web.OrderAction;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.awt.print.Book;

public class SpringBeanTest {

    //透過泛型依賴來配置Bean
    @Test
    public void setProByDependencyInjection() throws BeansException {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        PhoneService phoneService = ioc.getBean("phoneService", PhoneService.class);
        phoneService.save();

    }
}

執行結果:

86,傳統方法解決動態代理需求

程式碼結構:

介面Vehicle.java

package com.hspedu.spring.proxy2;

//該介面有run方法
public interface Vehicle {
    public void run();
}

Car.java

package com.hspedu.spring.proxy2;

public class Car implements Vehicle{
    @Override
    public void run() {
        System.out.println("交通工具開始執行了...");
        System.out.println("小汽車在公路 running..");
        System.out.println("交通工具停止執行了...");
    }
}

Ship.java

package com.hspedu.spring.proxy2;

public class Ship implements Vehicle{
    @Override
    public void run() {
        System.out.println("交通工具開始執行了...");
        System.out.println("大輪船在水上 running...");
        System.out.println("交通工具停止執行了...");
    }
}

TestVehicle.java

package com.hspedu.spring.proxy2;

import org.junit.Test;

public class TestVehicle {
    @Test
    public void run(){
        //OOP基礎
        Vehicle vehicle = new Car();
        vehicle.run();
        System.out.println("----------------");
        Vehicle vehicle1 = new Ship();
        vehicle1.run();
    }
}

執行結果:

如果想改某幾個物件,只能一個一個的改 ,得不到統一的管理

87,動態代理解決需求

動態代理解決思路,在呼叫方法時,使用反射機制,根據方法去決定呼叫哪個物件方法

程式碼結構:

Vehicle.java不變,動態代理機制是 debug得出的

Car.java

package com.hspedu.spring.proxy2;

public class Car implements Vehicle{
    @Override
    public void run() {
        //System.out.println("交通工具開始執行了...");
        System.out.println("小汽車在公路 running..");
        //System.out.println("交通工具停止執行了...");
    }
}

Ship.java

package com.hspedu.spring.proxy2;

public class Ship implements Vehicle{
    @Override
    public void run() {
        //System.out.println("交通工具開始執行了...");
        System.out.println("大輪船在水上 running...");
        //System.out.println("交通工具停止執行了...");
    }
}

VehicleProxyProvider.java

package com.hspedu.spring.proxy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * VehicleProxyProvider 該類可以返回一個代理物件.
 */
public class VehicleProxyProvider {
    //定義一個屬性
    //target_vehicle 表示真正要執行的物件
    //該物件實現了Vehicle介面
    private Vehicle target_vehicle;

    //構造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //編寫一個方法,可以返回一個代理物件, 該代理物件可以透過反射機制呼叫到被代理物件的方法
    //老師解讀
    //1. 這個方法非常重要, 理解有一定難度
    public Vehicle getProxy() {

        //得到類載入器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的物件/被執行物件 的介面資訊,底層是透過介面來完成呼叫
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();

        //建立InvocationHandler 物件
        //因為 InvocationHandler 是介面,所以我們可以透過匿名物件的方式來建立該物件

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke 方法是將來執行我們的target_vehicle的方法時,會呼叫到
             * o: 表示代理物件
             * method: 就是透過代理物件呼叫方法時,的哪個方法 代理物件.run()
             * args: 表示呼叫 代理物件.run(xx) 傳入的引數
             * return: 表示 代理物件.run(xx) 執行後的結果
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {
                System.out.println("交通工具開始執行了....");
                //這裡是我們的反射基礎 => OOP
                //method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()
                //target_vehicle 是? Ship物件
                //args 是null
                //這裡透過反射+動態繫結機制,就會執行到被代理物件的方法
                //執行完畢就返回
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止執行了....");
                return result;
            }
        };
        /**

          public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
          老師解讀
          1. Proxy.newProxyInstance() 可以返回一個代理物件
          2. ClassLoader loader: 類的載入器.
          3. Class<?>[] interfaces 就是將來要代理的物件的介面資訊
          4. InvocationHandler h 呼叫處理器/物件 有一個非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

TestVehicle.java

package com.hspedu.spring.proxy2;

import org.junit.Test;

public class TestVehicle {
    @Test
    public void run(){
        //OOP基礎
        Vehicle vehicle = new Car();

        //建立VehicleProxyProvider物件, 並且我們傳入的要代理的物件
        VehicleProxyProvider vehicleProxyProvider =
                new VehicleProxyProvider(vehicle);

        //獲取代理物件, 該物件可以代理執行方法
        //老師解讀
        //1. porxy 編譯型別 Vehicle
        //2. 執行型別 是代理型別 class com.sun.proxy.$Proxy9
        Vehicle proxy = vehicleProxyProvider.getProxy();
        System.out.println("proxy的編譯型別是 Vehicle");
        System.out.println("proxy的執行型別是 " + proxy.getClass());
        //下面老韓就要給大家解讀/debug怎麼 執行到 代理物件的 public Object invoke(Object o, Method method, Object[] args)
        //梳理完畢. proxy的編譯型別是 Vehicle, 執行型別是 class com.sun.proxy.$Proxy9
        //所以當執行run方法時,會執行到 代理物件的invoke

        //如何體現動態 [1. 被代理的物件 2. 方法]
        //1. proxy 執行型別是 com.sun.proxy.$Proxy0 該型別被轉型成 Vehicle
        // 因此可以呼叫 Vehicle 的介面方法
        //2. 當執行 run() 的時候會呼叫, 根據 Java 的動態繫結機制, 這時直接呼叫 Car的 run(),
        // 而是 proxy 物件的 invocationHandler 的 invoke 方法(!!!!!!)
        //3. invoke 方法使用反射機制來呼叫 run()方法注意這個 run 方法也可以是Vehicle 的其它方法)
        // 這時就可以在呼叫 run()方法前,進行前置處理和後置處理
        //4. 也就是說 proxy 的 target_vehicle 執行型別只要是實現了 Vehicle 介面
        // ,就可以去呼叫不同的方法, 是動態的,變化的,底層就是 使用反射完成的.
        proxy.run();
    }
}

執行結果:

91,動態代理深入(傳統方法解決)

傳統的解決思路,在各個方法的[前,執行過程, ]輸出日誌。

程式碼結構:

SmartAnimalable.java

package com.hspedu.spring.aop.proxy;

//介面
public interface SmartAnimalable {

    //求和
    float getSum(float i, float j);

    //求差
    float getSub(float i, float j);
}

SmartDog.java

package com.hspedu.spring.aop.proxy;

public class SmartDog implements SmartAnimalable{
    @Override
    public float getSum(float i, float j) {
        System.out.println("日誌-方法名-getSum-引數 " + i + " " + j);
        float result = i + j;
        System.out.println("方法內部列印result = " + result);
        System.out.println("日誌-方法名-getSum-結果result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        System.out.println("日誌-方法名-getSub-引數 " + i + " " + j);
        float result = i - j;
        System.out.println("方法內部列印result = " + result);
        System.out.println("日誌-方法名-getSub-結果result= " + result);
        return result;
    }
}

AopTest.java

package com.hspedu.spring.aop.proxy;

import org.junit.Test;

public class AopTest {
    @Test
    public void smartDogTest() {
        SmartAnimalable smartAnimalable = new SmartDog();
        smartAnimalable.getSum(10, 2);
        System.out.println("===========================");
        smartAnimalable.getSub(10, 2);
    }
}

執行結果:

93,動態代理深入(動態代理方式解決)

程式碼結構:

SmartAnimalable.java不變

SmartDog.java

package com.hspedu.spring.aop.proxy;

public class SmartDog implements SmartAnimalable{
    @Override
    public float getSum(float i, float j) {
        //System.out.println("日誌-方法名-getSum-引數 " + i + " " + j);
        float result = i + j;
        System.out.println("方法內部列印result = " + result);
        //System.out.println("日誌-方法名-getSum-結果result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        //System.out.println("日誌-方法名-getSub-引數 " + i + " " + j);
        float result = i - j;
        System.out.println("方法內部列印result = " + result);
        //System.out.println("日誌-方法名-getSub-結果result= " + result);
        return result;
    }
}

MyProxyProvider.java

package com.hspedu.spring.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * 可以返回一個動態代理物件, 可以執行SmartDog物件的方法
 */
public class MyProxyProvider {
    //定義我們要執行的目標物件, 該物件需要實現SmartAnimalable
    private SmartAnimalable target_obj;

    //構造器
    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }

    //方法, 可以返回代理物件,該代理物件可以執行目標物件
    public SmartAnimalable getProxy() {

        //1. 先到的類載入器/物件
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2. 得到要執行的目標物件的介面資訊
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 建立InvocationHandler
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("方法執行前-日誌-方法名-" + method.getName() + "-引數 "
                            + Arrays.asList(args)); //這裡從AOP看,就是一個橫切關注點-前置通知
                    //使用反射呼叫方法
                    result = method.invoke(target_obj, args);
                    System.out.println("方法執行正常結束-日誌-方法名-" + method.getName() + "-結果result= "
                            + result);//從AOP看, 也是一個橫切關注點-返回通知

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射執行方法時,出現異常,就會進入到catch{}
                    System.out.println("方法執行異常-日誌-方法名-" + method.getName()
                            + "-異常型別=" + e.getClass().getName());//從AOP看, 也是一個橫切關注點-異常通知
                } finally {//不管你是否出現異常,最終都會執行到finally{}
                    //從AOP的角度看, 也是一個橫切關注點-最終通知
                    System.out.println("方法最終結束-日誌-方法名-" + method.getName());
                }

                return result;
            }
        };
        //建立代理物件
        SmartAnimalable proxy =
                (SmartAnimalable) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

AopTest.java

package com.hspedu.spring.aop.proxy;

import org.junit.Test;

public class AopTest {
    @Test
    public void smartDogTestByProxy() {
        SmartAnimalable smartAnimalable = new SmartDog();

        //建立MyProxyProvider物件, 並且我們傳入的要代理的物件
        MyProxyProvider myProxyProvider =
                new MyProxyProvider(smartAnimalable);

        //獲取代理物件, 該物件可以代理執行方法
        SmartAnimalable proxy =
                myProxyProvider.getProxy();

        proxy.getSum(10, 2);
        System.out.println("====================");
        proxy.getSub(10, 2);
    }
}

執行結果:

95,AOP快速入門

基本介紹

可以把切面類裡的方法(想象成一把刀)切入到A類,B類任意一個位置

案例:

程式碼結構:

SmartAnimalable.java

package com.hspedu.spring.aop.aspectj;

//介面
public interface SmartAnimalable {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);
}

SmartDog.java

package com.hspedu.spring.aop.aspectj;

import org.springframework.stereotype.Component;

@Component //使用@Component 當spring容器啟動時,將 SmartDog注入到容器
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        float result = i + j;
        //int res = 9 / 0; //模擬一個算術異常
        System.out.println("方法內部列印result = " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        float result = i - j;
        System.out.println("方法內部列印result = " + result);
        return result;
    }
}

SmartAnimalAspect.java

package com.hspedu.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 切面類 , 類似於我們以前自己寫的MyProxyProvider,但是功能強大很多
 */
//@Order(value = 2)//表示該切面類執行的順序, value的值越小, 優先順序越高
@Aspect //表示是一個切面類[底層切面程式設計的支撐(動態代理+反射+動態繫結...)]
@Component //會注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

    //定義一個切入點, 在後面使用時可以直接引用, 提高了複用性
    @Pointcut(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)))")
    public void myPointCut() {
    }

    /**
     * 老師解讀
     * 1. @Before 表示前置通知:即在我們的目標物件執行方法前執行
     * 2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)
     * 指定切入到哪個類的哪個方法  形式是: 訪問修飾符 返回型別 全類名.方法名(形參列表)
     * 3. showBeginLog方法可以理解成就是一個切入方法, 這個方法名是可以程式設計師指定  比如:showBeginLog
     * 4. JoinPoint joinPoint 在底層執行時,由AspectJ切面框架, 會給該切入方法傳入 joinPoint物件
     * , 透過該方法,程式設計師可以獲取到 相關資訊
     *
     * @param joinPoint
     */

    //希望將f1方法切入到SmartDog-getSum前執行-前置通知
    //@Before(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //這裡我們使用定義好的切入點
    @Before(value = "myPointCut()")
    public void showBeginLog(JoinPoint joinPoint) {
        //透過連線點物件joinPoint 可以獲取方法簽名
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面類showBeginLog()[使用的myPointCut()]-方法執行前-日誌-方法名-" + signature.getName() + "-引數 "
                + Arrays.asList(joinPoint.getArgs()));
    }

    //返回通知:即把showSuccessEndLog方法切入到目標物件方法正常執行完畢後的地方
    //老韓解讀
    //1. 如果我們希望把目標方法執行的結果,返回給切入方法
    //2. 可以再 @AfterReturning 增加屬性 , 比如 returning = "res"
    //3. 同時在切入方法增加 Object res
    //4. 注意: returning = "res" 和 Object res 的 res名字一致
    //@AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res")
    //使用切入點
    @AfterReturning(value = "myPointCut()", returning = "res")
    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面類showSuccessEndLog()-方法執行正常結束-日誌-方法名-" + signature.getName() + " 返回的結果是=" + res);
    }


    //異常通知:即把showExceptionLog方法切入到目標物件方法執行發生異常的的catch{}
    //@AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable")
    //直接使用切入點表示式
    @AfterThrowing(value = "myPointCut()", throwing = "throwable")
    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面類showExceptionLog()-方法執行異常-日誌-方法名-" + signature.getName() + " 異常資訊=" + throwable);
    }

    //最終通知:即把showFinallyEndLog方法切入到目標方法執行後(不管是否發生異常,都要執行 finally{})
    //@After(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    //直接使用切入點
    @After(value = "myPointCut()")
    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-切面類showFinallyEndLog()-方法最終執行完畢-日誌-方法名-" + signature.getName());
    }
}

beans.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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

   <context:component-scan
           base-package="com.hspedu.spring.aop.aspectj"/>

   <!-- 開啟基於註解的AOP功能 -->
   <aop:aspectj-autoproxy/>
</beans>

AopAspectjTest.java

package com.hspedu.spring.aop.aspectj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopAspectjTest {
    @Test
    public void smartDogTestByProxy(){
        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        //這裡我們需要透過介面型別來獲取到注入的SmartDog物件-就是代理物件
        SmartAnimalable smartAnimalable =
                ioc.getBean(SmartAnimalable.class);

        smartAnimalable.getSum(10, 2);
//        System.out.println("smartAnimalable執行型別="
//                + smartAnimalable.getClass());//class com.sun.proxy.$Proxy17
    }
}

執行結果:

程式碼裡的getBean() 也相當於 動態代理裡的 getProxy() ,返回的是代理物件。

108,課後作業

這個題用到了細節5 和 下節的 切入表示式。

細節:

程式碼結構:

beans.xml 不變,和上節一樣。

介面UsbInterface.java

package com.hspedu.spring.aop.aspectj;

public interface UsbInterface {
    public void work();
}

Phone.java

package com.hspedu.spring.aop.aspectj;

import org.springframework.stereotype.Component;

@Component//將Phone物件當做一個元件注入容器
public class Phone implements UsbInterface{
    @Override
    public void work() {
        System.out.println("手機開始工作了...");
    }

}

Camera.java

package com.hspedu.spring.aop.aspectj;

import org.springframework.stereotype.Component;

@Component//將Camera物件當做一個元件注入容器
public class Camera implements UsbInterface{
    @Override
    public void work() {
        System.out.println("相機開始工作了...");
    }
}

SmartAnimalAspect.java

package com.hspedu.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 切面類 , 類似於我們以前自己寫的MyProxyProvider,但是功能強大很多
 */
//@Order(value = 2)//表示該切面類執行的順序, value的值越小, 優先順序越高
@Aspect //表示是一個切面類[底層切面程式設計的支撐(動態代理+反射+動態繫結...)]
@Component //會注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

    @Before(value = "execution(public void com.hspedu.spring.aop.aspectj.Phone.work()) || execution(public void com.hspedu.spring.aop.aspectj.Camera.work())")
    public void hi(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面類的hi()-執行的目標方法-" + signature.getName());
    }
}

AopAspectjTest.java

package com.hspedu.spring.aop.aspectj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopAspectjTest {
    @Test
    public void smartDogTestByProxy(){
        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        //這裡我們不能透過介面型別來獲取到注入的Phone物件-就是代理物件,因為Phone物件和Camera物件都實現了UsbInterface介面
//        UsbInterface bean = ioc.getBean(UsbInterface.class);
//        bean.work();

        UsbInterface phone = (UsbInterface) ioc.getBean("phone");
        UsbInterface camera = (UsbInterface) ioc.getBean("camera");

        phone.work();
        System.out.println("---------------------");
        camera.work();
    }
}

執行結果:

109,切入表示式

具體使用:

細節:

細節3程式碼結構:

beans.xml和上上一節一樣。

Car.java

package com.hspedu.spring.aop.aspectj;

import org.springframework.stereotype.Component;

@Component//將Phone物件當做一個元件注入容器
public class Car {
    public void run(){
        System.out.println("小汽車在running...");
    }
}

SmartAnimalAspect.java

package com.hspedu.spring.aop.aspectj;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 切面類 , 類似於我們以前自己寫的MyProxyProvider,但是功能強大很多
 */
//@Order(value = 2)//表示該切面類執行的順序, value的值越小, 優先順序越高
@Aspect //表示是一個切面類[底層切面程式設計的支撐(動態代理+反射+動態繫結...)]
@Component //會注入SmartAnimalAspect到容器
public class SmartAnimalAspect {

    //給Car配置一個前置通知
    @Before(value = "execution(public void com.hspedu.spring.aop.aspectj.Car.run())")
    public void ok1(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("切面類的ok1()-執行的目標方法-" + signature.getName());
    }
}

AopAspectjTest.java

package com.hspedu.spring.aop.aspectj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopAspectjTest {
    @Test
    public void smartDogTestByProxy(){
        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        //Car物件仍然是代理物件
        Car car = ioc.getBean(Car.class);
        car.run();
        System.out.println("car的執行型別=" + car.getClass());
    }
}

執行結果:

115,環繞通知

在第 95節程式碼的基礎上修改 ,可以把 SmartAnimalAspect.java刪了,重新複製成 SmartAnimalAspect2.java,還要修改 AopAspectjTest.java

SmartAnimalAspect2.java

package com.hspedu.spring.aop.aspectj;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * @author 韓順平
 * @version 1.0
 * 切面類 , 類似於我們以前自己寫的MyProxyProvider,但是功能強大很多
 */
@Aspect //表示是一個切面類[底層切面程式設計的支撐(動態代理+反射+動態繫結...)]
@Component //會注入SmartAnimalAspect2到容器
public class SmartAnimalAspect2 {

    //演示環繞通知的使用-瞭解
    //老師解讀
    //1. @Around: 表示這是一個環繞通知[完成其它四個通知的功能]
    //2. value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float)) 切入點表示式
    //3. doAround 表示要切入的方法 - 呼叫結構 try-catch-finally
    @Around(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        String methodName = joinPoint.getSignature().getName();
        try {
            //1.相當於前置通知完成的事情
            Object[] args = joinPoint.getArgs();
            List<Object> argList = Arrays.asList(args);
            System.out.println("AOP環繞通知[-前置通知]" + methodName + "方法開始了--引數有:" + argList);
            //在環繞通知中一定要呼叫joinPoint.proceed()來執行目標方法
            result = joinPoint.proceed();
            //2.相當於返回通知完成的事情
            System.out.println("AOP環繞通知[-返回通知]" + methodName + "方法結束了--結果是:" + result);
        } catch (Throwable throwable) {
            //3.相當於異常通知完成的事情
            System.out.println("AOP環繞通知[-異常通知]" + methodName + "方法拋異常了--異常物件:" + throwable);
        } finally {
            //4.相當於最終通知完成的事情
            System.out.println("AOP環繞通知[-後置通知]" + methodName + "方法最終結束了...");
        }
        return result;
    }
}

AopAspectjTest.java

package com.hspedu.spring.aop.aspectj;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopAspectjTest {
    @Test
    public void testDoAround() {
        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");


        SmartAnimalable smartAnimalable =
                ioc.getBean(SmartAnimalable.class);

        smartAnimalable.getSum(10, 2);
    }
}

執行結果:

119,基於XML配置的AOP

程式碼結構:

SmartAnimalable.java 和第95節的一樣,就是要注意 import 那引入的包名。

SmartDog.java 把@Component 註解註釋了。

SmartAnimalAspect.java

package com.hspedu.spring.aop.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;

import java.util.Arrays;

/**
 * @author 韓順平
 * @version 1.0
 *
 * 這是我們開發一個切面類, 但是不用註解,而是使用XML配置
 */
public class SmartAnimalAspect {
    public void showBeginLog(JoinPoint joinPoint) {
        //透過連線點物件joinPoint 可以獲取方法簽名
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面類showBeginLog()[使用的myPointCut()]-方法執行前-日誌-方法名-" + signature.getName() + "-引數 "
                + Arrays.asList(joinPoint.getArgs()));
    }

    public void showSuccessEndLog(JoinPoint joinPoint, Object res) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面類showSuccessEndLog()-方法執行正常結束-日誌-方法名-" + signature.getName() + " 返回的結果是=" + res);
    }


    public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面類showExceptionLog()-方法執行異常-日誌-方法名-" + signature.getName() + " 異常資訊=" + throwable);
    }

    public void showFinallyEndLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println("SmartAnimalAspect-XML配置-切面類showFinallyEndLog()-方法最終執行完畢-日誌-方法名-" + signature.getName());
    }
}

beans.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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--使用XML配置,完成AOP程式設計-->
   <!--配置一個切面類物件-bean-->
   <bean class="com.hspedu.spring.aop.xml.SmartAnimalAspect" id="smartAnimalAspect"/>

   <!--配置一個SmartDog物件-bean-->
   <bean class="com.hspedu.spring.aop.xml.SmartDog" id="smartDog"/>

   <!--配置切面類, 細節一定要引入 xmlns:aop-->
   <aop:config>
      <!--配置切入點-->
      <aop:pointcut id="myPointCut" expression="execution(public float com.hspedu.spring.aop.xml.SmartDog.getSum(float, float)))"/>

      <!--這裡指定切面物件,切面的前置,返回, 異常, 最終通知-->
      <aop:aspect ref="smartAnimalAspect" order="10">
         <!--配置前置通知-->
         <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
         <!--返回通知-->
         <aop:after-returning method="showSuccessEndLog" pointcut-ref="myPointCut" returning="res"/>
         <!--異常通知-->
         <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="throwable"/>
         <!--最終通知-->
         <aop:after method="showFinallyEndLog" pointcut-ref="myPointCut"/>
         <!--配置環繞通知-->
         <!--<aop:around method=""/>-->
      </aop:aspect>
   </aop:config>
</beans>

AopAspectjXMLTest.java

package com.hspedu.spring.aop.xml;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopAspectjXMLTest {
    @Test
    public void testDoAround() {
        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");
        SmartAnimalable smartAnimalable =
                ioc.getBean(SmartAnimalable.class);
        smartAnimalable.getSum(10, 2);
    }
}

執行結果:

123,作業(註解方式)

程式碼結構:

Cal.java

package com.hspedu.spring.aop.homework;

public interface Cal {
    public int cal1(int n);
    public int cal2(int n);
}

MyCal.java

package com.hspedu.spring.aop.homework;

import org.springframework.stereotype.Component;

@Component//將MyCal物件當做一個元件注入容器
public class MyCal implements Cal{
    @Override
    public int cal1(int n) {
        int res = 1;
        for(int i = 1; i <= n; i++) {
            res += i;
        }
        System.out.println("cal1 執行結果=" + res);
        return res;
    }

    @Override
    public int cal2(int n) {
        int res = 1;
        for(int i = 1; i <= n; i++) {
            res *= i;
        }
        System.out.println("cal2 執行結果=" + res);
        return res;
    }
}

MyCalAop.java

package com.hspedu.spring.aop.homework;

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

@Aspect //MyCalAOP 是一個切面類
@Component //MyCalAOP/物件 作為元件注入到spring容器
public class MyCalAop {

    //前置通知
    //這裡注意,如果目標類和切面類,在同一個包,可以省略包名
    //因為cal1和cal2方法,都要去輸出開始執行時間,因此使用MyCal.*
    @Before(value = "execution(public int MyCal.*(int))")
    public void calStart(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName() + " 執行, 開始執行時間=" + System.currentTimeMillis());
    }

    //返回通知
    //這裡注意,如果目標類和切面類,在同一個包,可以省略包名
    //因為cal1和cal2方法,都要去輸出開始執行時間,因此使用MyCal.*
    @AfterReturning(value = "execution(public int MyCal.*(int))")
    public void calEnd(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName() + " 執行, 結束時間=" + System.currentTimeMillis());

    }
}

beans.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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--掃描指定包-->
   <context:component-scan
           base-package="com.hspedu.spring.aop.homework"/>

   <!--啟用基於註解的AOP功能-->
   <aop:aspectj-autoproxy/>
</beans>

TestMyCalAOP.java

package com.hspedu.spring.aop.homework;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMyCalAOP {
    @Test
    public void testMyCalByAnnotation() {

        //得到spring容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("beans.xml");

        Cal cal = ioc.getBean(Cal.class);

        cal.cal1(10);
        System.out.println("-------------------------");
        cal.cal2(5);
    }
}

執行結果:

124,作業(基於XML)

程式碼結構:

Cal.java,MyCal.java , TestMyCalAOP.java和上節一樣,要注意引入的包名。

MyCalAOP.java

package com.hspedu.spring.aop.homework.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;


public class MyCalAOP {

    //前置通知
    public void calStart(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName() + " 基於XML配置- 執行, 開始執行時間=" + System.currentTimeMillis());

    }

    //返回通知
    public void calEnd(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        System.out.println(signature.getName() + " 基於XML配置- 執行, 結束時間=" + System.currentTimeMillis());

    }
}

beans.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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

   <!--配置MyCalAOP-bean-->
   <bean class="com.hspedu.spring.aop.homework.xml.MyCalAOP" id="myCalAOP" />

   <!--配置MyCal-bean-->
   <bean class="com.hspedu.spring.aop.homework.xml.MyCal" id="myCal"/>

   <!--配置切面類-->
   <aop:config>
      <!--配置切入點表示式-->
      <aop:pointcut id="myPointCut" expression="execution(public int com.hspedu.spring.aop.homework.xml.MyCal.*(int))"/>

      <!--配置前置,返回通知-->
      <aop:aspect ref="myCalAOP" order="10">
         <aop:before method="calStart" pointcut-ref="myPointCut"/>

         <aop:after-returning method="calEnd" pointcut-ref="myPointCut"/>
      </aop:aspect>
   </aop:config>
</beans>

執行結果:

相關文章