什么是Spring
是一個為簡化企業(yè)級應(yīng)用開發(fā)的開源框架,作為IOC(DI)泄私、AOP容器咖气。
特性
輕量級:占用空間小挖滤;非侵入性(是否被框架綁架):通過配置的方法崩溪,使代碼中不引用框架的類,刪除jar包后不會報錯斩松;
控制反轉(zhuǎn)伶唯、依賴注入:將設(shè)計好的對象交給容器,該對象的屬性也由容器進(jìn)行注入惧盹;
面向切面編程:
容器:管理Java對象(所有的類)的生命周期乳幸;
框架:使用簡單的組件配置組合成一個復(fù)雜的應(yīng)用瞪讼;
一站式:在IOC和AOP基礎(chǔ)上可以整合各種企業(yè)的開源框架和第三方類庫,Spring本身也提供了視圖層的SpringMVC和持久層的Spring JDBC粹断;
相關(guān)術(shù)語
Ioc(控制反轉(zhuǎn) Inversion of Control)
Di(依賴注入 Dependency Injection)
Aop(面向切面編程 Aspect Oriented Programming)
以上都是一種方法論符欠,不是Spring專有
Dao(數(shù)據(jù)訪問對象 Data Access Object)
Orm(對象關(guān)系映射 (Object Relational Mapping)
Service(服務(wù)、業(yè)務(wù)邏輯 等于 Business)
MVC(模型-視圖-控制器 Model View Controller)
IOC—控制反轉(zhuǎn)瓶埋、DI—依賴注入
把創(chuàng)建實例的控制權(quán)交給框架希柿,由框架創(chuàng)建實例(控制反轉(zhuǎn))并把實例分發(fā)給調(diào)用的程序(依賴注入)。由Spring容器來控制對象的生命周期养筒,默認(rèn)是單例模式的曾撤,目的是層和層之間的解耦。
如何實現(xiàn)的解耦
依賴:如果在一個類中想要調(diào)用另一個對象的非靜態(tài)方法晕粪,必須先創(chuàng)建這個對象挤悉,不創(chuàng)建時編譯報錯,此時就稱這個對象是這個類的依賴巫湘。
某一個類中的依賴在正常編譯的情況下装悲,這個類才有可能正常編譯。由此尚氛,便產(chǎn)生了耦合诀诊。
IOC為此而生,不管所依賴的對象是否存在怠褐,都能夠編譯成功:
通過實現(xiàn)接口的方式(面向接口編程)畏梆,類中不再聲明實現(xiàn)類您宪,而是聲明該類的接口奈懒。
IOC示例代碼
- 新建Java項目
- 導(dǎo)入相關(guān)jar包:Spring框架jar包及commons-logging.jar(https://mvnrepository.com/下載)
- 創(chuàng)建接口
package t;
public interface HelloWorld
{
public void sayHello();
}
- 創(chuàng)建實現(xiàn)類
package t;
public class HelloWorldImpl implements HelloWorld
{
@Override
public void sayHello()
{
System.out.println("helloworld");
}
}
- 編寫配置文件
<?xml version="1.0" encoding="utf-8"?>
<!-- 上面為xml文件的頭,指明xml的版本號和當(dāng)前文件的編碼宪巨,必須在第一行 -->
<!-- 聲明spring配置文件的跟標(biāo)簽beans -->
<!-- 通過xmlns這個命名空間屬性聲明當(dāng)前xml文檔需要用到的標(biāo)簽所在的包 -->
<!-- xmlns:xsi聲明xsi屬性前綴所在的命名空間 -->
<!-- xsi:schemaLocation聲明當(dāng)前xml文件里用標(biāo)簽的合法性校驗 -->
<!-- xsi:schemaLocation屬性的值包含“命名空間和校驗文件” -->
<!-- 擴展名是xsd的文件是xml的語法校驗文件 -->
<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-4.0.xsd">
<!-- 用bean標(biāo)簽聲明一個HelloWorldImpl類的實例磷杏, 并且把實例的名字定義為helloWorld-->
<!-- 其中需要注意,class屬性里一定是實現(xiàn)類捏卓;id屬性在整個配置文件里必須唯一-->
<bean id="helloWorld" class="t.HelloWorldImpl"/>
</beans>
- 編寫測試類
package t;
import org.springframework.context.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test
{
public static void main(String[] args)
{
//通過src目錄(即classpath)下的t目錄下的IOC.xml配置文件极祸,創(chuàng)建spring容器
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext("t/IOC.xml");
//去容器里取到id=helloWorldBean的實例,并且把這個實例聲明為接口的類型
HelloWorldImpl bean = (HelloWorldImpl)applicationContext.getBean("helloWorld");
bean.sayHello();
applicationContext .close();//關(guān)閉容器
}
}
DI(依賴注入)
把有依賴關(guān)系的類的實例注入為指定類的屬性值怠晴。
需要注入的類和被注入的類都需要交個Spring容器來管理遥金。
在Controller層中聲明service層的接口,注入service的實現(xiàn)類蒜田,在Service層中注入Dao層的接口稿械,注入dao的實現(xiàn)類。
構(gòu)造器注入
- 有一個屬性
- 屬性要在構(gòu)造方法中被賦值
- 創(chuàng)建需要有屬性被注入的類
public class TextPrinter {
private Formater formater;//該屬性被注入
private int size;
public TextPrinter(Formater formater,int size){
this.formater=formater;
this.size=size;
}
public void print(String info){
System.out.println("Set size="+this.size);
this.formater.execute(info);
}
}
- 注入屬性的類
public class Formater {
public void execute(String info) {
System.out.println("Formater = "+info);
}
}
- 配置文件里冲粤,配置注入標(biāo)簽
<!-- 聲明id為f的bean實例 -->
<bean id="f" class="s1.Formater"/>
<!-- 聲明id為tp的bean實例 -->
<bean id="tp" class="s1.TextPrinter">
<!-- 通過constructor-arg調(diào)用tp實例的構(gòu)造方法進(jìn)行注入 -->
<!--
index : 標(biāo)明構(gòu)造方法里參數(shù)的下標(biāo)美莫,即為第幾個參數(shù)注入
ref子標(biāo)簽代表注入一個實例(引用)页眯;bean : 指明一個已經(jīng)在配置文件中配置好的bean的id
type : 標(biāo)明這個參數(shù)的類型
value : 直接給這個參數(shù)注入一個具體的值
-->
<constructor-arg index="0">
<ref bean="f"/>
</constructor-arg>
<constructor-arg index="1" type="int" value="5"/>
</bean>
- 寫測試類測試上面的注入結(jié)果
AbstractApplicationContext context = new ClassPathXmlApplicationContext("f.xml");
TextPrinter bean = (TextPrinter) context.getBean("tp");
bean.print("Spring 4");
context.close();
屬性注入
- 有至少一個屬性
- 這個屬性至少有公有set方法
- 創(chuàng)建需要有屬性被注入的類
public class TextPrinter2 {
private Formater formater;//被注入的屬性
private int size;//被注入的屬性
public Formater getFormater() {
return formater;
}
//一定要有這個方法
public void setFormater(Formater formater) {
this.formater = formater;
}
public int getSize() {
return size;
}
//一定要有這個方法
public void setSize(int size) {
this.size = size;
}
public void print(String info){
System.out.println("Set size="+this.size);
this.formater.execute(info);
}
}
- 注入屬性的類 參照上面
- 配置文件里配置屬性注入標(biāo)簽
...
<bean id="tp2" class="s1.TextPrinter2">
<!--
通過property標(biāo)簽進(jìn)行屬性注入
name : 標(biāo)明類里被注入的屬性的變量名
ref子標(biāo)簽同上
value子標(biāo)簽 type屬性同上
-->
<property name="formater">
<ref bean="f"/>
</property>
<property name="size">
<value type="int">5</value>
</property>
</bean>
...
- 寫測試類測試上面的屬性注入結(jié)果 參考上面
集合類型的注入
- 數(shù)組
- list
- set
- map
- Properties
- 創(chuàng)建需要有集合屬性被注入的類
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Department {
private String name;
private String [] empName;//數(shù)組
private List<Employee> empList;//list集合
private Set<Employee> empsets;//set集合
private Map<String,Employee> empMaps;//map集合
private Properties pp;//Properties的使用
public Set<Employee> getEmpsets() {
return empsets;
}
public void setEmpsets(Set<Employee> empsets) {
this.empsets = empsets;
}
public String[] getEmpName() {
return empName;
}
public void setEmpName(String[] empName) {
this.empName = empName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmpList() {
return empList;
}
public void setEmpList(List<Employee> empList) {
this.empList = empList;
}
public Map<String, Employee> getEmpMaps() {
return empMaps;
}
public void setEmpMaps(Map<String, Employee> empMaps) {
this.empMaps = empMaps;
}
public Properties getPp() {
return pp;
}
public void setPp(Properties pp) {
this.pp = pp;
}
}
- 創(chuàng)建集合里元素的實例類
public class Employee {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 在配置文件中配置集合屬性的注入
...
<!-- 聲明被注入的實例emp1 -->
<bean id="emp1" class="s1.Employee">
<property name="name" value="北京"/>
<property name="id" value="1"/>
</bean>
<!-- 聲明被注入的實例emp2 -->
<bean id="emp2" class="s1.Employee">
<property name="name" value="天津"/>
<property name="id" value="2"/>
</bean>
<bean id="department" class="s1.Department">
<!-- 用list子標(biāo)簽給數(shù)組注入值 -->
<property name="empName">
<list>
<value>小明</value>
<value>小明小明</value>
<value>小明小明小明小明</value>
</list>
</property>
<!-- 用list子標(biāo)簽給list集合屬性注入,注入的是Employee類的實例 -->
<property name="empList">
<list>
<ref bean="emp2"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
</list>
</property>
<!-- 用set子標(biāo)簽給set集合屬性注入厢呵,注入的是Employee類的實例 -->
<property name="empsets">
<set>
<ref bean="emp1"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
</set>
</property>
<!-- 用map和entry子標(biāo)簽給map集合屬性注入窝撵,注入的是Employee類的實例,同時給每個實例一個key -->
<property name="empMaps">
<map>
<entry key="11" value-ref="emp1"/>
<entry key="22" value-ref="emp2"/>
<entry key="22" value-ref="emp1"/>
</map>
</property>
<!-- 用給props和prop子標(biāo)簽給property屬性集合注入襟铭,注入的key和value都是String -->
<property name="pp">
<props>
<prop key="pp1">abcd</prop>
<prop key="pp2">hello</prop>
</props>
</property>
</bean>
...
- 寫測試類測試上面的集合屬性注入結(jié)果
...
Department department=(Department) context.getBean("department");
for(String emName:department.getEmpName()){
System.out.println(emName);
}
System.out.println("**********取出list集合數(shù)據(jù)*****");
for(Employee e : department.getEmpList()){
System.out.println("name="+e.getName()+" "+e.getId());
}
System.out.println("**********取出set集合數(shù)據(jù)*****");
for(Employee e : department.getEmpsets()){
System.out.println("name="+e.getName());
}
System.out.println("*******取出map集合數(shù)據(jù)方法一****");
Map<String,Employee> empmaps=department.getEmpMaps();
Iterator it=empmaps.keySet().iterator();
while(it.hasNext()){
String key=(String) it.next();
Employee emp=empmaps.get(key);
System.out.println("key="+key+" "+emp.getName());
}
System.out.println("*******取出map集合數(shù)據(jù)方法二****");
Set<Map.Entry<String, employee>> entries = department.getEmpMaps().entrySet();
for (Map.Entry<String, employee> e:entries
) {
System.out.println(e.getKey()+" "+e.getValue().getId()+" "+e.getValue().getName());
}
System.out.println("*******取出map集合數(shù)據(jù)方法三****");
Set<String> keySet = department.getEmpMaps().keySet();
for (String s:keySet
) {
System.out.print(s+" ");
employee employee = d.getEmpmap().get(s);
System.out.println(employee.getId()+" "+employee.getName());
}
System.out.println("**********取出Properties數(shù)據(jù)*****");
Properties pp = d.getPp();
System.out.println(pp.getProperty("name"));
System.out.println(pp.getProperty("url"));
Enumeration<?> enumeration = pp.propertyNames();
while(enumeration.hasMoreElements()){
System.out.println(enumeration.nextElement());
}
...
注解的方式
語法 : @注解名("參數(shù)")
用注解不能有構(gòu)造器碌奉,否則會報錯
ioc,替代bean標(biāo)簽的注解
@Component - 其他類
@Controller - servlet
@Service - service
@Repository - dao
注解在class聲明上方蝌矛,同樣的功能道批,分四個單詞,不同層的類用不同單詞類注解
@Service("MyBean") 相當(dāng)于 <bean id="MyBean" class="被注解的類"/>
@Service("MyBean")
public class DIHW {
private HelloWorld hImpl;
public void print(){
hImpl.sayHello(" d i ");
}
}
di入撒,替代property和ref標(biāo)簽的注解
- @Resource - javax
- @AutoWired @Qualifier - spring
注解在需要被注入的屬性上面隆豹,可以聲明被注入實例的id,或者默認(rèn)
[@Resource(name="MyBean")] 相當(dāng)于
[@AutoWired @Qualifier("hello313Impl")] 相當(dāng)于
<property name="注解下面的屬性名">
<ref bean="MyBean"/>
</property>
@Autowired
@Qualifier("MyBean")
或
@Resource(name="MyBean")
//@Resource(type=MyBean.class)
private HelloWorld hImpl;
di注解默認(rèn)的注入規(guī)則
AutoWired注解的默認(rèn)規(guī)則——按照接口類型自動裝配
只寫這個注解茅逮,產(chǎn)生的id按照駝峰命名法命名璃赡,按照被注入的屬性的接口類型來找實例,如果找不到献雅,或找到多個都報錯碉考;
如果想按照名字來找實例,或者有多個類實現(xiàn)這個接口挺身,必須引入Qualifier注解
Resource注解的默認(rèn)規(guī)則——按照屬性名自動裝配
1.如果同時指定了name和type侯谁,則從Spring上下文中找到唯一匹配的bean進(jìn)行裝配,找不到則拋出異常章钾。
2.如果指定了name墙贱,則從上下文中查找名稱(id)匹配的bean進(jìn)行裝配,找不到則拋出異常贱傀。
3.如果指定了type惨撇,則從上下文中找到類似匹配的唯一bean進(jìn)行裝配,找不到或是找到多個府寒,都會拋出異常魁衙。
4.如果既沒有指定name,又沒有指定type株搔,則自動按照byName方式進(jìn)行裝配;如果沒有匹配在按照類型裝配纵隔。
自動裝配和掃描
spring配置文件的頭部的通用定義帆卓,如果要使用注入米丘,要引入新的命名空間和新的校驗文件
<?xml version="1.0" encoding="UTF-8"?>
...
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
">
...
</beans>
啟動注解拄查,并且自動掃描那個類被注解堕扶,需要在spring的配置文件中添加標(biāo)簽
...
<context:annotation-config/>
<!--聲明4個類幫助系統(tǒng)識別注解-->
<context:component-scan base-package="要掃描的包"/>
<!--使用 <context:component-scan/> 后,就可以將 <context:annotation-config/> 移除了--!>
...
AOP—面向切面編程(對事務(wù)進(jìn)行控制)
多個類中有相同的代碼塊梭依,為減少代碼量稍算,將此代碼塊取出,作為一個切面役拴,切面的前后位置叫做切點糊探,將此切面交給Spring框架管理,自動執(zhí)行此段代碼河闰,減少了方法中的代碼量科平。
相關(guān)概念
1.切面(Aspect):需要插入的代碼塊
2.連接點(JoinPoint):
3.切點(Pointcut):指定某個方法作為切面切的地方
4.通知(Advice):指定切面在切點的插入位置
前置通知(before advice)
后置通知(after return advice)
環(huán)繞通知(around advice)
異常通知(after throwing advice)
5.目標(biāo)對象(target object)
6.AOP代理(aop proxy)
XML和注解方式AOP
xml方式
- 新建兩個類
//被切入的類
public class Car {
public void go(){
System.out.println("go go go!");
}
}
//存放切入代碼的類
public class CarLogger {
public void beforeRun(){
System.out.println("之前");
}
public void afterRun(){
System.out.println("之后");
}
}
- 創(chuàng)建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: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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="car" class="aop.Car" />
<bean id="logger" class="aop.CarLogger" />
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut expression="execution(* aop.Car.go(..))" id="go"/>
<!--切點表達(dá)式:找到切點,“..”為參數(shù)任意姜性〉苫郏“*”為返回值任意-->
<aop:before pointcut-ref="go" method="beforeRun" />
<aop:after pointcut-ref="go" method="afterRun" />
</aop:aspect>
</aop:config>
</beans>
- 創(chuàng)建測試類
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAop {
public static void main( String[] args ){
ApplicationContext context = new ClassPathXmlApplicationContext("aop-config.xml");
Car car=(Car) context.getBean("car");
car.go();
}
}
注解方式
- 同上新建兩個類
- 修改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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="car" class="aop.Car" />
<bean id="logger" class="aop.CarLogger" />
<!--自動完成創(chuàng)建代理織入切面的工作-->
<aop:aspectj-autoproxy/>
</beans>
- 修改CarLogger類如下
@Aspect
public class CarLogger {
@Before("execution(* aop.Car.go(..))")
public void beforeRun(){
System.out.println("之前");
}
@After("execution(* aop.Car.go(..))")
public void afterRun(){
System.out.println("之后");
}
}
或
@Aspect
public class CarLogger {
@Pointcut("execution(* aop.Car.go(..))")
public void anyMethod(){}
@Before("anyMethod()")
public void beforeRun(){
System.out.println("之前");
}
@After("anyMethod()")
public void afterRun(){
System.out.println("之后");
}
}