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知識總結,其餘部分將在下一篇文章進行總結。
注:以上文章僅是個人學習過程總結,若有不當之處,望不吝賜教