1. 程式人生 > >Spring_IOC與AOP

Spring_IOC與AOP

上篇博文中模擬了IOC的底層實現,接下來對spring的IOC和AOP部分進行總結。

1、首先看看spring框架是怎麼替我們建立物件的,spring_ioc入門。

匯入相關的jar包,然後copy下面的程式,放入指定的位置。就可以體驗到spring的ioc服務。

User.java:

public class User {

	public void test() {
		System.out.println("test...");
	}
	
}

log4j.properties:

#控制父類日誌記錄器的日誌級別為info,預設所有模組下只輸出info級別以上的日誌
log4j.rootLogger=info,console
 
############# 日誌輸出到控制檯 ############# 
#日誌輸出到控制檯使用的api類  
log4j.appender.console=org.apache.log4j.ConsoleAppender  
#指定當前輸出源的日誌級別,有了前面的配置,就不需要配置該項了
#log4j.appender.console.Threshold = info
#指定日誌輸出的格式:靈活的格式
log4j.appender.console.layout=org.apache.log4j.PatternLayout  
#具體格式的內容
log4j.appender.console.layout.ConversionPattern=%d %-2p [%c.%M()] - %m%n

ApplicationContext.xml:(圖中沒有展開,該檔案可以放在src目錄下,也可以放在其他的路徑下,同樣名字可以是其他,如bean.xml,而筆者為了方便管理,把該檔案放入springConfig資料夾中)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<bean id="user" class="h.l.pojo.User"></bean>
	
</beans>

UserTest.java: 

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

class UserTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext("springConfig/ApplicationContext.xml");
	}

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

}

執行結果: 

2018-11-25 10:28:18,864 INFO [org.springframework.context.support.ClassPathXmlApplicationContext.prepareRefresh()] - Refreshing org[email protected]29b5cd00: startup date [Sun Nov 25 10:28:18 CST 2018]; root of context hierarchy
2018-11-25 10:28:18,943 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions()] - Loading XML bean definitions from class path resource [springConfig/ApplicationContext.xml]
[email protected]
test...

可以發現,User物件的建立不用我們自己動手,spring框架會自動線dom4j解析ApplicationContext.xml檔案,然後根據id找到對應的bean,得到該bean中的class屬性,通過反射,創建出指定物件,好處是什麼呢?class屬性所指定的類路徑,無論該類放哪個路徑或者資料夾下,都可以通過更改class屬性中的值,讓框架準確幫你建立物件,解耦!(可以參考Spring_IOC底層實現模擬

2、入門了spring_ioc後,下面總結spring中bean例項化的三種方式。

(1)使用類的無參構造建立:(下面的寫法就是使用的是pojo類的無參方法實現bean例項化的,如果想測試的話,pojo類中帶有參構造方法,不帶無參構造方法即可發現,此時物件是無法建立的)

<bean id="user" class="h.l.pojo.User"></bean>

無法建立時的報錯資訊:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [springConfig/ApplicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [h.l.pojo.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: h.l.pojo.User.<init>()

 此時解決方式,就是在pojo類中新增無參構造方法就可以了。

(2)使用靜態工廠進行建立:

配置如下:

<bean id="userFactory" class="h.l.pojo.UserFactory" factory-method="getBean"></bean>

靜態工廠如下: 

public class UserFactory {

	public static User getBean() {
		return new User();
	}
}

 測試類如下:

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

(3)使用例項工廠進行建立:

配置如下:

<bean id="userFactory" class="h.l.pojo.UserFactory"></bean>
<bean id="user" factory-bean="userFactory" factory-method="getBean"></bean>

例項工廠如下:

public class UserFactory {

	public User getBean() {
		return new User();
	}
}

測試如下:

	@Test
	void test() {
		User user = (User) context.getBean("user");
		System.out.println(user);
		user.test();
	}

ps:實際上開發過程中,我們更多的是使用第一種方法,即使用類的無參構造建立物件,配置bean也來得更加方便。

3、bean標籤中的屬性:

id屬性:bean的標識,根據id得到配置物件。注意的是id值不能包含特殊屬性。

class屬性:建立物件所在類的全路徑。

name屬性:功能類似id,在name屬性中可以包含特殊字元。

scope屬性:

  • singleton:預設值,單例的。
  • prototype:多例的。
  • request:web專案中,Spring建立一個Bean物件,將物件存入request域中。
  • session:web專案中,Spring建立一個Bean物件,將物件存入session域中。
  • globalSession:web專案中,Spring建立一個Bean物件,將物件存入globalSession中。

至於單例和多例的測試,僅僅是通過IOC容器建立兩個物件,比較一下是否相等即可,單例肯定為true,多例為false。

<bean id="user" class="h.l.pojo.User" scope="prototype或者singleton,預設單例singleton"></bean>

4、屬性的注入:

屬性的注入方式有三種:

使用set方法注入、有參構造注入、使用介面注入

 在Spring框架中,支援前兩種方式

(1)有參構造注入

<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
	<constructor-arg name="username" value="hello"></constructor-arg>
</bean>
public class PropertyDemo1 {

	private String username;

	public PropertyDemo1(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "PropertyDemo1 [username=" + username + "]";
	}
	
}

(2)set方法的注入

<bean id="propertyDemo1" class="h.l.pojo2.PropertyDemo1">
	<property name="username" value="world"></property>
</bean>
public class PropertyDemo1 {

	private String username;

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "PropertyDemo1 [username=" + username + "]";
	}
}

(3)使用set方法注入物件型別的屬性:

<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
	<property name="userDao" ref="userDao"></property>
</bean>
public class UserService {

	private UserDao userDao;

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}
public class UserDao {

	public void test() {
		System.out.println("Dao...");
	}
}
class UserServiceTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext(
				"springConfig/ApplicationContext.xml");
	}

	@Test
	void test() {
		UserService userService=(UserService)context.getBean("userService");
		userService.test();
	}

}

