IOC容器
概念
IOC,全程Inversion of Control(控制反轉(zhuǎn))
通過(guò)控制反轉(zhuǎn)(創(chuàng)建對(duì)象的權(quán)限交給框架,所以叫反轉(zhuǎn))創(chuàng)建的對(duì)象被稱(chēng)為Spring Bean
,這個(gè)Bean和用new創(chuàng)建出來(lái)的對(duì)象是沒(méi)有任何區(qū)別的。
官方解釋?zhuān)篠pring 通過(guò) IoC 容器來(lái)管理所有 Java 對(duì)象的實(shí)例化和初始化,控制對(duì)象與對(duì)象之間的依賴(lài)關(guān)系。
舉個(gè)例子:有一杯水,杯子相當(dāng)于IOC容器,杯子里面的水相當(dāng)于Bean對(duì)象,這個(gè)杯子從開(kāi)始裝水,到水被喝完結(jié)束這過(guò)程,就相當(dāng)于容器控制對(duì)象的生命周期(從對(duì)象被創(chuàng)建,到最后被銷(xiāo)毀的過(guò)程)
控制反轉(zhuǎn)
-
控制反轉(zhuǎn)是一種思想而非技術(shù)。
-
控制反轉(zhuǎn)是為了降低程序耦合度,提高程序擴(kuò)展力。
-
控制反轉(zhuǎn),反轉(zhuǎn)的是什么?
-
- 將對(duì)象的創(chuàng)建權(quán)利交出去,交給第三方容器負(fù)責(zé)。
- 將對(duì)象和對(duì)象之間關(guān)系的維護(hù)權(quán)交出去,交給第三方容器負(fù)責(zé)。
-
控制反轉(zhuǎn)這種思想如何實(shí)現(xiàn)呢?
-
- DI(Dependency Injection):依賴(lài)注入
IOC過(guò)程說(shuō)明
依賴(lài)注入
DI(Dependency Injection):依賴(lài)注入,依賴(lài)注入實(shí)現(xiàn)了控制反轉(zhuǎn)的思想。
依賴(lài)注入:
- 指Spring創(chuàng)建對(duì)象的過(guò)程中,將對(duì)象依賴(lài)屬性(XML)通過(guò)配置進(jìn)行注入
依賴(lài)注入常見(jiàn)的實(shí)現(xiàn)方式包括兩種:
- 第一種:set注入
- 第二種:構(gòu)造注入
所以結(jié)論是:IOC 就是一種控制反轉(zhuǎn)的思想, 而 DI 是對(duì)IoC的一種具體實(shí)現(xiàn)。
Bean管理說(shuō)的是:Bean對(duì)象的創(chuàng)建,以及Bean對(duì)象中屬性的賦值(或者叫做Bean對(duì)象之間關(guān)系的維護(hù))。
依賴(lài)注入DI在Spring中的實(shí)現(xiàn)方式
Spring 的 IoC 容器就是 IoC思想的一個(gè)落地的產(chǎn)品實(shí)現(xiàn)。IoC容器中管理的組件也叫做 bean。在創(chuàng)建 bean 之前,首先需要?jiǎng)?chuàng)建IoC 容器。Spring 提供了IoC 容器的兩種實(shí)現(xiàn)方式:
①BeanFactory
這是 IoC 容器的基本實(shí)現(xiàn),是 Spring 內(nèi)部使用的接口。面向 Spring 本身,不提供給開(kāi)發(fā)人員使用。
②ApplicationContext
BeanFactory 的子接口,提供了更多高級(jí)特性。面向 Spring 的使用者,幾乎所有場(chǎng)合都使用 ApplicationContext 而不是底層的 BeanFactory。
③ApplicationContext接口 的主要實(shí)現(xiàn)類(lèi)
類(lèi)型名 | 簡(jiǎn)介 |
---|---|
ClassPathXmlApplicationContext | 通過(guò)讀取類(lèi)路徑下的 XML 格式的配置文件創(chuàng)建 IOC 容器對(duì)象 |
FileSystemXmlApplicationContext | 通過(guò)文件系統(tǒng)路徑讀取 XML 格式的配置文件創(chuàng)建 IOC 容器對(duì)象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些擴(kuò)展方法 refresh() 和 close() ,讓 ApplicationContext 具有啟動(dòng)、關(guān)閉和刷新上下文的能力。 |
WebApplicationContext | 專(zhuān)門(mén)為 Web 應(yīng)用準(zhǔn)備,基于 Web 環(huán)境創(chuàng)建 IOC 容器對(duì)象,并將對(duì)象引入存入 ServletContext 域中。 |
基于XML管理Spring Bean管理
一般項(xiàng)目里很少用這種XML管理Bean的,這里就不演示了,想看的可以轉(zhuǎn)戰(zhàn)
尚硅谷Spring6
基于注解管理Spring Bean管理
何為注解?注解是代碼中的一種特殊標(biāo)記
開(kāi)啟組件掃描過(guò)程
用注解創(chuàng)建Spring Bean的過(guò)程
(沒(méi)有用SpringBoot,默認(rèn)只用Spring,如果用SpringBoot就可以用@ComponentScan等等來(lái)指定掃描路徑)
Spring 默認(rèn)不使用注解裝配 Bean,因此我們需要在 Spring 的 XML 配置中,通過(guò) context:component-scan 元素開(kāi)啟 Spring Beans的自動(dòng)掃描功能。開(kāi)啟此功能后,Spring 會(huì)自動(dòng)從掃描指定的包(base-package 屬性設(shè)置)及其子包下的所有類(lèi),如果類(lèi)上使用了 @Component 注解,就將該類(lèi)裝配到容器中。
看一下XML配置文件com.atguigu.spring6
這包下的文件就會(huì)被掃描,只要掃描到 @Component 注解,就將該類(lèi)裝配到容器中
<?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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--開(kāi)啟組件掃描功能,這個(gè)包下的文件就會(huì)被掃描-->
<context:component-scan base-package="com.atguigu.spring6"></context:component-scan>
</beans>
注意:在使用 context:component-scan 元素開(kāi)啟自動(dòng)掃描功能前,首先需要在 XML 配置的一級(jí)標(biāo)簽 中添加 context 相關(guān)的約束。
情況一:最基本的掃描方式
和上面的例子一樣,當(dāng)前路徑下全掃
<context:component-scan base-package="com.atguigu.spring6">
</context:component-scan>
情況二:指定要排除的組件
可以排除某些不想被掃描的類(lèi)
<context:component-scan base-package="com.atguigu.spring6">
<!-- context:exclude-filter標(biāo)簽:指定排除規(guī)則 -->
<!--
type:設(shè)置排除或包含的依據(jù)
type="annotation",根據(jù)注解排除,expression中設(shè)置要排除的注解的全類(lèi)名
type="assignable",根據(jù)類(lèi)型排除,expression中設(shè)置要排除的類(lèi)型的全類(lèi)名
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:exclude-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>
情況三:僅掃描指定組件
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<!-- context:include-filter標(biāo)簽:指定在原有掃描規(guī)則的基礎(chǔ)上追加的規(guī)則 -->
<!-- use-default-filters屬性:取值false表示關(guān)閉默認(rèn)掃描規(guī)則 -->
<!-- 此時(shí)必須設(shè)置use-default-filters="false",因?yàn)槟J(rèn)規(guī)則即掃描指定包下所有類(lèi) -->
<!--
type:設(shè)置排除或包含的依據(jù)
type="annotation",根據(jù)注解排除,expression中設(shè)置要排除的注解的全類(lèi)名
type="assignable",根據(jù)類(lèi)型排除,expression中設(shè)置要排除的類(lèi)型的全類(lèi)名
-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--<context:include-filter type="assignable" expression="com.atguigu.spring6.controller.UserController"/>-->
</context:component-scan>
使用注解定義 Bean
Spring 提供了以下多個(gè)注解,這些注解可以直接標(biāo)注在 Java 類(lèi)上,將它們定義成 Spring Bean。
功能其實(shí)都一樣,比如說(shuō)我的controller類(lèi)叫XXXcontroller,并不是非要用@Controller來(lái)標(biāo)記,用@Service等其他注解也一樣可以完成目標(biāo),這里用不同名字的注解是為了更好的區(qū)分類(lèi)功能。
注解 | 說(shuō)明 |
---|---|
@Component | 該注解用于描述 Spring 中的 Bean,它是一個(gè)泛化的概念,僅僅表示容器中的一個(gè)組件(Bean),并且可以作用在應(yīng)用的任何層次,例如 Service 層、Dao 層等。 使用時(shí)只需將該注解標(biāo)注在相應(yīng)類(lèi)上即可。 |
@Repository | 該注解用于將數(shù)據(jù)訪(fǎng)問(wèn)層(Dao 層)的類(lèi)標(biāo)識(shí)為 Spring 中的 Bean,其功能與 @Component 相同。 |
@Service | 該注解通常作用在業(yè)務(wù)層(Service 層),用于將業(yè)務(wù)層的類(lèi)標(biāo)識(shí)為 Spring 中的 Bean,其功能與 @Component 相同。 |
@Controller | 該注解通常作用在控制層(如SpringMVC 的 Controller),用于將控制層的類(lèi)標(biāo)識(shí)為 Spring 中的 Bean,其功能與 @Component 相同。 |
注入注解@Autowired和@Resource
實(shí)驗(yàn)一:@Autowired注入
單獨(dú)使用@Autowired注解,默認(rèn)根據(jù)類(lèi)型裝配?!灸J(rèn)是byType】
看下Autowired源碼
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
源碼中有兩處需要注意:
-
第一處:該注解可以標(biāo)注在哪里?
@Target的元注解就標(biāo)記了注解的位置 -
-
ElementType.CONSTRUCTOR
構(gòu)造方法上 -
ElementType.METHOD
方法上 -
ElementType.PARAMETER
形參上 -
ElementType.FIELD
屬性上 -
ElementType.ANNOTATION_TYPE
注解上
-
-
第二處:該注解有一個(gè)required屬性,默認(rèn)值是true,表示在注入的時(shí)候要求被注入的Bean必須是存在的,如果不存在則報(bào)錯(cuò)。如果required屬性設(shè)置為false,表示注入的Bean存在或者不存在都沒(méi)關(guān)系,存在的話(huà)就注入,不存在的話(huà),也不報(bào)錯(cuò)。
@Autowired的幾種注入方式
這幾個(gè)場(chǎng)景都是說(shuō)明注解可以用的地方
①場(chǎng)景一:屬性注入(用的最多)
創(chuàng)建UserDao接口(假裝有數(shù)據(jù)庫(kù))
PS:Dao層和那個(gè)Mapper層是一個(gè)東西
package com.atguigu.spring6.dao;
public interface UserDao {
public void print();
}
創(chuàng)建UserDaoImpl實(shí)現(xiàn)
package com.atguigu.spring6.dao.impl;
import com.atguigu.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao/Mapper層執(zhí)行結(jié)束");
}
}
創(chuàng)建UserService接口
package com.atguigu.spring6.service;
public interface UserService {
public void out();
}
創(chuàng)建UserServiceImpl實(shí)現(xiàn)類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
創(chuàng)建UserController類(lèi)
package com.atguigu.spring6.controller;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public void out() {
userService.out();
System.out.println("Controller層執(zhí)行結(jié)束。");
}
}
測(cè)試一
package com.atguigu.spring6.bean;
import com.atguigu.spring6.controller.UserController;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
private Logger logger = LoggerFactory.getLogger(UserTest.class);
@Test
public void testAnnotation(){
//從配置文件中讀取Bean配置信息
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
//獲取Bean
UserController userController = context.getBean("userController", UserController.class);
//調(diào)用方法
userController.out();
logger.info("執(zhí)行成功");
}
}
測(cè)試結(jié)果:
所有調(diào)用的方法均可以正常執(zhí)行
以上構(gòu)造方法和setter方法都沒(méi)有提供,經(jīng)過(guò)測(cè)試,仍然可以注入成功。
②場(chǎng)景二:set注入
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
修改UserController類(lèi)
package com.atguigu.spring6.controller;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
//這種就相當(dāng)于把整個(gè)Service注入了,那么userService就可以直接調(diào)用
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller層執(zhí)行結(jié)束。");
}
}
測(cè)試:成功調(diào)用
③場(chǎng)景三:構(gòu)造方法注入
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
修改UserController類(lèi)
package com.atguigu.spring6.controller;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller層執(zhí)行結(jié)束。");
}
}
測(cè)試:成功調(diào)用
④場(chǎng)景四:形參上注入
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao;
//直接把注解加入到參數(shù)上面,就可以自動(dòng)注入,后面就可以調(diào)用了
public UserServiceImpl(@Autowired UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
修改UserController類(lèi)
package com.atguigu.spring6.controller;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
private UserService userService;
public UserController(@Autowired UserService userService) {
this.userService = userService;
}
public void out() {
userService.out();
System.out.println("Controller層執(zhí)行結(jié)束。");
}
}
測(cè)試:成功調(diào)用
⑤場(chǎng)景五:只有一個(gè)構(gòu)造函數(shù),無(wú)注解(可以省略)
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
// @Autowired 這個(gè)注解在只有一個(gè)有參構(gòu)造是可以省略!
private UserDao userDao;
//唯一的一個(gè)有參構(gòu)造,但凡多一個(gè)都注入不了
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
測(cè)試通過(guò)
當(dāng)有參數(shù)的構(gòu)造方法只有一個(gè)時(shí),@Autowired注解可以省略。
說(shuō)明:有多個(gè)構(gòu)造方法時(shí)呢?大家可以測(cè)試(再添加一個(gè)無(wú)參構(gòu)造函數(shù)),測(cè)試報(bào)錯(cuò)
⑥場(chǎng)景六:@Autowired注解和@Qualifier注解聯(lián)合
@Autowired注解是默認(rèn)byType進(jìn)行注入的
@Qualifier注解是默認(rèn)根據(jù)名稱(chēng)進(jìn)行注入的
添加dao層實(shí)現(xiàn)
package com.atguigu.spring6.dao.impl;
import com.atguigu.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoRedisImpl implements UserDao {
@Override
public void print() {
System.out.println("Redis Dao層執(zhí)行結(jié)束");
}
}
此時(shí)UserDao 就會(huì)有兩個(gè)實(shí)現(xiàn)類(lèi)(之前還創(chuàng)建了一個(gè)UserDao的實(shí)現(xiàn)類(lèi),加上這個(gè)就兩個(gè))。因?yàn)槭歉鶕?jù)類(lèi)型注入的,裝配的過(guò)程就會(huì)出現(xiàn)兩個(gè)對(duì)應(yīng)UserDao的對(duì)象。
測(cè)試:測(cè)試異常
錯(cuò)誤信息中說(shuō):不能裝配,UserDao這個(gè)Bean的數(shù)量等于2
怎么解決這個(gè)問(wèn)題呢?當(dāng)然要byName,根據(jù)名稱(chēng)進(jìn)行裝配了。
修改UserServiceImpl類(lèi)
用Qualifier指定bean(實(shí)現(xiàn)類(lèi))的名字,默認(rèn)首字母小寫(xiě)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
//先類(lèi)型,類(lèi)型注入不了就按指定名字
@Autowired
@Qualifier("userDaoImpl") // 指定bean(實(shí)現(xiàn)類(lèi))的名字,默認(rèn)首字母小寫(xiě)
private UserDao userDao;
@Override
public void out() {
userDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
總結(jié)
- @Autowired注解可以出現(xiàn)在:屬性上、構(gòu)造方法上、構(gòu)造方法的參數(shù)上、setter方法上。
- 當(dāng)帶參數(shù)的構(gòu)造方法只有一個(gè),@Autowired注解可以省略。()
- @Autowired注解默認(rèn)根據(jù)類(lèi)型注入。如果要根據(jù)名稱(chēng)注入的話(huà),需要配合@Qualifier注解一起使用。
實(shí)驗(yàn)二:@Resource注入
@Resource注解也可以完成屬性注入。那它和@Autowired注解有什么區(qū)別?
- @Resource注解是JDK擴(kuò)展包中的,也就是說(shuō)屬于JDK的一部分。所以該注解是標(biāo)準(zhǔn)注解,更加具有通用性。
- @Autowired注解是Spring框架自己的,而非JDK的。
- @Resource注解默認(rèn)根據(jù)名稱(chēng)裝配byName,未指定name時(shí),使用屬性名作為name。通過(guò)name找不到的話(huà)會(huì)自動(dòng)啟動(dòng)通過(guò)類(lèi)型byType裝配。
- @Autowired注解默認(rèn)根據(jù)類(lèi)型裝配byType,如果想根據(jù)名稱(chēng)裝配,需要配合@Qualifier注解一起用。
- @Resource注解用在屬性上、setter方法上。
- @Autowired注解用在屬性上、setter方法上、構(gòu)造方法上、構(gòu)造方法參數(shù)上。
源碼這里就不詳細(xì)展開(kāi)了
Resource注入的兩種情況
我們知道Resource會(huì)根據(jù)名字進(jìn)行注入,那么這個(gè)名字在制定了名字時(shí),也就是@Resource(value="名稱(chēng)")
時(shí),就會(huì)按照這個(gè)指定的名字注入。
如果沒(méi)有指定這個(gè)名字,就會(huì)按照被注入類(lèi)的屬性名稱(chēng)(不是類(lèi)里的屬性,是哪個(gè)myUserDao的名稱(chēng))來(lái)注入。
不指定時(shí):
**指定名稱(chēng)時(shí):**按照已經(jīng)設(shè)定好的屬性來(lái)注入
幾種注入方式
①場(chǎng)景一:根據(jù)name注入
修改UserDaoImpl類(lèi)
package com.atguigu.spring6.dao.impl;
import com.atguigu.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository("myUserDao")
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao層執(zhí)行結(jié)束");
}
}
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "myUserDao")
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
測(cè)試通過(guò),這種是正常的根據(jù)設(shè)定好的名稱(chēng)去注入
②場(chǎng)景二:name未知注入
修改UserDaoImpl類(lèi),我這里定義好了名字,但是注入的位置沒(méi)有標(biāo)記
package com.atguigu.spring6.dao.impl;
import com.atguigu.spring6.dao.UserDao;
import org.springframework.stereotype.Repository;
@Repository("myUserDao")
public class UserDaoImpl implements UserDao {
@Override
public void print() {
System.out.println("Dao層執(zhí)行結(jié)束");
}
}
修改UserServiceImpl類(lèi)
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao myUserDao;
@Override
public void out() {
myUserDao.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
測(cè)試通過(guò)
當(dāng)@Resource注解使用時(shí)沒(méi)有指定name的時(shí)候,還是根據(jù)name進(jìn)行查找,這個(gè)name是屬性名。
③場(chǎng)景三 其他情況
修改UserServiceImpl類(lèi),userDao1屬性名不存在
package com.atguigu.spring6.service.impl;
import com.atguigu.spring6.dao.UserDao;
import com.atguigu.spring6.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao1;
@Override
public void out() {
userDao1.print();
System.out.println("Service層執(zhí)行結(jié)束");
}
}
測(cè)試異常
根據(jù)異常信息得知:顯然當(dāng)通過(guò)name找不到的時(shí)候,自然會(huì)啟動(dòng)byType進(jìn)行注入,以上的錯(cuò)誤是因?yàn)閁serDao接口下有兩個(gè)實(shí)現(xiàn)類(lèi)導(dǎo)致的。所以根據(jù)類(lèi)型注入就會(huì)報(bào)錯(cuò)。
@Resource的set注入可以自行測(cè)試
總結(jié):
@Resource注解:默認(rèn)byName(指注解上的那個(gè)value名字)注入,沒(méi)有指定name時(shí)把屬性名當(dāng)做name,根據(jù)name找不到時(shí),才會(huì)byType注入。byType注入時(shí),某種類(lèi)型的Bean只能有一個(gè)。
手寫(xiě)IOC
我們都知道,Spring框架的IOC是基于Java反射機(jī)制實(shí)現(xiàn)的,下面我們先回顧一下java反射。
回顧反射
反射(上)
新建一個(gè)Car類(lèi)用于反射測(cè)試
public class Car {
private String name;
private int age;
private String color;
...一大堆setter getter constructor 這里不贅述了
private void run() {
System.out.println("私有方法-run.....");
}
}
要測(cè)試的內(nèi)容:
- 用反射獲取class對(duì)象的多種方式
- 獲取構(gòu)造方法
- 通過(guò)反射獲取類(lèi)中屬性
- 獲取方法
通過(guò)反射獲取class對(duì)象的幾種方法
public class TestCar {
//獲取Class對(duì)象的多種方式
@Test
public void test01() throws Exception {
// 1 類(lèi)名.class
Class class1=Car.class;
// 2 對(duì)象.getClass()
Car car = new Car();
Class class2 = car.getClass();
// 3 Class.forName("類(lèi)的全路徑")
//因?yàn)橛锌赡苈窂讲粚?duì)獲取不到,所以要捕獲或者拋出異常
Class class3 = Class.forName("com.cc.reflect.Car");
//把剛剛獲取的類(lèi)實(shí)例化
Car o=(Car) class3.getDeclaredConstructor().newInstance();
//輸出類(lèi)的信息
System.out.println(class1.toString());
System.out.println(class2.toString());
System.out.println(class3.toString());
}
}
打印輸出,可以看到,直接獲取到了類(lèi)的路徑信息
反射(中)
通過(guò)反射獲取class對(duì)象的構(gòu)造方法
這里有個(gè)條件,把Car的其中一個(gè)構(gòu)造方法改成私有的,原本是公開(kāi)的
package com.cc.reflect;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
public class TestCar {
//獲取構(gòu)造方法
@Test
public void test02() throws Exception {
//獲取class文件
//這里的car有三個(gè)屬性,并且已經(jīng)寫(xiě)好了構(gòu)造、setter、getter
Class clazz = Car.class;
//獲取所有構(gòu)造方法(public的),放在數(shù)組里(這個(gè)數(shù)組因?yàn)槭潜闅v公有方法的,所以這個(gè)數(shù)組只能發(fā)現(xiàn)并存放一個(gè)共有的構(gòu)造)
Constructor[] constructors=clazz.getConstructors();
for (Constructor c: constructors) {
System.out.println("構(gòu)造方法名稱(chēng)"+c.getName()+ "參數(shù)個(gè)數(shù)"+c.getParameterCount());
}
//獲取所有構(gòu)造方法(public和private的),放在數(shù)組里(這個(gè)數(shù)組因?yàn)槭潜闅v公有+私有方法的,所以這個(gè)數(shù)組發(fā)現(xiàn)并存放所有的構(gòu)造(無(wú)論是共有還是私有))
//PS:Declared聲稱(chēng)的
Constructor[] constructors1=clazz.getDeclaredConstructors();
for (Constructor c: constructors1) {
System.out.println("全部構(gòu)造方法名稱(chēng)"+c.getName()+ "參數(shù)個(gè)數(shù)"+c.getParameterCount());
}
}
}
運(yùn)行測(cè)試
指定有參構(gòu)造創(chuàng)建對(duì)象
- public的構(gòu)造方法
@Test
public void test03() throws Exception {
//獲取class文件
Class clazz = Car.class;
//指定有參數(shù)構(gòu)造創(chuàng)建對(duì)象
//1 構(gòu)造public
Constructor c1 = clazz.getConstructor(String.class, int.class, String.class);
Car car1 = (Car)c1.newInstance("夏利", 10, "紅色");
System.out.println(car1.toString());
}
- private的構(gòu)造方法
@Test
public void test03() throws Exception {
//獲取class文件
Class clazz = Car.class;
//2 構(gòu)造private
Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
//默認(rèn)是訪(fǎng)問(wèn)不了私有方法的,把這個(gè)選項(xiàng)打開(kāi)才能訪(fǎng)問(wèn)到,實(shí)例化的方法和之前一樣
c2.setAccessible(true);
Car car2 = (Car)c2.newInstance("捷達(dá)", 20, "藍(lán)色");
System.out.println(car2);
}
反射(下)
在類(lèi)里新增兩個(gè)方法,一個(gè)public一個(gè)private
通過(guò)反射獲取屬性
主要是先獲取field屬性數(shù)組,遍歷,匹配,設(shè)置可進(jìn)入,set值。
@Test
public void test04() throws Exception {
//獲取class文件
Class clazz = Car.class;
//通過(guò)無(wú)參構(gòu)造創(chuàng)建car對(duì)象
Car car = (Car)clazz.getDeclaredConstructor().newInstance();
//獲取屬性,屬性集合是個(gè)數(shù)組
Field[] fields=clazz.getDeclaredFields();
//遍歷這個(gè)數(shù)組獲取屬性,操作屬性
for (Field field : fields) {
//當(dāng)匹配到name屬性時(shí),進(jìn)入操作
if (field.getName().equals("name")){
//將私有屬性允許操作
field.setAccessible(true);
//設(shè)置值(car對(duì)象的name屬性設(shè)置為 五菱宏光)
field.set(car,"五菱宏光");
}
}
System.out.println(car.toString());
}
通過(guò)反射獲取類(lèi)內(nèi)方法
- 獲取所有public方法
@Test
public void test05(){
//創(chuàng)建對(duì)象
Car car = new Car("奔馳",10,"黑色");
Class clazz = car.getClass();
//1.獲取類(lèi)內(nèi)部的public方法
//和上面的屬性大同小異,也是通過(guò)用數(shù)組接收類(lèi)內(nèi)部的所有方法,組成一個(gè)Method數(shù)組
Method[] methods=clazz.getMethods();
for (Method m:methods){
System.out.println("所有public方法的名字"+m.getName());
}
}
- 在獲取的基礎(chǔ)上執(zhí)行某個(gè)public方法
反射里面的執(zhí)行一般用invoke方法public Object invoke(Object obj, Object... args)
@Test
public void test05() throws Exception {
//創(chuàng)建對(duì)象
Car car = new Car("奔馳",10,"黑色");
Class clazz = car.getClass();
//1.獲取類(lèi)內(nèi)部的public方法
//和上面的屬性大同小異,也是通過(guò)用數(shù)組接收類(lèi)內(nèi)部的所有方法,組成一個(gè)Method數(shù)組
Method[] methods=clazz.getMethods();
for (Method m:methods){
//System.out.println("所有public方法的名字"+m.getName());
if (m.getName().equals("test1")){
//遍歷到名字為test1的方法時(shí)執(zhí)行
m.invoke(car);
}
}
}
執(zhí)行結(jié)果:
- 獲取所有private方法
方法和上面一樣類(lèi)似,就是改一下調(diào)用的方法
@Test
public void test06() throws Exception {
//創(chuàng)建對(duì)象
Car car = new Car("奔馳",10,"黑色");
Class clazz = car.getClass();
//1.獲取類(lèi)內(nèi)部的public方法
//也是通過(guò)用數(shù)組接收類(lèi)內(nèi)部的所有方法,組成一個(gè)Method數(shù)組,通過(guò)getDeclaredMethods獲取
Method[] methods=clazz.getDeclaredMethods();
for (Method m:methods){
System.out.println("所有public+private方法的名字"+m.getName());
}
}
輸出結(jié)果:輸出所有私有和公有方法
- 在獲取的基礎(chǔ)上執(zhí)行某個(gè)private方法
@Test
public void test06() throws Exception {
//創(chuàng)建對(duì)象
Car car = new Car("奔馳",10,"黑色");
Class clazz = car.getClass();
//1.獲取類(lèi)內(nèi)部的public方法
//也是通過(guò)用數(shù)組接收類(lèi)內(nèi)部的所有方法,組成一個(gè)Method數(shù)組,通過(guò)getDeclaredMethods獲取
Method[] methods=clazz.getDeclaredMethods();
for (Method m:methods){
if (m.getName().equals("test2")){
//遍歷到名字為test2的私有方法時(shí)執(zhí)行
m.setAccessible(true);
m.invoke(car);
}
}
}
執(zhí)行結(jié)果:
手寫(xiě)IOC
先空著,占個(gè)位,以后再補(bǔ)
AOP
計(jì)算器的例子
聲明計(jì)算器接口Calculator,包含加減乘除的抽象方法
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
創(chuàng)建對(duì)應(yīng)實(shí)現(xiàn)類(lèi)
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法內(nèi)部 result = " + result);
return result;
}
.....很多接口方法實(shí)現(xiàn)
}
此時(shí)來(lái)了一個(gè)需求,我要在輸出的時(shí)候打日志log
如果一行一行代碼寫(xiě),就會(huì)變成這樣
public class CalculatorLogImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("[日志] add 方法開(kāi)始了,參數(shù)是:" + i + "," + j);
int result = i + j;
System.out.println("方法內(nèi)部 result = " + result);
System.out.println("[日志] add 方法結(jié)束了,結(jié)果是:" + result);
return result;
}
......省略很多相似的實(shí)現(xiàn)方法,但是都是帶了很多l(xiāng)og
}
打個(gè)比方我要是有一天需要改每一個(gè)日志的輸出方法,那么就會(huì)非常難搞,每一個(gè)都得這么改,過(guò)于復(fù)雜。
引出的問(wèn)題
①現(xiàn)有代碼缺陷**
針對(duì)帶日志功能的實(shí)現(xiàn)類(lèi),我們發(fā)現(xiàn)有如下缺陷:
- 對(duì)核心業(yè)務(wù)功能有干擾,導(dǎo)致程序員在開(kāi)發(fā)核心業(yè)務(wù)功能時(shí)分散了精力
- 附加功能分散在各個(gè)業(yè)務(wù)功能方法中,不利于統(tǒng)一維護(hù)
②解決思路
解決這兩個(gè)問(wèn)題,核心就是:解耦。我們需要把附加功能從業(yè)務(wù)功能代碼中抽取出來(lái)。
③困難
解決問(wèn)題的困難:要抽取的代碼在方法內(nèi)部,靠以前把子類(lèi)中的重復(fù)代碼抽取到父類(lèi)的方式?jīng)]法解決。所以需要引入新的技術(shù)。
現(xiàn)有解決方案-代理模式
代理模式概念
①介紹
二十三種設(shè)計(jì)模式中的一種,屬于結(jié)構(gòu)型模式。它的作用就是通過(guò)提供一個(gè)代理類(lèi),讓我們?cè)谡{(diào)用目標(biāo)方法的時(shí)候,不再是直接對(duì)目標(biāo)方法進(jìn)行調(diào)用,而是通過(guò)代理類(lèi)間接調(diào)用。讓不屬于目標(biāo)方法核心邏輯的代碼從目標(biāo)方法中剝離出來(lái)——解耦。調(diào)用目標(biāo)方法時(shí)先調(diào)用代理對(duì)象的方法,減少對(duì)目標(biāo)方法的調(diào)用和打擾,同時(shí)讓附加功能能夠集中在一起也有利于統(tǒng)一維護(hù)。
使用代理后:
相當(dāng)于把目標(biāo)對(duì)象用代理對(duì)象包裹起來(lái)
②生活中的代理
- 廣告商找大明星拍廣告需要經(jīng)過(guò)經(jīng)紀(jì)人
- 合作伙伴找大老板談合作要約見(jiàn)面時(shí)間需要經(jīng)過(guò)秘書(shū)
- 房產(chǎn)中介是買(mǎi)賣(mài)雙方的代理
③相關(guān)術(shù)語(yǔ)
- 代理:將非核心邏輯剝離出來(lái)以后,封裝這些非核心邏輯的類(lèi)、對(duì)象、方法。
- 目標(biāo):被代理“套用”了非核心邏輯代碼的類(lèi)、對(duì)象、方法。
靜態(tài)代理
創(chuàng)建靜態(tài)代理類(lèi)(代理類(lèi)是另外的類(lèi),不是在原有類(lèi)上操作的):
這也就是相當(dāng)于解耦了,唯一的區(qū)別是不用動(dòng)原來(lái)的核心代碼,但是本質(zhì)上相當(dāng)于copy出來(lái)了一份,在copy的基礎(chǔ)上進(jìn)行修改增強(qiáng)。
public class CalculatorStaticProxy implements Calculator {
// 將被代理的目標(biāo)對(duì)象聲明為成員變量
private Calculator target;
public CalculatorStaticProxy(Calculator target) {
this.target = target;
}
@Override
public int add(int i, int j) {
// 附加功能由代理類(lèi)中的代理方法來(lái)實(shí)現(xiàn)
System.out.println("[日志] add 方法開(kāi)始了,參數(shù)是:" + i + "," + j);
// 通過(guò)目標(biāo)對(duì)象來(lái)實(shí)現(xiàn)核心業(yè)務(wù)邏輯
int addResult = target.add(i, j);
System.out.println("[日志] add 方法結(jié)束了,結(jié)果是:" + addResult);
return addResult;
}
}
靜態(tài)代理確實(shí)實(shí)現(xiàn)了解耦,但是由于代碼都寫(xiě)死了,完全不具備任何的靈活性。就拿日志功能來(lái)說(shuō),將來(lái)其他地方也需要附加日志,那還得再聲明更多個(gè)靜態(tài)代理類(lèi),那就產(chǎn)生了大量重復(fù)的代碼,日志功能還是分散的,沒(méi)有統(tǒng)一管理。
提出進(jìn)一步的需求:將日志功能集中到一個(gè)代理類(lèi)中,將來(lái)有任何日志需求,都通過(guò)這一個(gè)代理類(lèi)來(lái)實(shí)現(xiàn)。這就需要使用動(dòng)態(tài)代理技術(shù)了。
動(dòng)態(tài)代理
我們創(chuàng)建一個(gè)代理類(lèi),來(lái)幫助我們?cè)诓僮髑昂筒僮骱筝敵鋈罩?br>
生產(chǎn)代理對(duì)象的工廠(chǎng)類(lèi):
源碼,看一下就行
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy(){
/**
* newProxyInstance():創(chuàng)建一個(gè)代理實(shí)例
* 其中有三個(gè)參數(shù):
* 1、classLoader:加載動(dòng)態(tài)生成的代理類(lèi)的類(lèi)加載器
* 2、interfaces:目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口的class對(duì)象所組成的數(shù)組
* 3、invocationHandler:設(shè)置代理對(duì)象實(shí)現(xiàn)目標(biāo)對(duì)象方法的過(guò)程,即代理類(lèi)中如何重寫(xiě)接口中的抽象方法
*/
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 幾個(gè)參數(shù)的含義
* proxy:代理對(duì)象
* method:代理對(duì)象需要實(shí)現(xiàn)的方法,即其中需要重寫(xiě)的方法
* args:method所對(duì)應(yīng)方法的參數(shù)
*/
Object result = null;
try {
System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",參數(shù):"+ Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",結(jié)果:"+ result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",異常:"+e.getMessage());
} finally {
System.out.println("[動(dòng)態(tài)代理][日志] "+method.getName()+",方法執(zhí)行完畢");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
AOP概念以及相關(guān)術(shù)語(yǔ)
AOP(Aspect Oriented Programming)是一種設(shè)計(jì)思想,是軟件設(shè)計(jì)領(lǐng)域中的面向切面編程,它是面向?qū)ο缶幊痰囊环N補(bǔ)充和完善,它以通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理方式實(shí)現(xiàn),在不修改源代碼的情況下,給程序動(dòng)態(tài)統(tǒng)一添加額外功能的一種技術(shù)。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
相關(guān)術(shù)語(yǔ)
①橫切關(guān)注點(diǎn)
分散在每個(gè)各個(gè)模塊中解決同一樣的問(wèn)題,如用戶(hù)驗(yàn)證、日志管理、事務(wù)處理、數(shù)據(jù)緩存都屬于橫切關(guān)注點(diǎn)。
從每個(gè)方法中抽取出來(lái)的同一類(lèi)非核心業(yè)務(wù)。在同一個(gè)項(xiàng)目中,我們可以使用多個(gè)橫切關(guān)注點(diǎn)對(duì)相關(guān)方法進(jìn)行多個(gè)不同方面的增強(qiáng)。
這個(gè)概念不是語(yǔ)法層面的,而是根據(jù)附加功能的邏輯上的需要:有十個(gè)附加功能,就有十個(gè)橫切關(guān)注點(diǎn)。
②通知(增強(qiáng))
增強(qiáng),通俗說(shuō),就是你想要增強(qiáng)的功能,比如 安全,事務(wù),日志等。
每一個(gè)橫切關(guān)注點(diǎn)上要做的事情都需要寫(xiě)一個(gè)方法來(lái)實(shí)現(xiàn),這樣的方法就叫通知方法。
- 前置通知:在被代理的目標(biāo)方法前執(zhí)行
- 返回通知:在被代理的目標(biāo)方法成功結(jié)束后執(zhí)行(正常結(jié)束)
- 異常通知:在被代理的目標(biāo)方法異常結(jié)束后執(zhí)行(拋出異常)
- 后置通知:在被代理的目標(biāo)方法最終結(jié)束后執(zhí)行(最后執(zhí)行)
- 環(huán)繞通知:使用try…catch…finally結(jié)構(gòu)圍繞整個(gè)被代理的目標(biāo)方法,包括上面四種通知對(duì)應(yīng)的所有位置
③切面
封裝通知方法的類(lèi)。
④目標(biāo)
被代理的目標(biāo)對(duì)象。
⑤代理
向目標(biāo)對(duì)象應(yīng)用通知之后創(chuàng)建的代理對(duì)象。
⑥連接點(diǎn)
這也是一個(gè)純邏輯概念,不是語(yǔ)法定義的。
把方法排成一排,每一個(gè)橫切位置看成x軸方向,把方法從上到下執(zhí)行的順序看成y軸,x軸和y軸的交叉點(diǎn)就是連接點(diǎn)。通俗說(shuō),就是spring允許你使用通知的地方
⑦切入點(diǎn)
定位連接點(diǎn)的方式。
每個(gè)類(lèi)的方法中都包含多個(gè)連接點(diǎn),所以連接點(diǎn)是類(lèi)中客觀存在的事物(從邏輯上來(lái)說(shuō))。
如果把連接點(diǎn)看作數(shù)據(jù)庫(kù)中的記錄,那么切入點(diǎn)就是查詢(xún)記錄的 SQL 語(yǔ)句。
Spring 的 AOP 技術(shù)可以通過(guò)切入點(diǎn)定位到特定的連接點(diǎn)。通俗說(shuō),要實(shí)際去增強(qiáng)的方法
切點(diǎn)通過(guò) org.springframework.aop.Pointcut 接口進(jìn)行描述,它使用類(lèi)和方法作為連接點(diǎn)的查詢(xún)條件。
作用
-
簡(jiǎn)化代碼:把方法中固定位置的重復(fù)的代碼抽取出來(lái),讓被抽取的方法更專(zhuān)注于自己的核心功能,提高內(nèi)聚性。
-
代碼增強(qiáng):把特定的功能封裝到切面類(lèi)中,看哪里有需要,就往上套,被套用了切面邏輯的方法就被切面給增強(qiáng)了。
基于注解AOP
前期說(shuō)明
本質(zhì)上:動(dòng)態(tài)代理的底層是靜態(tài)代理,在配置好動(dòng)態(tài)代理類(lèi)之后,系統(tǒng)按照你的配置要求,對(duì)目標(biāo)類(lèi)生成靜態(tài)代理類(lèi),執(zhí)行的時(shí)候就去自動(dòng)執(zhí)行增強(qiáng)過(guò)的動(dòng)態(tài)代理類(lèi)了
- 動(dòng)態(tài)代理分為JDK動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理
- 當(dāng)目標(biāo)類(lèi)有接口的情況使用JDK動(dòng)態(tài)代理和cglib動(dòng)態(tài)代理,沒(méi)有接口時(shí)只能使用cglib動(dòng)態(tài)代理
- JDK動(dòng)態(tài)代理動(dòng)態(tài)生成的代理類(lèi)會(huì)在com.sun.proxy包下,類(lèi)名為$proxy1,和目標(biāo)類(lèi)實(shí)現(xiàn)相同的接口
- cglib動(dòng)態(tài)代理動(dòng)態(tài)生成的代理類(lèi)會(huì)和目標(biāo)在在相同的包下,會(huì)繼承目標(biāo)類(lèi)
- 動(dòng)態(tài)代理(InvocationHandler):JDK原生的實(shí)現(xiàn)方式,需要被代理的目標(biāo)類(lèi)必須實(shí)現(xiàn)接口。因?yàn)檫@個(gè)技術(shù)要求代理對(duì)象和目標(biāo)對(duì)象實(shí)現(xiàn)同樣的接口(兄弟兩個(gè)拜把子模式)。
- cglib:通過(guò)繼承被代理的目標(biāo)類(lèi)(認(rèn)干爹模式)實(shí)現(xiàn)代理,所以不需要目標(biāo)類(lèi)實(shí)現(xiàn)接口。
- AspectJ:是AOP思想的一種實(shí)現(xiàn)。本質(zhì)上是靜態(tài)代理,將代理邏輯“織入”被代理的目標(biāo)類(lèi)編譯得到的字節(jié)碼文件,所以最終效果是動(dòng)態(tài)的。weaver就是織入器。Spring只是借用了AspectJ中的注解。
前期準(zhǔn)備
引入AOP的依賴(lài)
創(chuàng)建一個(gè)計(jì)算器類(lèi)的接口以及對(duì)應(yīng)實(shí)現(xiàn)類(lèi)
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
實(shí)現(xiàn)類(lèi):
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法內(nèi)部 result = " + result);
return result;
}
....省略其他代碼
}
配置文件
首先要在xml配置文件中開(kāi)啟組件掃描
在Spring的配置文件中配置:
創(chuàng)建一個(gè)bean.xml文件,并寫(xiě)入相關(guān)配置
<!-- 開(kāi)啟組件掃描 -->
<context:component-scan base-package="com.cc.annotationAop"></context:component-scan>
<!-- 開(kāi)啟AspectJ的自動(dòng)代理,為目標(biāo)對(duì)象自動(dòng)生成代理 -->
<aop:aspectj-autoproxy />
切入點(diǎn)表達(dá)式語(yǔ)法
看一下切入點(diǎn)表達(dá)式的構(gòu)成
切入點(diǎn)表達(dá)式:
execution(訪(fǎng)問(wèn)修飾符 增強(qiáng)方法返回類(lèi)型 方法所在類(lèi)全類(lèi)名.方法名(方法參數(shù)))
來(lái)點(diǎn)細(xì)節(jié):
-
用*號(hào)代替“權(quán)限修飾符”和“返回值”部分表示“權(quán)限修飾符”和“返回值”不限
-
在包名的部分,一個(gè)“*”號(hào)只能代表包的層次結(jié)構(gòu)中的一層,表示這一層是任意的。
- 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
-
在包名的部分,使用“*…”表示包名任意、包的層次深度任意
-
在類(lèi)名的部分,類(lèi)名部分整體用*號(hào)代替,表示類(lèi)名任意
-
在類(lèi)名的部分,可以使用*號(hào)代替類(lèi)名的一部分
- 例如:*Service匹配所有名稱(chēng)以Service結(jié)尾的類(lèi)或接口
-
在方法名部分,可以使用*號(hào)表示方法名任意
-
在方法名部分,可以使用*號(hào)代替方法名的一部分
- 例如:*Operation匹配所有方法名以O(shè)peration結(jié)尾的方法
-
在方法參數(shù)列表部分,使用(…)表示參數(shù)列表任意
-
在方法參數(shù)列表部分,使用(int,…)表示參數(shù)列表以一個(gè)int類(lèi)型的參數(shù)開(kāi)頭
-
在方法參數(shù)列表部分,基本數(shù)據(jù)類(lèi)型和對(duì)應(yīng)的包裝類(lèi)型是不一樣的
- 切入點(diǎn)表達(dá)式中使用 int 和實(shí)際方法中 Integer 是不匹配的
-
在方法返回值部分,如果想要明確指定一個(gè)返回值類(lèi)型,那么必須同時(shí)寫(xiě)明權(quán)限修飾符
- 例如:execution(public int …Service.(…, int)) 正確
例如:execution( int *…Service.(…, int)) 錯(cuò)誤
- 例如:execution(public int …Service.(…, int)) 正確
@Before()和@After()
以前置通知和后置通知為例,對(duì)方法進(jìn)行增強(qiáng)。
// @Aspect表示這個(gè)類(lèi)是一個(gè)切面類(lèi)
@Aspect
// @Component注解保證這個(gè)切面類(lèi)能夠放入IOC容器
@Component
public class LogAspect {
// 設(shè)置切入點(diǎn)和通知類(lèi)型
// 切入點(diǎn)表達(dá)式:execution(訪(fǎng)問(wèn)修飾符 增強(qiáng)方法返回類(lèi)型 方法所在類(lèi)全類(lèi)名.方法名(方法參數(shù)))
// 通知類(lèi)型:
// 前置@Before(value = "切入點(diǎn)表達(dá)式配置切入點(diǎn)"),這里增強(qiáng)了CalculatorImpl的add方法的任意參數(shù)
@Before(value = "execution(public int com.cc.annotationAop.CalculatorImpl.add(..))")
public void beforeMethod(JoinPoint joinPoint){
//可以根據(jù)JoinPoint獲取對(duì)象信息(方法名,參數(shù)等等)
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
// 后置@After(),這里增強(qiáng)了CalculatorImpl的任意方法的任意參數(shù)
@After("execution(* com.cc.annotationAop.CalculatorImpl.*(..))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
}
}
測(cè)試一下:
注意:AOP的情況下,手動(dòng)創(chuàng)建對(duì)象是沒(méi)辦法增強(qiáng)的
public class testAop {
@Test
public void testAdd(){
//xml方式獲取對(duì)象(注意,對(duì)象必須由框架創(chuàng)建,不能手動(dòng)new,手動(dòng)new就不執(zhí)行AOP了)
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
Calculator calculator = ac.getBean( Calculator.class);
calculator.add(1, 2);
}
}
測(cè)試通過(guò),可以發(fā)現(xiàn),按照預(yù)想結(jié)果輸出,JoinPoint信息也能輸出
以上就是兩種通知類(lèi)型,還有許多其他的通知形式
@AfterReturning()
和@After()差不太多,主要是多了一個(gè)方法返回值
如果和@After()同時(shí)存在,那么先執(zhí)行@AfterReturning()的增強(qiáng)內(nèi)容,再執(zhí)行@After()的增強(qiáng)內(nèi)容,優(yōu)先級(jí)略高
必須方法正常執(zhí)行結(jié)束以后,有返回值,才會(huì)觸發(fā)AfterReturning
如果方法都被異常中斷了,沒(méi)有返回值,那么返回值結(jié)果也就沒(méi)有意義了,所以觸發(fā)的條件就是方法正常結(jié)束
// 返回@AfterReturning() 在被代理的目標(biāo)方法成功結(jié)束后執(zhí)行,可以獲取到目標(biāo)方法的執(zhí)行結(jié)果
// 注意,returning的返回值是增強(qiáng)方法結(jié)果的返回值,對(duì)應(yīng)的屬性名字要和傳入的參數(shù)名字保持一致
//此處如果多個(gè)屬性的話(huà),value就要標(biāo)注了
@AfterReturning(value = "execution(* com.cc.annotationAop.CalculatorImpl.*(..))",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",結(jié)果:"+result);
}
執(zhí)行測(cè)試
@AfterThrowing()
在被代理的目標(biāo)方法異常結(jié)束后執(zhí)行,可以獲取異常信息
// 異常@AfterThrowing()
// 目標(biāo)方法執(zhí)行,在被代理的目標(biāo)方法拋出異常后執(zhí)行,可以獲取到目標(biāo)方法拋出的異常信息
// 注意:注解里throwing屬性的名字,要與傳入?yún)?shù)的名稱(chēng)保持一致
@AfterThrowing(value = "execution(* com.cc.annotationAop.CalculatorImpl.*(..))",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->異常通知,方法名:" + methodName + ",結(jié)果:" + ex);
}
在對(duì)應(yīng)的類(lèi)中加入手動(dòng)異常
執(zhí)行測(cè)試
@Around()
(刪掉剛剛手動(dòng)異常的內(nèi)容)
環(huán)繞通知就是在之前增強(qiáng)的基礎(chǔ)上,再包一層,其他增強(qiáng)也會(huì)執(zhí)行
注意,這里如果想執(zhí)行方法,就不能用JoinPoint了,JoinPoint只能獲取方法信息,無(wú)法促使方法執(zhí)行,這里改用ProceedingJoinPoint對(duì)象,才可以執(zhí)行方法
環(huán)繞通知也有和AfterReturning一樣的返回值,可以操作返回值
// 環(huán)繞@Around()
// 在方法的執(zhí)行前后都會(huì)執(zhí)行,只有execution執(zhí)行點(diǎn)一個(gè)參數(shù)
// 注意,這里如果想執(zhí)行方法,就不能用JoinPoint了,JoinPoint只能獲取方法信息,無(wú)法促使方法執(zhí)行
// 這里改用ProceedingJoinPoint對(duì)象,可以執(zhí)行方法
// 環(huán)繞通知也可以有AfterReturning的返回值,可以操作返回值
@Around("execution(* com.cc.annotationAop.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
try {
System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法執(zhí)行之前");
//目標(biāo)對(duì)象(連接點(diǎn))方法的執(zhí)行
result = proceedingJoinPoint.proceed();
System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法返回值之后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法出現(xiàn)異常時(shí)");
} finally {
System.out.println("環(huán)繞通知-->目標(biāo)對(duì)象方法執(zhí)行完畢");
}
return result;
}
執(zhí)行測(cè)試
通知類(lèi)型總結(jié)
- 前置通知:在被代理的目標(biāo)方法前執(zhí)行
- 返回通知:在被代理的目標(biāo)方法成功結(jié)束后執(zhí)行(正常結(jié)束)
- 異常通知:在被代理的目標(biāo)方法異常結(jié)束后執(zhí)行(拋出異常)
- 后置通知:在被代理的目標(biāo)方法最終結(jié)束后執(zhí)行(最后執(zhí)行)
- 環(huán)繞通知:使用try…catch…finally結(jié)構(gòu)圍繞整個(gè)被代理的目標(biāo)方法,包括上面四種通知對(duì)應(yīng)的所有位置
各種通知的執(zhí)行順序:
- Spring版本5.3.x以前:
- 前置通知
- 目標(biāo)操作
- 后置通知
- 返回通知或異常通知
- Spring版本5.3.x以后:
- 前置通知
- 目標(biāo)操作
- 返回通知或異常通知
- 后置通知
重(chong)用切入點(diǎn)表達(dá)式
表達(dá)式可以復(fù)用,要不然每個(gè)都寫(xiě)一遍太麻煩,而且不好維護(hù)
注意:如果不是在同一個(gè)切面(切面類(lèi))使用的話(huà),比如:A切面類(lèi)里定義好的重用表達(dá)式,在B切面類(lèi)使用,就要在路徑前加上包名來(lái)區(qū)分
在重用表達(dá)式定義的類(lèi)里面使用,就直接用方法名即可
如果不在重用表達(dá)式定義的類(lèi)里面使用,需要包名+類(lèi)名+方法名
①重用切入點(diǎn)表達(dá)式聲明
@Pointcut("execution(* com.cc.annotationAop.CalculatorImpl.add(..))")
public void pointCut(){}
②在同一個(gè)切面(類(lèi))中使用
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
③在不同切面(類(lèi))中使用
//@Before("引用全路徑.類(lèi)名.表達(dá)式定義()")
@Before("com.cc.annotationAop.LogAspect.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",參數(shù):"+args);
}
切面優(yōu)先級(jí)
相同目標(biāo)方法上同時(shí)存在多個(gè)切面時(shí),切面的優(yōu)先級(jí)控制切面的內(nèi)外嵌套順序
- 優(yōu)先級(jí)高的切面:外面
- 優(yōu)先級(jí)低的切面:里面
使用@Order注解可以控制切面的優(yōu)先級(jí):
- @Order(較小的數(shù)):優(yōu)先級(jí)高
- @Order(較大的數(shù)):優(yōu)先級(jí)低
基于XML的AOP
前期準(zhǔn)備參考注解的AOP
具體實(shí)現(xiàn)的形式在bean.xml里面進(jìn)行文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-570636.html
<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>
<aop:config>
<!--配置切面類(lèi)-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>
現(xiàn)在項(xiàng)目上用的不多,就不詳細(xì)展開(kāi)了文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-570636.html
到了這里,關(guān)于Spring IOC & AOP的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!