Spring架構由諸多模塊組成,可分類爲java
Spring架構組成以下圖
web
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.qf</groupId> <artifactId>hello-spring</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- Spring經常使用依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.6.RELEASE</version> </dependency> </dependencies> </project>
命名無限制,約定俗成命名有:spring-context.xml、applicationContext.xml、beans.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
public class MyClass{ public void show(){ System.out.println("HelloWorld"); } }
在spring-context.xml中配置MyClass的bean後,當項目啓動時,spring容器會自動建立MyClass實例,這個實例名字叫mcspring
<!-- 配置實例(id:「惟一標識」 class="須要被建立的目標對象全限定名") --> <bean id="mc" class="com.qf.spring.part1.factory.MyClass" />
測試代碼express
public class TestFactory{ /** * 程序中的對象都交由Spring的ApplicationContext工廠進行建立。 */ public static void main(String[] args){ //1\. 讀取配置文件中所需建立的bean對象,並得到工廠對象 ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml"); //2\. 經過id獲取bean對象 MyClass mc = (MyClass) ctx.getBean("mc"); //3\. 使用對象 mc.show(); } }
控制反轉是Spring框架的核心,所謂控制反轉就是應用自己不負責依賴對象的建立及維護,依賴對象的建立及維護是由外部容器負責的, 這樣控制權就由應用轉移到了外部容器,控制權的轉移就是所謂反轉。這樣就由以前的本身建立依賴對象,變爲由spring容器建立。(變主動爲被動,即反轉)。控制反轉解決了具備依賴關係的組件之間的強耦合,使得項目形態更加穩健。apache
public class UserDAOImpl implements UserDAO{....}
public class UserServiceImpl implements UserService { // 經過傳統的new方式強耦合了UserDAOImpl!!!,使得UserServiceImpl變得不穩健!! private UserDAO userDAO= new UserDAOImpl(); @Override public User queryUser() { return userDAO.queryUser(); } .... }
// 不引用任何一個具體的組件(實現類),在須要其餘組件的位置預留存取值入口(set/get) public class UserServiceImpl implements UserService { // !!!再也不耦合任何DAO實現!!!,消除不穩健因素!! private UserDAO userDAO; // 爲userDAO定義set/get,容許userDAO屬性接收spring賦值 //Getters And Setters @Override public User queryUser() { return userDAO.queryUser(); } .... }
在spring配置文件中配置UserDAO和UserService對應的bean編程
<bean id="userDAO" class="com.qf.spring.part1.injection.UserDaoImpl"></bean> <!-- UserServiceImpl組件 --> <bean id="userService" class="com.qf.spring.part1.injection.UserServiceImpl"> <!-- 由spring爲userDAO屬性賦值,值爲id="userDAO"的bean --> <property name="userDAO" ref="userDAO"/> </bean>
在Spring建立對象的同時,爲其屬性賦值,稱之爲依賴注入,注入方式主要有如下2種設計模式
建立對象時,Spring工廠會經過Setter方法爲對象的屬性賦值。緩存
public class User { private Integer id; private String password; private String sex; private Integer age; private Date bornDate; private String[] hobbys; private Set<String> phones; private List<String> names; private Map<String,String> countries; private Properties files; //Getters And Setters }
<bean id="u1" class="com.qf.spring.part1.injection.User"> <!--base field--> <property name="id" value="1001" /> <property name="password" value="123456" /> <property name="sex" value="male" /> <property name="age" value="20" /> <property name="bornDate" value="1990/1/1" /><!--注意格式"/"--> </bean>
<bean id="u1" class="com.qf.spring.part1.injection.User"> <!--Array--> <property name="hobbys"> <array> <value>Run</value> <value>Swim</value> <value>Climb</value> </array> </property> <!--Set--> <property name="phones"> <set> <value>13777777777</value> <value>13888888888</value> <value>13999999999</value> </set> </property> <!--List--> <property name="names"> <list> <value>tom</value> <value>jack</value> <value>marry</value> </list> </property> <!--Map--> <property name="countries"> <map> <entry key="CN" value="China" /> <entry key="US" value="America" /> <entry key="KR" value="Korea" /> </map> </property> <!--Properties--> <property name="files"> <props> <prop key="first">One</prop> <prop key="second">Two</prop> <prop key="third">Three</prop> </props> </property> </bean>
<!--次要bean,被做爲屬性--> <bean id="addr" class="com.qf.spring.part1.injection.Address"> <property name="position" value="北京市海淀區" /> <property name="zipCode" value="100001" /> </bean> <!--主要bean,操做的主體--> <bean id="u2" class="com.qf.spring.part1.injection.User"> <property name="address" ref="addr" /><!--address屬性引用addr對象--> </bean>
建立對象時,Spring工廠會經過構造方法爲對象的屬性賦值。服務器
public class Student { private Integer id; private String name; private String sex; private Integer age; //Constructors public Student(Integer id , String name , String sex , Integer age){ this.id = id; this.name = name; this.sex = sex; this.age = age; } }
<!--構造注入--> <bean id="u3" class="com.qf.zcg.spring.day1.t2.ioc.Student"> <constructor-arg name="id" value="1234" /> <!-- 除標籤名稱有變化,其餘均和Set注入一致 --> <constructor-arg name="name" value="tom" /> <constructor-arg name="age" value="20" /> <constructor-arg name="sex" value="male" /> </bean>
工廠建立以後,會將Spring配置文件中的全部對象都建立完成(餓漢式),提升程序運行效率,避免屢次IO,減小對象建立時間。(概念接近鏈接池,一次性建立好,使用時直接獲取)
import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @PostConstruct //初始化 public void init(){ System.out.println("init method executed"); } @PreDestroy //銷燬 public void destroy(){ System.out.println("destroy method executed"); }
單例bean:singleton
隨工廠啓動建立 ==》 構造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構建完成 ==》隨工廠關閉銷燬
多例bean:prototype
被使用時建立 ==》 構造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 構建完成 ==》JVM垃圾回收銷燬
將核心功能與輔助功能(事務、日誌、性能監控代碼)分離,達到核心業務功能更純粹、輔助業務功能可複用。
經過代理類的對象,爲原始類的對象(目標類的對象)添加輔助功能,更容易更換代理實現類、利於維護。
//目標 final OrderService os = new OrderServiceImpl(); //額外功能 InvocationHandler handler = new InvocationHandler(){//1.設置回調函數(額外功能代碼) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("start..."); method.invoke(os, args); System.out.println("end..."); return null; } }; //2.建立動態代理類 Object proxyObj = Proxy.newProxyInstance(ClassLoader , Interfaces , InvocationHandler);
final OrderService os = new OrderServiceImpl(); Enhancer cnh = new Enhancer();//1.建立字節碼曾強對象 enh.setSuperclass(os.getClass());//2.設置父類(等價於實現原始類接口) enh.setCallback(new InvocationHandler(){//3.設置回調函數(額外功能代碼) @Override public Object invoke(Object proxy , Method method, Object[] args) throws Throwable{ System.out.println("start..."); Object ret = method.invoke(os,args); System.out.println("end..."); return ret; } }); OrderService proxy = (OrderService)enh.create();//4.建立動態代理類 proxy,createOrder();
AOP(Aspect Oriented Programming),即面向切面編程,利用一種稱爲"橫切"的技術,剖開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。
通俗的概念來說,所謂的面向切面編程就是針對被代理對象的方法在某個特定的執行時機(方法調用以前、方法調用以後、方法拋出異常),作出一些額外的橫向的處理。好處在於:1. 能夠在不修改原有代碼基礎上橫向擴展咱們的內容;2. 將一些方法中的通用邏輯進行統一化的處理。
OOP(面向對象編程)和AOP(面向切面編程)的區別:oop是對類縱向的擴展;aop是橫向的擴展。
Spring的AOP編程便是經過動態代理類爲原始類的方法添加輔助功能。
引入AOP相關依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.6.RELEASE</version> </dependency>
spring-context.xml引入AOP命名空間
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> </beans>
定義原始類
package com.qf.aaron.aop.basic; public interface UserService { public void save(); }
package com.qf.aaron.aop.basic; public class UserServiceImpl implements UserService { public void save() { System.out.println("save method executed..."); } }
定義通知類(添加額外功能
package com.qf.aaron.aop.basic; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class MyAdvice implements MethodBeforeAdvice { //實現前置通知接口 public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("before advice executed..."); } }
定義bean標籤
<!--原始對象--> <bean id="us" class="com.qf.aaron.aop.basic.UserServiceImpl" /> <!--輔助對象--> <bean id="myAdvice" class="com.qf.aaron.aop.basic.MyAdvice" />
定義切入點(PointCut)
造成切面(Aspect)
<aop:config> <!--切點--> <aop:pointcut id="myPointCut" expression="execution(* save())" /> </aop:config> ```java <aop:config> <!--組裝切面 --> <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" /> </aop:config>
可定義的通知類有6種,能夠按需求選擇通知類。
前置通知:MethodBeforeAdvice 後置通知:AfterAdvice 後置通知:AfterReturningAdvice //有異常不執行,方法會因異常而結束,無返回值 異常通知:ThrowsAdvice 環繞通知:MethodInterceptor
可是spring中默認開啓JDK動態代理,當須要使用CGLIB動態代理時,須要在spring配置文件中配置。
<!-- 使用cglib的方式實現aop --> <aop:aspectj-autoproxy proxy-target-class="true"/>
編寫aspectJ通知代碼:
@Aspect public class AspectJAdvisor { // 環繞通知 @Around("execution(* org.example.service.impl.*.*(..))") public Object timer(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("前置通知"); Object o = pjp.proceed(); System.out.println("【後置通知】"); return o; } // 後置通知 @After("execution(* org.example.service.impl.*.*(..))") public void after(JoinPoint jp) throws Throwable{ System.out.println("====After method invokded===="); } // 前置通知 @Before("execution(* org.example.service.impl.*.*(..))") public void before(JoinPoint jp){ System.out.println("====before method invoked===="); } // 正常返回的通知 @AfterReturning("execution(* org.example.service.impl.*.*(..))") public void afterReturning(JoinPoint jp){ System.out.println("====after value return===="); } // 拋出異常後的通知,方法的異常必須與代理類拋出的異常一致,throwing的值要與異常的形參名保持一致 @AfterThrowing(value = "execution(* org.example.service.impl.*.*(..))", throwing="npe") public void afterThrowException(JoinPoint jp, NullPointerException npe){ System.out.println("====after exception throwing===="); } }
配置
<bean id="userService" class="org.example.service.impl.UserServiceImpl"></bean> <bean id="throwsAdvisor" class="org.example.advisor.AspectJAdvisor"></bean> <!--- aspectJ是使用cglib來實現動態代理的 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
用於替換自建類型組件的 <bean…>標籤;能夠更快速的聲明bean
// @Service說明 此類是一個業務類,須要將此類歸入工廠 等價替換掉 <bean class="xxx.UserServiceImpl"> // @Service默認beanId == 首字母小寫的類名"userServiceImpl" // @Service("userService") 自定義beanId爲"userService" @Service //聲明bean,且id="userServiceImpl" @Scope("singleton") //聲明建立模式,默認爲單例模式 ;@Scope("prototype")便可設置爲多例模式 public class UserServiceImpl implements UserService { ... }
用於完成bean中屬性值的注入
@Service public class UserServiceImpl implements UserService { @Autowired //注入類型爲UserDAO的bean @Qualifier("userDAO2") //若是有多個類型爲UserDAO的bean,能夠用此註解從中挑選一個 private UserDAO userDAO; }
@Service public class UserServiceImpl implements UserService { @Resource("userDAO3") //注入id=「userDAO3」的bean private UserDAO userDAO; /* @Resource //注入id=「userDAO」的bean private UserDAO userDAO; */ }
public class XX{ @Value("100") //注入數字 private Integer id; @Value("shine") //注入String private String name; }
用於控制事務切入
//類中的每一個方法都切入事務(有本身的事務控制的方法除外) @Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1) public class UserServiceImpl implements UserService { ... //該方法本身的事務控制,僅對此方法有效 @Transactional(propagation=Propagation.SUPPORTS) public List<User> queryAll() { return userDao.queryAll(); } public void save(User user){ userDao.save(user); } }
<!-- 告知spring,哪些包中 有被註解的類、方法、屬性 --> <!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> --> <context:component-scan base-package="com.qf"></context:component-scan> <!-- 告知spring,@Transactional在定製事務時,基於txManager=DataSourceTransactionManager --> <tx:annotation-driven transaction-manager="txManager"/>
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect // 聲明此類是一個切面類:會包含切入點(pointcut)和通知(advice) @Component //聲明組件,進入工廠 public class MyAspect { // 定義切入點 @Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))") public void pc(){} @Before("pc()") // 前置通知 public void mybefore(JoinPoint a) { System.out.println("target:"+a.getTarget()); System.out.println("args:"+a.getArgs()); System.out.println("method's name:"+a.getSignature().getName()); System.out.println("before~~~~"); } @AfterReturning(value="pc()",returning="ret") // 後置通知 public void myAfterReturning(JoinPoint a,Object ret){ System.out.println("after~~~~:"+ret); } @Around("pc()") // 環繞通知 public Object myInterceptor(ProceedingJoinPoint p) throws Throwable { System.out.println("interceptor1~~~~"); Object ret = p.proceed(); System.out.println("interceptor2~~~~"); return ret; } @AfterThrowing(value="pc()",throwing="ex") // 異常通知 public void myThrows(JoinPoint jp,Exception ex){ System.out.println("throws"); System.out.println("===="+ex.getMessage()); } }
<!-- 添加以下配置,啓用aop註解 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
能夠免去工廠的建立過程;
能夠直接將要測試的組件注入到測試類。
@RunWith(SpringJUnit4Cla***unner.class) //由SpringJUnit4Cla***unner啓動測試 @ContextConfiguration("classpath:applicationContext.xml") //spring的配置文件位置 public class SpringTest{//當前測試類也會被歸入工廠中,因此其中屬性能夠注入 @Autowired // 注入要測試的組件 @Qualifier("userDAO") private UserDAO userDAO; @Test public void test(){ // 測試使用userDAO userDAO.queryUser(); .... } }
感謝你看到這裏,看完有什麼的不懂的能夠在評論區問我,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章!