-
控制反轉 IOC (Inversion Of Control)
-
面向切面編程 AOP (Aspect-Oriented Programming)
控制反轉指的是應用中的對象依賴關系不在由自己維護,而交給Spring由它的容器幫我們維護,因此也叫做依賴注入DI (Dependency Injection)。
一.使用BeanFactory解耦
?這里使用BeanFactory來降低我們熟知的MVC編程模式中service層與dao層之間的耦合關系。
解耦前(service層關鍵代碼)
// Service層中需要Dao層的實例來與數據庫交互完成業務邏輯 public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoImpl();public void registerUser(User user) {userDao.addUser(user);}
解耦后(service層關鍵代碼)
public class UserServiceImpl implements UserService {private UserDao userDao;// 提供set方法public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void registerUser(User user) {userDao.addUser(user);}
public class BeanFactory {private static Map<String, Object> beans = new HashMap<String, Object>();//靜態代碼塊加載資源static {try {ResourceBundle bundle = ResourceBundle.getBundle("objects");Enumeration<String> keys = bundle.getKeys();while (keys.hasMoreElements()) {String key = keys.nextElement();String className = bundle.getString(key);Object clazz = Class.forName(className).newInstance();beans.put(key, clazz);}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("加載類配置文件出錯!");}}//對外提供獲取bean的方法public static <T> T getBean(String className, Class<T> T) {Object o = beans.get(className);if (o != null)return (T) o;else throw new RuntimeException("找不到類:" + className);} }
userDao=com.dintalk.dao.impl.UserDaoImpl
UserServiceImpl userServiceImpl = new UserServiceImpl(); UserDao userDao = BeanFactory.getBean("userDao",UserDao.class); userServiceImpl.setUserDao(userDao);
? 解耦前,service層中直接new出了其所依賴的實例對象userDaoImpl。而通過工廠解耦后,service中只聲明了UserDao的接口引用,并提供了set方法,我們在使用servcie時,可以通過set方法傳入從工廠中獲得的實現了UserDao接口的任一實現類的實例。而實現類的配置又暴露在了配置文件當中,解耦的同時也增加了程序的動態性。
BeanFactory原理:
? 這里使用的是靜態工廠,在工廠類中定義了一個Map用于存放工廠管理的Bean實例,靜態代碼塊隨類的加載執行一次,讀取配置文件中的key-value信息。通過循環和反射,將配置文件中的key仍作為Map的key;將配置文件中key對應的類全限定名通過反射構造實例后作為其對應的value存于Map中。達到這樣的效果:BeanFactory類加載完畢后,它便管理了一個Map集合,Map集合的key就是配置文件中的key,Map中的value就是配置文件中value對應的類的實例。如此,對外提供一個getBean方法,通過key返回其對應的實例,這便實現了通過BeanFactory來管理實例對象。
二.Spring使用步驟
以使用xml配置文件的方式示例:
1.導入坐標或jar包
? 如果使用Maven構建,我們可以導入spring-context。因為上下文模塊依賴其他模塊,所有其他模塊也會自動導入。
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version> </dependency>
? 在resources下創建spring的主配置文件,添加頭信息時需格外注意。最好保存模板或到官網復制粘貼,稍有差錯將導致異常。
<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean>...</bean><!-- 1.主配置文件中導入模塊配置文件 --><import resource="user.xml"/> </beans>
? 可以按分模塊在配置文件中裝配Bean,再在主配置文件中進行導入。但要注意,如果出現id相同的情況,后加載的配置會覆蓋掉前面的配置!
加載配置文件獲取Bean
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = appContext.getBean("userDao", UserDao.class);
ApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml","user.xml"});
? Spring的一大特點是最小侵入性編程,它不會強迫我們去實現它的接口或實現類,POJO依舊是那個POJO。我們只是將依賴交由Spring管理,因此,IOC配置也就是Bean的裝配便是很大一部分工作。Spring為我們提供了三種裝配Bean的方式:
-
基于xml配置文件 ★★★★
-
基于注解(往往配合xml配置文件使用) ★★★★
-
基于java類的配置(會用到注解)
其實,無論使用哪一種方式,我們的目的只有一個,那就是我們要將程序中的依賴關系描述清楚,將Bean裝配好交由Spring的容器!
1.基于xml文件的裝配
bean的實例化
<!-- 0.通過默認構造方法生產bean --> <bean id="userDao" class="cn.dintalk.dao.impl.UserDaoImpl"></bean> <!-- 1.通過實例工廠生產bean --> <bean id="myBeanFactory" class="cn.dintalk.factory.BeanFactory1"/> <bean id="myBean" factory-bean="myBeanFactory" factory-method="getBean"> <!-- 2.通過靜態工廠生產bean --> <bean id="userDao1" class="cn.dintalk.factory.BeanFactory" factory-method="getBean"/><!-- bean的存活范圍及生命周期方法 --> <bean id="userDao2" scope="singleton" init-method="m1" destroy-method="m2" class="cn.dintalk.dao.impl.UserDaoImpl"></bean>
-
singleton
-
prototype
-
request
-
session
-
globalsession
生命周期方法在單例模式下才有意義,想想是為什么呢?
數據的注入
<!-- 3.數據的注入 --> <!-- 3.1構造方法注入 --> <bean id="user" class="cn.dintalk.domain.User"><constructor-arg index="0" value="王舞"/><constructor-arg index="1" value="wangwu"/> </bean> <!-- 3.2setter屬性注入 --> <bean id="user1" class="cn.dintalk.domain.User"><property name="name" value="趙思"/><property name="password" value="zhaosi"/> </bean> <!-- 3.3p命名空間注入 --> <bean id="user2" class="cn.dintalk.domain.User" p:name="張珊" p:password="zhangshan"/><!-- 4.常用數據類型的注入 --> <bean id="user3" class="cn.dintalk.domain.User"><!-- 4.0數組的注入 --><property name="myArr"><array><value>str1</value><value>str2</value></array></property><!-- 4.1List的注入 --><property name="myList"><list><value>str1</value><value>str2</value></list></property><!-- 4.2Set的注入 --><property name="mySet"><set><value>str1</value><value>str2</value></set></property><!-- 4.3Map的注入--><property name="myMap"><map><entry key="s1" value="str1"/><entry key="s2" value="str2"/></map></property><!-- 4.4Properties的注入 --><property name="myPro"><props><prop key="s1">str1</prop><prop key="s2">str2</prop></props></property> </bean><!-- 5.依賴的注入--> <bean id="userService" class="cn.dintalk.service.impl.UserServiceImpl"><property name="userDao" ref="userDao"></property> </bean>
? 使用注解來裝配bean可以簡化我們的步驟,提高效率。可以替代xml文件的裝配方式,但是一般是和xml文件的方式打雙打。使用第三方工具包時使用xml的方式要方便一些,章節末我們通過DButil的示例。由于xml的方式比較好理解,而注解又是xml文件方式的簡化,因此我們對比著來學習。
bean的實例化
@Component("accountService") public class AccountServiceImpl implements AccountService{ /* - @Controller 用在表現層 - @Service 用在業務層 - @Respository 用在持久層 這三個注解的作用和@Component完全一樣,就是更加語義化(分層) */ // bean的存活范圍和生命周期 @Component("accountService") @Scope("singleton") public class AccountServiceImpl implements AccountService { // 初始化方法 @PostConstruct private void init(){ // 銷毀方法 @PreDestroy private void destroy(){
@Autowired @Qualifier("accountDao") private AccountDao accountDao; /* - @Autowired 自動裝配,查找Spring容器按照類型自動賦予對象。★★★ - @Qualifier("accountDao") 與@Autowired配合,指定具體名稱的實現類對象。★★★ - @Resource(name="accountDao") Spring對JSR-250中定義的注解的支持。 */ // - @Value 注入簡單類型的數據 @Value("16") // 值都是字符串類型,spring會自行解析 private Integer age; @Value("張珊") private String name;
基于注解的配置加載(獲取容器對象)
方式一:依舊使用ClassPathXmlApplicationContext(需配置)★★★
<!-- 配置文件中,指定掃描注解的包 --> <context:component-scan base-package="cn.dintalk"/>
方式二:使用AnnotationConfigApplicationContext加載配置,獲取bean
ApplicationContext context = new AnnotationConfigApplicationContext(MyBean.class);MyBean myBean = context.getBean("myBean", MyBean.class);
3.基于java類的裝配
? 基于注解的通過組件掃描和自動裝配實現Spring的自動化配置是更為推薦的方式,但有時候自動化配置的方案行不通,因此需要明確配置Spring。同樣比如,我們想將第三方庫中的組件裝配到我們的應用中,這種情況下,沒有辦法在它的類上添加@Component和@Autowired注解的。因此我們必須采用顯示裝配的方式,顯示裝配有兩種可選方案:上述的xml裝配方式和我們即將闡述的Java類的裝配方式。還是那一句話,無論是哪一種方式,目的只有一個,那就是將一些必要的信息告知我們的程序。
bean的實例化
@Configuration //Spring配置類,帶有Configuratio注解就是配置類.加不加無所謂 @ComponentScan("cn.dintalk") //<context:component-scan base-package="cn.dintalk"/> @Import({JdbcConfig.class,MailConfig.class}) //聚合多個配置類<import resource=""/> public class SpringConfig {/* @Configuration可不加:因為我們在加載配置時還會指定到該類- ApplicationContext applicationContext =new AnnotationConfigApplicationContext(SpringConfig.class); */@PropertySource("jdbc.properties")//導入外部的properties文件 public class JdbcConfig {//讀取properties文件中key對應的value值@Value("${jdbc.driverClassName}")private String driverClassName;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;//創建數據源//告知spring容器,將該方法的返回值對象,以“druidDataSource”存放到容器中@Bean("druidDataSource")public DataSource createDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}//創建QueryRunner對象,并交給spring容器管理@Bean("queryRunner")//@Qualifier("druidDataSource") DataSource dataSource://數據源對象對應spring容器中一個名字叫做druidDataSource的public QueryRunner createQueryRunner(@Qualifier("druidDataSource") DataSource dataSource){QueryRunner queryRunner = new QueryRunner(dataSource);return queryRunner;}
參考同基于注解的裝配
基于java類的配置加載
//AnnotationConfigApplicationContext構造參數:指定配置類的類型 //可以指定多個 ApplicationContext applicationContext =new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = applicationContext.getBean("userService", UserService.class);
? DBUtils是Apache提供的對JDBC封裝了的公共組件。
1.普通的使用
第一步:導入jar包或Maven坐標
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.7</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.14</version></dependency>
public class DruidUtil {private static DataSource dataSource;static {InputStream inputStream = null;try {inputStream = DruidUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");Properties properties = new Properties();properties.load(inputStream);dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();throw new RuntimeException("加載數據庫配置文件失敗!");}finally {if (inputStream != null){try {inputStream.close();} catch (IOException e) {e.printStackTrace();throw new RuntimeException("關閉文件資源失敗!");}}}}public static DataSource getDataSource(){ // 獲取數據源return dataSource;}public Connection getConnection(){ // 獲取連接try {return dataSource.getConnection();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e);}} }
private QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource()); // 增刪改: 使用update(sql,params); //update方法內部:先從給定的數據源獲取一個連接,在方法即將執行完畢后,將連接歸還(到連接池) public void addAccount(Account account) {if (account == null)throw new RuntimeException("參數錯誤");try {queryRunner.update("insert into accounts values(null,?,?)",account.getAccountName(), account.getBalance());} catch (SQLException e) {throw new RuntimeException(e);}} //查詢:使用 query(sql,Handler,params)public Account findById(Integer aid) {if (aid == null)throw new RuntimeException("參數異常");try {return queryRunner.query("select * from accounts where aid = ?", newBeanHandler<Account>(Account.class), aid);} catch (SQLException e) {throw new RuntimeException(e);}}
第一步:導入Spring的jar包或Maven坐標
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version> </dependency>
<!-- 1.配置druid數據源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///spring02"/><property name="username" value="sh"/><property name="password" value="sh123"/> </bean> <!-- 2.配置QueryRunner --> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"><constructor-arg index="0" ref="druidDataSource"/> </bean> <!-- 3.配置AccountDao --> <bean id="accountDao" class="cn.dintalk.dao.impl.AccountDaoImpl"><property name="queryRunner" ref="queryRunner"/> </bean> <!-- 4.配置AccountService --> <bean id="accountService" class="cn.dintalk.service.impl.AccountServiceImpl"><property name="accountDao" ref="accountDao"/> </bean>
//DAO層 提供set方法以注入 private QueryRunner queryRunner; public void setQueryRunner(QueryRunner queryRunner) {this.queryRunner = queryRunner; }//service層 提供set方法以注入 private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao; }//CRUD操作同上
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
由于使用到第三方包,所以無法全部使用注解,需要和xml的方式結合。
第一步:配置applicationContext文件
<!-- 1.基于注解,聲明掃描注解的包 --> <context:component-scan base-package="cn.dintalk"/> <!-- 2.配置druid數據源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///spring02"/><property name="username" value="sh"/><property name="password" value="sh123"/> </bean> <!-- 3.配置QueryRunner --> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"><constructor-arg index="0" ref="druidDataSource"/> </bean>
// DAO層中 @Repository("accountDao") public class AccountDaoImpl implements AccountDao {@Autowiredprivate QueryRunner queryRunner;// Service層中 @Service("accountService") public class AccountServiceImpl implements AccountService {@Autowired@Qualifier("accountDao")private AccountDao accountDao;
Tips: 配置加載方式
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
第一步:創建配置類
@PropertySource("jdbc.properties")//導入外部的properties文件 @ComponentScan("cn.dintalk") // 添加注解掃描包 public class SpringConfig {//讀取properties文件中key對應的value值@Value("${jdbc.driverClassName}")private String driverClassName;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;//創建數據源@Bean("druidDataSource")public DataSource createDataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}//創建QueryRunner對象,并交給spring容器管理@Bean("queryRunner")//@Qualifier("druidDataSource") DataSource dataSource: 數據源對象對應spring容器中一個名字叫做druidDataSource的public QueryRunner createQueryRunner(@Qualifier("druidDataSource") DataSource dataSource){QueryRunner queryRunner = new QueryRunner(dataSource);return queryRunner;}
// DAO層中 @Repository("accountDao") public class AccountDaoImpl implements AccountDao {@Autowiredprivate QueryRunner queryRunner;// Service層中 @Service("accountService") public class AccountServiceImpl implements AccountService {@Autowired@Qualifier("accountDao")private AccountDao accountDao;
Tips: 配置加載方式
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
applicationContext.xml文件頭約束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"> </beans>
?
?
關注微信公眾號,隨時隨地學習