當然,也可以使用帶參建構函式注入:

<bean id="userDao" class="h.l.pojo3.UserDao"></bean>
<bean id="userService" class="h.l.pojo3.UserService">
	<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
public class UserService {

	private UserDao userDao;

	public UserService(UserDao userDao) {
		this.userDao = userDao;
	}

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

對應的地方作相應的修改即可,但是實際開發中使用set的方法注入會更多些,因為pojo類中有set方法,能有更多的靈活性。

(4)p名稱空間注入:

public class Person {

	private String username;

	public void setUsername(String username) {
		this.username = username;
	}

	@Override
	public String toString() {
		return "Person [username=" + username + "]";
	}
	
}

通過上面的方式也能夠實現屬性的注入。如果屬性中有物件型別屬性,p名稱空間注入即可按如下方式進行:

<bean id="user" class="h.l.pojo.User" p:username="I am ok"></bean>
<bean id="person" class="h.l.pojo4.Person" p:username="Is-Me-HL" p:user-ref="user"></bean>

除了p名稱空間,還有c名稱空間,用法類似,只是語法有些差異,請自行百度。

(5)複雜型別注入:

public class Person {
	private String[] arrs;
	private List<String>list;
	private Map<String,String>map;
	private Properties properties;

	public void setArrs(String[] arrs) {
		this.arrs = arrs;
	}

	public void setList(List<String> list) {
		this.list = list;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	public void show() {
		System.out.println("arrs:"+"=========="+arrs);
		System.out.println("list:"+"=========="+list);
		System.out.println("map:"+"=========="+map);
		System.out.println("properties:"+"=========="+properties);
	}
	
}
<bean id="person" class="h.l.pojo4.Person">
		<property name="arrs">
			<list>
				<value>1</value>
				<value>2</value>
				<value>3</value>
			</list>
		</property>
		<property name="list">
			<list>
				<value>11</value>
				<value>22</value>
				<value>33</value>
			</list>
		</property>
		<property name="map">
			<map>
				<entry key="1" value="11"></entry>
				<entry key="2" value="22"></entry>
				<entry key="3" value="33"></entry>
			</map>
		</property>
		<property name="properties">
			<props>
				<prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
				<prop key="jdbc.url">jdbc:mysql://localhost:3306/XXX</prop>
				<prop key="jdbc.user">root</prop>
				<prop key="jdbc.password">root</prop>
			</props>
		</property>
	</bean>

(6)IOC和DI的區別:

IOC:控制反轉,把物件建立交給spring進行配置。

DI:依賴注入,向類裡面的屬性中設定值。

關係:依賴注入不能單獨存在,需要在ioc基礎之上完成。

5、spring註解開發:在核心jar包的基礎上新增spring-aop-4.3.14.RELEASE.jar。

(1)建立物件的四個註解:

import org.springframework.stereotype.Component;

@Component(value="user")
public class User {

