Spring核心功能
DI(IOC)
何謂DI(IOC)
DI(依賴注入)是spring的核心功能之一。
Dependency Injection 和 Inversion of Control 其實(shí)就是一個(gè)東西的兩種不同的說法而已。本質(zhì)上是一回事。Dependency Injection 是一個(gè)程序設(shè)計(jì)模式和架構(gòu)模型, 一些時(shí)候也稱作 Inversion of Control,盡管在技術(shù)上來講,Dependency Injection 是一個(gè) Inversion of Control 的特殊實(shí)現(xiàn),Dependency Injection 是指一個(gè)對(duì)象應(yīng)用另外一個(gè)對(duì)象來提供一個(gè)特殊的能力,例如:把一個(gè)數(shù)據(jù)庫連接以參數(shù)的形式傳到一個(gè)對(duì)象的結(jié)構(gòu)方法里面而不是在那個(gè)對(duì)象內(nèi)部自行創(chuàng)建一個(gè)連接。Inversion of Control 和 Dependency Injection 的基本思想就是把類的依賴從類內(nèi)部轉(zhuǎn)化到外部以減少依賴。 應(yīng)用Inversion of Control,對(duì)象在被創(chuàng)建的時(shí)候,由一個(gè)調(diào)控系統(tǒng)內(nèi)所有對(duì)象的外界實(shí)體,將其所依賴的對(duì)象的引用,傳遞給它。也可以說,依賴被注入到對(duì)象中。所以,Inversion of Control 是,關(guān)于一個(gè)對(duì)象如何獲取他所依賴的對(duì)象的引用,這個(gè)責(zé)任的反轉(zhuǎn)。IoC是通過處理對(duì)象定義依賴的方式來工作,也就是說,一起協(xié)作的對(duì)象,要么通過構(gòu)造函數(shù)參數(shù)來獲得,要么在構(gòu)造之后給對(duì)象設(shè)置屬性來獲得,要么從工廠方法返回的方式來獲得。容器先創(chuàng)建bean,然后再注入這些依賴。這個(gè)獲取過程是完全反過來的,所以命名為控制反轉(zhuǎn)(IoC)。
DI能夠刪除任何特定的依賴于別的類或第三方接口的類,并且能夠在初始化構(gòu)造時(shí)加載要依賴的類。DI的優(yōu)點(diǎn)是你可以依賴類的實(shí)現(xiàn)而并不需要更改你的代碼。你甚至可以在接口不變的條件下重寫依賴的實(shí)現(xiàn)而不用改變你的編碼,即面向接口的編程。
DI實(shí)現(xiàn)方式
- 構(gòu)造器注入:通過構(gòu)造器注入,能使當(dāng)前實(shí)例作為不可變對(duì)象,并且能確保所有需要的依賴都是非空的.更進(jìn)一步,構(gòu)造器注入返回給客戶代碼的是一個(gè)完全初始化狀態(tài)的對(duì)象.
- Setter方法注入:Setter方法注入作為構(gòu)造器注入的補(bǔ)充實(shí)現(xiàn).能注入可選的有默認(rèn)值的依賴.否則,會(huì)隨處校驗(yàn)依賴的非空與否.
- 自動(dòng)裝配
-
@Autowired:即通過注解自動(dòng)裝配,默認(rèn)方式是byType. -
@Resource:即通過注解自動(dòng)裝配,默認(rèn)方式是byName. -
@javax.inject.Inject:類似與@Autowired -
@Qualifier:指定實(shí)現(xiàn)不同的限定符,在具體注入時(shí),通過該注解具體限定
-
Spring容器會(huì)在容器加載時(shí)校驗(yàn)依賴非空和循環(huán)依賴.在初始化Bean時(shí),Spring會(huì)在bean真正創(chuàng)建之前盡可能晚的設(shè)置屬性和解決依賴關(guān)系.
bean
bean定義和依賴實(shí)現(xiàn)方式
XML文本配置文件
<beans>
<bean id="beanId" class="com.example.ClassName"></bean>
</beans>
通過上面的方式來定義一個(gè)bean,通過在bean中添加依賴來達(dá)到目的.
主要依賴方式有:
- 構(gòu)造器注入.
- Setter屬性方法注入
通過XML配置文件構(gòu)造Spring beans和依賴缺失了編譯時(shí)的類型檢查,比如構(gòu)造器參數(shù)的類型錯(cuò)誤,甚至是構(gòu)造器錯(cuò)誤的參數(shù)只有在ApplicationContext容器在運(yùn)行時(shí)構(gòu)造時(shí)才會(huì)檢查。
使用注解
通過配置自動(dòng)注解掃描的根包,并且在bean上使用注解@Component(@Service,@Repositoty,@javax.inject.Named)等標(biāo)示他是一個(gè)bean.
<!-- 開啟注解配置 即Autowried -->
<context:annotation-config/>
<!-- 使用自動(dòng)注入的時(shí)候要 添加他來掃描bean之后才能在使用的時(shí)候 -->
<context:component-scan base-package="com.wyp.module.service,com.wyp.module.dao"/>
通過如上配置自動(dòng)掃描根包下的類,并作自動(dòng)注解綁定
解釋下<context:annotation-config/>:
它的作用是隱式的向
Spring容器注冊(cè)AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor這4個(gè)
BeanPostProcessor.注冊(cè)這4個(gè)bean處理器主要的作用是為了你的系統(tǒng)能夠識(shí)別相應(yīng)的注解。
如果想使用@Autowired,@PersistenceContext,@Required,@Resource,@PostConstruct,@PreDestroy,就需要按照傳統(tǒng)聲明一條一條去聲明注解Bean,就會(huì)顯得十分繁瑣.
因此如果在Spring的配置文件中事先加上<context:annotation-config/>這樣一條配置的話,那么所有注解的傳統(tǒng)聲明就可以被忽略,即不用在寫傳統(tǒng)的聲明,Spring會(huì)自動(dòng)完成聲明。
解釋下<context:component-scan/>:
作用是讓Bean定義注解工作起來,也就是上述傳統(tǒng)聲明方式.它的base-package屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會(huì)被處理。
值得注意的是
<context:component-scan/>不但啟用了對(duì)類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng) Bean 定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊(cè)了AutowiredAnnotationBeanPostProcessor和
CommonAnnotationBeanPostProcessor),因此當(dāng)使用<context:component-scan/>后,就可以將<context:annotation-config/>移除了。
@Autowired
private UserService userService;
@Service("userService")
public class UserServiceImpl implements UserService
通過如上配置定義一個(gè)bean,并作自動(dòng)綁定
主要注解依賴方式有:
-
@Autowired:即通過注解自動(dòng)裝配,默認(rèn)方式是byType. -
@Resource:即通過注解自動(dòng)裝配,默認(rèn)方式是byName. -
@javax.inject.Inject:類似與@Autowired
當(dāng)通過@Autowired注入時(shí),默認(rèn)是通過類型匹配具體的實(shí)現(xiàn)類的,但是如果接口有多個(gè)實(shí)現(xiàn)類,Spring容器是沒法做選擇的,有兩種方式解決這個(gè)問題:
-
@Primary注解,指定當(dāng)有多個(gè)候選實(shí)現(xiàn)時(shí),首選這個(gè)實(shí)現(xiàn).
2.@Qualifier注解指定不同實(shí)現(xiàn)不同的限定符,在具體注入時(shí),通過該注解具體限定.
解釋下@Autowired:
可以對(duì)成員變量、方法和構(gòu)造函數(shù)進(jìn)行標(biāo)注,來完成自動(dòng)裝配的工作。
@Autowired的標(biāo)注位置不同。它們都會(huì)在Spring在初始化這個(gè)bean時(shí),自動(dòng)裝配這個(gè)屬性。注解之后就不需要
set/get方法了。
其中@Inject 和@Named是JSR 330 Standard Annotations.s
通過Spring提供的擴(kuò)展方式做處理
- 可以通過
init-method的方式來實(shí)現(xiàn)初始化注入,還可以通過實(shí)現(xiàn)``InitializingBean`接口來實(shí)現(xiàn),但此種方式對(duì)業(yè)務(wù)代碼有侵入性,少用。 - bean加載過程可以通過設(shè)置
factory-method的方式設(shè)置工廠方法,來設(shè)置一些靜態(tài)屬性 - 調(diào)用
getter方法,用工廠Bean PropertyPathFactoryBean - 調(diào)用普通方法(實(shí)例方法或者類方法),用工廠
Bean MethodInvokingFactoryBean - 獲取Field的值,用工廠
Bean FieldRetrievingFactoryBean
@Configuration&@Bean
@Bean可以出現(xiàn)在@Configurationor@Component,其中@Configuration類似于xml中的<beans>,而@Component類似于xml中的<bean>,@Component可以作為@Configuration的替代。
但是有一些問題:當(dāng)我們使用@Bean注解在例如@Component作用的class里面時(shí),將會(huì)發(fā)生一種稱之為注解@Bean的lite mode出現(xiàn),這種不會(huì)使用CGLIB代理.所以只要我在@Bean修飾的方法之間不相互編碼調(diào)用,代碼將會(huì)很好的運(yùn)作.
下面是@Bean的lite mode示例:
@Component
public class ConfigInComponent {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
上述代碼在new SimpleBeanConsumer(simpleBean())這一步實(shí)例化bean時(shí),不會(huì)將第一步@Bean實(shí)例化的bean自動(dòng)注入到simpleBeanConsumerbean中,而是重新用simpleBean(),生成一個(gè)新的SimpleBean 實(shí)例.而@Configuration則不會(huì)發(fā)生上述情況,代碼如下:
@Configuration
public class ConfigInConfiguration {
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
}
要改善上述問題,可以通過以下方式實(shí)現(xiàn):
@Component
public class ConfigInComponent {
@Autowired
SimpleBean simpleBean;
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
@Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean);
}
}
通過將@Bean生成的bean Autowired到屬性上,并在@Bean實(shí)例化SimpleBeanConsumerbean時(shí)傳入此屬性,來達(dá)到目的.
參考: Spring @Configuration vs @Component
bean生命周期

bean的作用域
sessionrequestprototypesingletonapplication
其中singleton是容器級(jí)別的,即一個(gè)容器一個(gè)bean實(shí)例,spring的單例實(shí)例緩存在ConcurrentHashMap中;而GOF的單例模式是基于ClassLoader的,即一個(gè)類加載器只能有一個(gè)實(shí)例
通過bean后處理來增強(qiáng)功能
BeanFactoryPostProcessor
通過實(shí)現(xiàn)BeanFactoryPostProcessor,對(duì)bean配置的元數(shù)據(jù)做一些處理(可以改變初始化bean的內(nèi)容),比如為安全考慮的數(shù)據(jù)庫密碼加密配置在配置文件中,在jdbc連接數(shù)據(jù)庫時(shí)需要解密可以通過擴(kuò)展BeanFactoryPostProcessor來實(shí)現(xiàn).
@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition definition = beanFactory.getBeanDefinition("xmlBeanDefinition");
MutablePropertyValues propertyValues=definition.getPropertyValues();
if (propertyValues.contains("name")) {
PropertyValue property=propertyValues.getPropertyValue("name");
String name=((TypedStringValue) property.getValue()).getValue();
propertyValues.add("name",name.replace(" ",""));
}
if (propertyValues.contains("age")) {
PropertyValue property=propertyValues.getPropertyValue("age");
Double age=Double.parseDouble(((TypedStringValue) property.getValue()).getValue());
propertyValues.add("age",Math.round(age));
}
}
@Override
public int getOrder() {
return 3;
}
}
如上,可以通過實(shí)現(xiàn)Ordered或者注解@Order的方式來指定加載順序.
BeanPostProcessor
通過實(shí)現(xiàn)BeanPostProcessor,可以實(shí)現(xiàn)在Spring容器在完成Bean的實(shí)例化,配置和其他的初始化前后做一些自己的業(yè)務(wù)處理,比如我們可以統(tǒng)計(jì)自定義的的Bean集合.
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(beanName.equals("userService")){
UserService ds=(UserService) bean;
System.out.println(ds);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(beanName.equals("userService")){
UserService ds=(UserService) bean;
System.out.println(ds);
}
return bean;
}
}
AOP(面向切面的編程)
AOP(面向切面的編程)是通過劃分關(guān)注點(diǎn),來做一些事,這些事在實(shí)際編程中與整體業(yè)務(wù)無關(guān),比如事務(wù)控制,日志管理等,通過aop將其與業(yè)務(wù)代碼解耦,以實(shí)現(xiàn)精簡(jiǎn)的業(yè)務(wù)編碼.其整體描述為在什么時(shí)候做什么事.
AOP三要素:
- 方面(Aspect)--多個(gè)通知和切點(diǎn)的集合
- 切入點(diǎn)(Pointcut)--在什么地方
- 通知(增強(qiáng)處理)(Advice)--在什么時(shí)候干什么事
- 比如有
MethodInterceptor,AfterAdvice,BeforeAdvice等.描述的是在什么時(shí)候做一些增強(qiáng)處理.
- 比如有
在編程配置中,如下:
<bean id="methodAdvice" class="com.earthlyfish.aop.EhcacheAroundAdvice">
<property name="cache" ref="methodCache"></property>
</bean>
<!-- 配置切點(diǎn),通知 -->
<aop:config>
<aop:pointcut
expression="(execution(public * com.earthlyfish.service..get*(..))) or (execution(public * com.earthlyfish.service..find*(..)))"
id="methodCachePoint" />
<aop:advisor advice-ref="methodAdvice" pointcut-ref="methodCachePoint" />
</aop:config>
<aop:pointcut>:用來定義切入點(diǎn),該切入點(diǎn)可以重用
<aop:advisor>:用來定義只有一個(gè)通知和一個(gè)切入點(diǎn)的切面
<aop:aspect>:用來定義切面,該切面可以包含多個(gè)切入點(diǎn)和通知,而且標(biāo)簽內(nèi)部的通知和切入點(diǎn)定義是無序的;和advisor的區(qū)別就在此,advisor只包含一個(gè)通知和一個(gè)切入點(diǎn)
事務(wù)管理
Spring通過TransactionManager來實(shí)現(xiàn)事務(wù)管理,現(xiàn)有兩種方式,一種是通過aop注入式的方式實(shí)現(xiàn),另一種是通過@Transactional在方法上實(shí)現(xiàn)事務(wù)管理.
aop注入式
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="query*" read-only="true" propagation="REQUIRED" />
<tx:method name="get*" read-only="true" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="add*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="save*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="update*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="delete*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="remove*" propagation="REQUIRED"
rollback-for="java.lang.Exception,java.lang.RuntimeException" />
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<!--切入點(diǎn)指明了在所有方法產(chǎn)生事務(wù)攔截操作 -->
<aop:pointcut id="module-pointcut"
expression="execution(* com.wyp.module.service.*.*(..))" />
<!--定義了將采用何種攔截操作,這里引用到 txAdvice,即在什么時(shí)候做什么事 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="module-pointcut" />
</aop:config>
@Transactional
需要在配置文件中有tx相關(guān)的注解.
<!-- 此注解表示聲明式事務(wù),在方法上通過@Transactional控制事務(wù) -->
<tx:annotation-driven transaction-manager="txManager" />
在需要的地方通過@Transactional注入:
@Transactional(propagation=Propagation.REQUIRED)
public UserType registerUser(Customer customer) {
對(duì)于這種方式,如果一個(gè)類里面有兩個(gè)方法A和B,A方法調(diào)用了B方法,如果調(diào)用A方法的話,整個(gè)事務(wù)運(yùn)用會(huì)有下面三種情況:
- A上加了注解,B上不加,則整體會(huì)受事務(wù)控制.
- A上加了注解,B上加,則整體會(huì)受事務(wù)控制.其實(shí)這里調(diào)用和1一樣,在AOP代理時(shí)只給A方法加了環(huán)繞事務(wù)通知.
- A上不加注解,B上加,則整體不受事務(wù)控制.這是因?yàn)檎{(diào)用A方法時(shí)由于沒有判斷到事務(wù)注解的存在,因此代理類沒有聲稱事務(wù)控制的字節(jié)碼,這直接使用的被代理類的字節(jié)碼,所以不受事務(wù)控制.
編碼式
編碼式通過在代碼中加入事務(wù)管理來達(dá)到目的,但由于事務(wù)邏輯與主業(yè)務(wù)邏輯沒啥關(guān)系,代碼沒必要緊耦合在一起,所以此方法不怎么使用.
JPA知識(shí)點(diǎn)
mapping pojo結(jié)構(gòu)詳述
啥事不干,先上一個(gè)多對(duì)多關(guān)系的例子.
@Entity
@Table(name = "t_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "role_name")
private String roleName;
@Column(name = "role_description")
private String roleDescription;
@Column
private boolean enabled;
//角色下的所用用戶
@JsonIgnore
@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
@Entity
@Table(name = "t_user")
public class User implements Serializable, Cloneable {
/**
* 靜態(tài)long類型常量serialVersionUID的作用:
* <p>
* 顯示的設(shè)置serialVersionUID值就可以保證版本的兼容性,如果你在類中寫上了這個(gè)值,就算類變動(dòng)了,
* 它反序列化的時(shí)候也能和文件中的原值匹配上。而新增的值則會(huì)設(shè)置成零值,刪除的值則不會(huì)顯示。
*/
private static final long serialVersionUID = -8220100956296048447L;
@Id
@GeneratedValue
private Long id;
@Column(unique = true)
private String name;
@Column(length = 32)
private String password;
@Column(length = 32)
private String salt;
@Column(precision = 3)
private int age;
/**
* 關(guān)系維護(hù)端,負(fù)責(zé)多對(duì)多關(guān)系的維護(hù)
*
* @JoinTable 表示關(guān)聯(lián)表的信息,其中:
* 1.name 表示關(guān)聯(lián)表的名字
* 2.joinColumns 指定外鍵的名字,關(guān)聯(lián)到關(guān)系維護(hù)端Role
* 3.inverseJoinColumns 指定外鍵的名字,要關(guān)聯(lián)的關(guān)系被維護(hù)端
* 以上完全可以默認(rèn),默認(rèn)情況下:
* 1.name 主表名_從表名
* 2.joinColumns 主表_id
* 3.inverseJoinColumns 從表_id
*/
@ManyToMany
@JoinTable(name = "t_user_role", joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
上述兩端代碼描述的是Role和User的多對(duì)多關(guān)系.
注解說明
-
mappedBymappedBy和inverse的作用是相同的,只不過inverse用在xml文件中,表示的意思是在關(guān)系雙方都有關(guān)系維護(hù)任務(wù)時(shí),以哪一方為主導(dǎo),即沒有被mappedBy修飾的一方維護(hù).即外鍵將儲(chǔ)存在沒有被mappedBy修飾一方. @Temporal
@Temporal是將java.sql.*下的Data等轉(zhuǎn)成java.util.*包下的Date.-
@Transient@Transient標(biāo)記該字段不記錄到數(shù)據(jù)結(jié)構(gòu)
初始化數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)
可以通過設(shè)置hibernate.hbm2ddl.auto的值來達(dá)到初始化數(shù)據(jù)結(jié)構(gòu)的目的,有以下幾個(gè)值.
-
create: 每次加載JPA,重新創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),這就是導(dǎo)致數(shù)據(jù)庫表數(shù)據(jù)丟失的原因 -
create-drop:加載JPA時(shí)創(chuàng)建,退出是刪除表結(jié)構(gòu) -
update:加載JPA自動(dòng)更新數(shù)據(jù)庫結(jié)構(gòu) -
none/validate:加載JPA時(shí),驗(yàn)證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),當(dāng)然none時(shí)不做任何操作
在本機(jī)開發(fā)調(diào)試初始化數(shù)據(jù)的時(shí)候可以選擇create、update等。
但是應(yīng)用發(fā)布正式版本的時(shí)候,對(duì)數(shù)據(jù)庫現(xiàn)有的數(shù)據(jù)或表結(jié)構(gòu)進(jìn)行自動(dòng)的更新是很危險(xiǎn)的。此時(shí)此刻應(yīng)該由DBA同志通過手工的方式進(jìn)行后臺(tái)的數(shù)據(jù)庫操作。
hibernate.hbm2ddl.auto的值建議是none或validate。validate應(yīng)該是最好的選擇:這樣 spring在加載之初,如果model層和數(shù)據(jù)庫表結(jié)構(gòu)不同,就會(huì)報(bào)錯(cuò),這樣有助于技術(shù)運(yùn)維預(yù)先發(fā)現(xiàn)問題。
當(dāng)然我們可以通過自定義初始化腳本的方式來實(shí)現(xiàn)初始化數(shù)據(jù):
通過data: classpath:/database/import.sql的方式來實(shí)現(xiàn)--Spring boot親測(cè)通過
遇到的問題
-
Spring MVC轉(zhuǎn)換JPA多對(duì)多對(duì)象的json時(shí),無限循環(huán)問題在使用JPA處理多對(duì)多關(guān)系時(shí),發(fā)生了無限循環(huán)的問題,代碼如下:
@Entity @Table(name = "t_menu") public class Menu implements Comparable<Menu> { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String menuName; private String menuDesc; private int priority; private String staticIndex; private int parantId; private boolean enabled; @Transient private List<Menu> children; //菜單所屬role @ManyToMany(mappedBy = "roleMenus", fetch = FetchType.LAZY) private Set<Role> roles = new HashSet<>(); //菜單所屬role @ManyToMany(mappedBy = "userMenus", fetch = FetchType.LAZY) private Set<User> users = new HashSet<>(); }上面轉(zhuǎn)換成json數(shù)據(jù)時(shí),出現(xiàn)無限循環(huán)以致棧溢出.
針對(duì)以上問題可以通過以下注解實(shí)現(xiàn):- @JsonIgnore json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開無限遞歸,序列化和反序列化均忽略,可以用在字段或get(序列化),set(反序列化)方法上
- @JsonBackReference json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開無限遞歸,序列化時(shí)忽略,可以用在字段或get(序列化),set(反序列化)方法上,序列化時(shí),相當(dāng)于@JsonIgnore
- @JsonManagedReference json轉(zhuǎn)換時(shí)會(huì)被序列化,反序列化時(shí),如果沒有該注解,則不會(huì)自動(dòng)注入@JsonBackReference標(biāo)注的屬性
使用如下:
//菜單所屬role @JsonIgnore @ManyToMany(mappedBy = "roleMenus", fetch = FetchType.LAZY) private Set<Role> roles = new HashSet<>(); //菜單所屬role @JsonBackReference @ManyToMany(mappedBy = "userMenus", fetch = FetchType.LAZY) private Set<User> users = new HashSet<>();
spring mvc
Configuring a servlet container
通過配置文件實(shí)現(xiàn)
通過org.springframework.web.servlet.DispatcherServlet管理,在web.xml里面配置:
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/rest-servlet.xml</param-value>
</init-param>
<!-- 這個(gè)配置文件在容器啟動(dòng)的時(shí)候 就加載 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
如果
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/rest-servlet.xml</param-value>
</init-param>
不指定,則默認(rèn)會(huì)加載該文件WEB-INF/[servlet-name]-servlet.xml.
通過這個(gè)web.xml,引出了另一個(gè)問題,即web.xml文件的加載順序是什么樣的?
通過查看tomcat源碼,tomcat加載web.xml的順序是:
context-param ---> listener ---> filter ---> servlet
首先tomcat會(huì)生成一個(gè)程序應(yīng)用級(jí)ServletContext,全局唯一,其中將context-param放在第一位主要是listener和filter會(huì)用到配置的初始化參數(shù),
比如Spring配置的contextConfigLocation,在ContextLoaderLister加載時(shí)會(huì)從ServletContext的初始化參數(shù)中獲取配置文件,進(jìn)行bean的初始化操作.
上面還有一點(diǎn),那就是servlet的加載,當(dāng)load-on-startup大于等于0時(shí),表示在tomcat容器啟動(dòng)時(shí)加載這個(gè)Servlet,否則,在第一次使用時(shí)才加載.
解釋下<mvc:annotation-driven />
<mvc:annotation-driven />是一種簡(jiǎn)寫形式,完全可以手動(dòng)配置替代這種簡(jiǎn)寫形式。<mvc:annotation-driven />
會(huì)自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter兩個(gè)bean,是spring MVC為@Controllers分發(fā)請(qǐng)求所必須的。
零配置實(shí)現(xiàn)
從servlet3.0開始,web支持no web.xml實(shí)現(xiàn)容器的初始化工作,是得于javax.servlet.ServletContainerInitializer對(duì)初始化工作的支持.
spring通過實(shí)現(xiàn)javax.servlet.ServletContainerInitializer,重寫onStartUp方法,來提供無xml文件的spring容器初始化工作.下面是一個(gè)spring實(shí)現(xiàn)初始化的例子.
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import cn.oracle.action.AppConfig;
public class DefaultConfigration implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) throws ServletException {
// 配置Spring提供的字符編碼過濾器
javax.servlet.FilterRegistration.Dynamic filter = container.addFilter("encoding",
new CharacterEncodingFilter());
// 配置過濾器的過濾路徑
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/");
// 基于注解配置的Spring容器上下文
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
// 注冊(cè)Spring容器配置類
rootContext.register(AppConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
}
}
具體的AppConfig相當(dāng)于原來的application-*.xml等spring的配置文件處理.如下:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"cn.oracle.*"})
public class AppConfig {
}
可以再此基礎(chǔ)上擴(kuò)展,編寫一個(gè)no without xml的app.
從上可以看到WebApplicationInitializer是一個(gè)接口,和ServletContainerInitializer沒有關(guān)系,下面看一下具體的細(xì)節(jié).
下面這一段代碼是spring做處理的過程.
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer
implements ServletContainerInitializer
{
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException
{
List initializers = new LinkedList();
if (webAppInitializerClasses != null) {
for (Class waiClass : webAppInitializerClasses)
{
if ((!waiClass.isInterface()) && (!Modifier.isAbstract(waiClass.getModifiers())) &&
(WebApplicationInitializer.class
.isAssignableFrom(waiClass))) {
try
{
initializers.add((WebApplicationInitializer)waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
可以看到SpringServletContainerInitializer實(shí)現(xiàn)了SpringServletContainerInitializer,而spring通過注解@HandlesTypes({WebApplicationInitializer.class})來實(shí)現(xiàn)掃描WebApplicationInitializer該類,并將其注入到Set集.
注 :@HandlesTypes is used to declare the class types that a ServletContainerInitializer can handle.
一些鏈接:
Spring validator
不說其他的,先看代碼-->
第一步:定義一個(gè)Validator
@Component
public class UserCUValidator implements Validator {
private UserService userService;
@Autowired
public UserCUValidator(UserService userService) {
this.userService = userService;
}
@Override
public boolean supports(Class<?> clazz) {
return UserDomain.class.isAssignableFrom(clazz)
|| User.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
if (target instanceof UserDomain) {
UserDomain domain = (UserDomain) target;
if (StringUtils.isNullOrEmpty(domain.getUser().getName())
|| StringUtils.isNullOrEmpty(domain.getUser().getPassword())
|| StringUtils.isNullOrEmpty(domain.getRePwd())) {
errors.rejectValue("user.field", "user.field.invalid", "user field is invalid");
return;
}
if (!domain.getUser().getPassword().equals(domain.getRePwd())) {
errors.rejectValue("user.password.different", "user.password.different", "user password is different");
return;
}
} else if (target instanceof User) {
ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
ValidationUtils.rejectIfEmpty(errors, "password", "password.empty");
}
}
}
第二步:使用Validator
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@Autowired
private UserCUValidator userCUValidator;
/**
* 在Spring的數(shù)據(jù)綁定器中進(jìn)行注冊(cè),而注冊(cè)時(shí)機(jī)是特定于控制器的
*
* @param binder
*/
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(userCUValidator);
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity loginUser(@RequestBody @Valid User user, HttpServletRequest request) {
ResponseEntity responseEntity = userService.loginUser(user);
if (Boolean.parseBoolean(responseEntity.getFlag())) {
user.setPassword(null);
HttpSession session = request.getSession();//sample as request.getSession(true)
session.setAttribute(CommonConstant.SESSION_CURRENT_USER, user);
}
return responseEntity;
}
很簡(jiǎn)單,原理就是在映射到具體的方法后,優(yōu)先validator校驗(yàn).
Spring boot
關(guān)于啟動(dòng)時(shí)注解
@SpringBootApplication
@ComponentScan(basePackages = "com.wyp.boot.earthlyfisher")
public class BootEntry {
/**
* 1.If you need to find out what auto-configuration is currently being
* applied, and why, start your application with the --debug switch .<br/>
*
* @param args
*/
/**
* @param args
*/
public static void main(String[] args) {
// SpringApplication.run(BootEntry.class, setBootArgs(args));
SpringApplication application = new SpringApplication(BootEntry.class);
/*
* Banner.Mode.OFF:關(guān)閉; Banner.Mode.CONSOLE:控制臺(tái)輸出,默認(rèn)方式;
* Banner.Mode.LOG:日志輸出方式;
*/
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
//application.run(setBootArgs(args));
}
/**
* command line args self define
*
* @param args
* @return
*/
private static String[] setBootArgs(String[] args) {
List<String> argsLst = new ArrayList<String>();
for (int i = 0; i < args.length; i++) {
argsLst.add(args[i]);
}
/** command line properties always take precedence over other property
* sources,and self-add command param
*/
argsLst.add("--debug");
String[] springArgs = new String[argsLst.size()];
argsLst.toArray(springArgs);
return springArgs;
}
}
@SpringBootApplication same as @Configuration @EnableAutoConfiguration @ComponentScan provides aliases to customize the attributes of @EnableAutoConfiguration and @ComponentScan
關(guān)于日志
如果日志配置如下:
logging:
file: D:\\image\\earthlyfihser.log
path: D:\\image
level:
root: info
- logging.file : 輸出到指定的文件,可以為相對(duì)路徑或者絕對(duì)路徑
- logging.path : 與logging.file 是互斥的,指定文件的路徑,默認(rèn)的文件名為 spring.log
日志文件默認(rèn)達(dá)到10Mb 時(shí),將會(huì)從新打開一個(gè)文件輸出,默認(rèn)的日志輸出級(jí)別為ERROR,WARN 和 INFO。需要注意的是日志系統(tǒng)的初始化要早于系統(tǒng)的生命周期,因此logging properties 不能夠通過@PropertySource 注解獲取。
這就是基礎(chǔ)的日志配置,可以直接在application.properties配置,我們還可以在classpath路徑下,通過定義具體的日志文件來配置.
Spring Boot 采用 Commons Logging 作為內(nèi)部的日志框架。
在默認(rèn)情況下,采用 Starters 來啟動(dòng)Spring Boot 項(xiàng)目, Logback 是默認(rèn)的日志實(shí)現(xiàn)方案。
多數(shù)據(jù)源配置:
關(guān)于打成war
在一般的項(xiàng)目中,需要將項(xiàng)目打包成war包供發(fā)布到tomcat,所以自己實(shí)現(xiàn)了下:
-
extends SpringBootServletInitializer,重寫configure方法.@SpringBootApplication public class BootEntry4War extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(BootEntry4War.class); } /** * 1.If you need to find out what auto-configuration is currently being * applied, and why, start your application with the --debug switch .<br/> * * @param args */ public static void main(String[] args) { SpringApplication application = new SpringApplication(BootEntry4War.class); application.run(args); } } - 更改pom文件打包形式為
war - 為了確保內(nèi)嵌的servlet容器不能干擾war包將部署的servlet容器。為了達(dá)到這個(gè)目的,你需要將內(nèi)嵌容器的依賴標(biāo)記為provided。
加載時(shí)預(yù)設(shè)環(huán)境變量值問題
關(guān)于Environment的使用
通過實(shí)現(xiàn)EnvironmentAware可以獲取加載的環(huán)境變量,命令行參數(shù)以及在application文件預(yù)設(shè)值的變量等.
public class BootEntry implements EnvironmentAware{
@Override
public void setEnvironment(Environment environment) {
System.out.println(environment.getProperty("spring.datasource.password"));
}
}
當(dāng)然以上已經(jīng)是完全加載后,并初始化所有Bean后,此時(shí)已經(jīng)不能對(duì)加載的值中間修改操作。
覆蓋加載的變量值的幾種方法
- 通過增加
EnvironmentPostProcessor的實(shí)現(xiàn)來實(shí)現(xiàn)該功能
public class CustomeEnvPostProcessor implements EnvironmentPostProcessor {
/**
* ConfigurableEnvironment的異變PropertySource key.
*/
private static final String SOURCE_NAME = "applicationConfigurationProperties";
/**
* 需要的application變量的key
*/
private static final String ENV_KEY_NAME = "spring.datasource.password";
@SuppressWarnings("unchecked")
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources ms = environment.getPropertySources();
if (ms.contains(SOURCE_NAME)) {
/*
* 1.獲取加載application文件的PropertySource
*/
PropertySource<?> source = ms.get(SOURCE_NAME);
if (source.containsProperty(ENV_KEY_NAME)) {
/*
* 2.由于復(fù)合關(guān)系,
* 在這里找出加載application文件的PropertySource下的所有EnumerableCompositePropertySource
*/
List<EnumerableCompositePropertySource> lst = (List<EnumerableCompositePropertySource>) source
.getSource();
/*
* 3.遍歷EnumerableCompositePropertySource集合,<br/>
* 找出每一個(gè)PropertySource的子PropertySource集合,并替換需要的key-value
*/
for (EnumerableCompositePropertySource target : lst) {
if (target.containsProperty(ENV_KEY_NAME)) {
Collection<PropertySource<?>> sourceSet = target.getSource();
for (PropertySource<?> propertySource : sourceSet) {
if (propertySource instanceof MapPropertySource) {
MapPropertySource targetSource = (MapPropertySource) propertySource;
addOrReplaceProperty(targetSource);
}
}
}
}
}
}
}
/**
* 在此做一些對(duì)application變量的替換
*
* @param targetSource
*/
private void addOrReplaceProperty(MapPropertySource targetSource) {
targetSource.getSource().put(ENV_KEY_NAME,
PasswordUtil.decodePassword(targetSource.getProperty(ENV_KEY_NAME) + ""));
}
}
通過追蹤源碼終于搞定了,好辛苦.
對(duì)于上面的實(shí)現(xiàn),需要將其加到META-INF/spring-factories文件里,如下:
#Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
com.wyp.boot.earthlyfisher.system.EnvPropertiesHandler
- 通過增加
PropertySourceLoader的實(shí)現(xiàn)來實(shí)現(xiàn)該功能
由于Springboot初始化加載文件是通過實(shí)現(xiàn)了PropertySourceLoader的class集來完成的,所以可以通過這種方式來實(shí)現(xiàn).我是這么想的,由于application文件后綴無非是yml或者properties,所以完全可以調(diào)用這兩種文件格式的加載方式來解決,需要做的只是對(duì)特定的key做擴(kuò)展.
public class CustomPropertLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" };
}
@Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
YamlPropertySourceLoader ymlLoader = new YamlPropertySourceLoader();
PropertySource<?> source = (MapPropertySource) ymlLoader.load(name, resource, profile);
String propertyKey = "spring.datasource.password";
if (source instanceof MapPropertySource) {
MapPropertySource target = (MapPropertySource) source;
if (target.containsProperty(propertyKey)) {
String pwd = target.getProperty(propertyKey) + "";
target.getSource().put(propertyKey, pwd);
}
}
return source;
}
}
當(dāng)然此種實(shí)現(xiàn),也需要將其加到META-INF/spring-factories文件里,如下:
#PropertySourceLoader
org.springframework.boot.env.PropertySourceLoader=\
com.wyp.boot.earthlyfisher.system.CustomPropertyHandler
- 重寫特定的使用實(shí)例
比如spring.datasource.password是為了dataSource的使用而設(shè)置的,完全可以自己定義數(shù)據(jù)源實(shí)例,用來替代SpringBoot自動(dòng)配置為你生成的數(shù)據(jù)源。只不過這中方式比較復(fù)雜,得把需要的屬性重新配置一遍,如下:
@Configuration
public class CustomDataSource {
@Value(value = "${spring.datasource.driverClass}")
String driverClassName;
@Value(value = "${spring.datasource.url}")
String url;
@Value(value = "${spring.datasource.username}")
String username;
@Value(value = "${spring.datasource.password}")
String password;
@Bean
@Primary
public DataSource dataSource() {
DataSource dataSource = new DataSource(); // org.apache.tomcat.jdbc.pool.DataSource;
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
- 覆蓋
PropertyPlaceholderConfigurer的實(shí)現(xiàn)
此種方式是在普通的Spring項(xiàng)目中自己處理配置文件的一般方式,但是由于springboot內(nèi)部處理的原因,暫時(shí)還沒有成功,具體的錯(cuò)誤原因是,只要我覆蓋了原生的實(shí)現(xiàn),則通過自定義注解配置屬性時(shí),會(huì)找不到,還需看源碼以進(jìn)一步研究.錯(cuò)誤信息如下:Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.datasource.driver-class-name' in string value "${spring.datasource.driver-class-name}" at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174) ~[spring-core-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-4.3.4.RELEASE.jar:4.3.4.RELEASE]
以上四種方式以第一種方式為佳,因?yàn)槠洳粻砍兜秸5募虞d邏輯,算是在所有參數(shù)加載完后做的一些補(bǔ)充.
Docker化部署
-
編寫Dockerfile
FROM frolvlad/alpine-oraclejdk8:slim VOLUME /tmp ADD earthlyfisher.jar app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 首先需要配置pom文件中Docker相關(guān)插件
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!--springloaded hot deploy-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
<imageName>mytest/springboottest</imageName>
<dockerDirectory>src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
<finalName>earthlyfisher</finalName>
</build>