	public void show() {
		System.out.println("User...");
	}
}

其餘三個註解分別是@Controller(web層)、@Service(業務層)、@Repository(持久層),實際上這四個註解的功能都是一樣的,目前來說。

(2)使用註解注入屬性:

前提:配置註解掃描:

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

	<!-- 開啟註解掃描 -->
	<context:component-scan base-package="h.l.pojo"></context:component-scan>
</beans>

第一個註解:@Autowired

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {

	@Autowired // 需要注意的是Autowired是根據類名找到對應類實現自動注入的,如找到下面的UserDao
	private UserDao userDao;

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

第二個註解:@Resource(name="")

import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component(value = "userService")
public class UserService {

	@Resource(name="userDao")
	private UserDao userDao;

	public void test() {
		System.out.println("service...");
		userDao.test();
	}
}

6、AOP:面向切面程式設計,擴充套件功能不通過修改原始碼實現。採用橫向抽取機制,取代傳統縱向繼承體系重複性程式碼。

關於動態代理:請移步:JDK動態代理

AOP的常見操作術語:

  • 連線點:類裡面哪些方法可以被增強,這些方法稱為增強點。
  • 切入點:在類裡面可以有很多的方法被增強,但實際上我們很有可能挑其中的幾個方法增強,因此,實際增強的方法稱為切入點。
  • 通知/增強:增強的邏輯,稱為增強,比如擴充套件日誌功能,這個日誌功能就稱為增強,包括前置通知(在方法前執行),後置通知(在方法之後執行),異常通知(方法出現異常),最終通知(在後置之後執行),環繞通知(在方法之前和之後執行)。
  • 切面:把增強應用到具體方法上,過程稱為切面(即把增強用到切入點過程)。

關於AOP的demo如下:

先匯入相關jar包

Book.java:

package h.l.pojo6;

public class Book {

	public void add() {
		System.out.println("add....");
	}
}

MyBook.java:

package h.l.pojo6;

public class MyBook {

	public void before1() {
		System.out.println("前置增強...");
	}
}

相關配置檔案:

<?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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

	<!-- 配置物件 -->
	<bean id="book" class="h.l.pojo6.Book"></bean>
	<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
	<!-- 配置aop操作 -->
	<aop:config>
		<!-- 配置切入點 -->
		<aop:pointcut expression="execution(* h.l.pojo6.Book.*(..))" id="pointcut1"/>
		<!-- 配置切面 -->
		<aop:aspect ref="myBook">
			<aop:before method="before1" pointcut-ref="pointcut1"/>
		</aop:aspect>
	</aop:config>
</beans>

BookTest.java測試類:

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

class BookTest {

	ApplicationContext context;
	@BeforeEach
	void setUp() throws Exception {
		context = new ClassPathXmlApplicationContext(
				"springConfig/ApplicationContext.xml");
	}
	@Test
	void test() {
		Book book = (Book) context.getBean("book");
		book.add();
	}

}

測試結果:

前置增強...
add....

上述就是aop(面向切面程式設計)的一個例項。關於Aspects框架使用表示式配置切入點:下面再進行些總結:

模板:execution(<訪問修飾符>?<返回型別><方法名>(<引數>)<異常>)

常用表示式:
    
    (1)execution(* h.l.pojo6.Book.add(..)):表示切入點是h.l.pojo6包下的Book類中的add方法

    (2)execution(* h.l.pojo6.Book.*(..)):表示切入點是h.l.pojo6包下的Book類中的所有方法

    (3)execution(* *.*(..)):表示切入點是所有包下的所有類中的所有方法

    (4)execution(* save*(..)):表示切入點是save開頭的所有方法

上面是AOP的xml方式開發,AOP註解開發方式如下:

配置檔案如下:

<?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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

	<!-- 配置物件 -->
	<bean id="book" class="h.l.pojo6.Book"></bean>
	<bean id="myBook" class="h.l.pojo6.MyBook"></bean>
	
	<!-- 開啟aop操作 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

 Book.java:

package h.l.pojo6;

public class Book {

	public void add() {
		System.out.println("add....");
	}
}

MyBook.java:

package h.l.pojo6;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyBook {

	@Before(value="execution(* h.l.pojo6.Book.*(..))")
	public void before1() {
		System.out.println("before1...");
	}
}

測試結果:

before1...
add....

以上僅是部分Spring知識總結,其餘部分將在下一篇文章進行總結。


注:以上文章僅是個人學習過程總結,若有不當之處,望不吝賜教