spring 那點(diǎn)事

Spring核心功能

DI(IOC)

何謂DI(IOC)

DI(依賴注入)是spring的核心功能之一。
Dependency InjectionInversion 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)方式

  1. 構(gòu)造器注入:通過構(gòu)造器注入,能使當(dāng)前實(shí)例作為不可變對(duì)象,并且能確保所有需要的依賴都是非空的.更進(jìn)一步,構(gòu)造器注入返回給客戶代碼的是一個(gè)完全初始化狀態(tài)的對(duì)象.
  2. Setter方法注入:Setter方法注入作為構(gòu)造器注入的補(bǔ)充實(shí)現(xiàn).能注入可選的有默認(rèn)值的依賴.否則,會(huì)隨處校驗(yàn)依賴的非空與否.
  3. 自動(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á)到目的.

主要依賴方式有:

  1. 構(gòu)造器注入.
  2. 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è)問題:

  1. @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@NamedJSR 330 Standard Annotations.s

通過Spring提供的擴(kuò)展方式做處理

  1. 可以通過init-method的方式來實(shí)現(xiàn)初始化注入,還可以通過實(shí)現(xiàn)``InitializingBean`接口來實(shí)現(xiàn),但此種方式對(duì)業(yè)務(wù)代碼有侵入性,少用。
  2. bean加載過程可以通過設(shè)置factory-method的方式設(shè)置工廠方法,來設(shè)置一些靜態(tài)屬性
  3. 調(diào)用getter方法,用工廠Bean PropertyPathFactoryBean
  4. 調(diào)用普通方法(實(shí)例方法或者類方法),用工廠Bean MethodInvokingFactoryBean
  5. 獲取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ā)生一種稱之為注解@Beanlite mode出現(xiàn),這種不會(huì)使用CGLIB代理.所以只要我在@Bean修飾的方法之間不相互編碼調(diào)用,代碼將會(huì)很好的運(yùn)作.

下面是@Beanlite 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的作用域

  1. session
  2. request
  3. prototype
  4. singleton
  5. application

其中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ì)有下面三種情況:

  1. A上加了注解,B上不加,則整體會(huì)受事務(wù)控制.
  2. A上加了注解,B上加,則整體會(huì)受事務(wù)控制.其實(shí)這里調(diào)用和1一樣,在AOP代理時(shí)只給A方法加了環(huán)繞事務(wù)通知.
  3. 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<>();

上述兩端代碼描述的是RoleUser的多對(duì)多關(guān)系.

注解說明

  1. mappedBy

    mappedByinverse的作用是相同的,只不過inverse用在xml文件中,表示的意思是在關(guān)系雙方都有關(guān)系維護(hù)任務(wù)時(shí),以哪一方為主導(dǎo),即沒有被mappedBy修飾的一方維護(hù).即外鍵將儲(chǔ)存在沒有被mappedBy修飾一方.

  2. @Temporal
    @Temporal是將java.sql.*下的Data等轉(zhuǎn)成java.util.*包下的Date.

  3. @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è)值.

  1. create : 每次加載JPA,重新創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),這就是導(dǎo)致數(shù)據(jù)庫表數(shù)據(jù)丟失的原因
  2. create-drop :加載JPA時(shí)創(chuàng)建,退出是刪除表結(jié)構(gòu)
  3. update :加載JPA自動(dòng)更新數(shù)據(jù)庫結(jié)構(gòu)
  4. none/validate :加載JPA時(shí),驗(yàn)證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),當(dāng)然none時(shí)不做任何操作

在本機(jī)開發(fā)調(diào)試初始化數(shù)據(jù)的時(shí)候可以選擇createupdate等。

但是應(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的值建議是nonevalidate。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è)通過

遇到的問題

  1. 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):

    1. @JsonIgnore json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開無限遞歸,序列化和反序列化均忽略,可以用在字段或get(序列化),set(反序列化)方法上
    2. @JsonBackReference json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開無限遞歸,序列化時(shí)忽略,可以用在字段或get(序列化),set(反序列化)方法上,序列化時(shí),相當(dāng)于@JsonIgnore
    3. @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è)DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter 兩個(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-*.xmlspring的配置文件處理.如下:

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
  1. logging.file : 輸出到指定的文件,可以為相對(duì)路徑或者絕對(duì)路徑
  2. 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ù)源配置:

Spring Boot多數(shù)據(jù)源配置與使用

關(guān)于打成war

在一般的項(xiàng)目中,需要將項(xiàng)目打包成war包供發(fā)布到tomcat,所以自己實(shí)現(xiàn)了下:

  1. 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);
    }
    }
    
  2. 更改pom文件打包形式為war
  3. 為了確保內(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ì)加載的值中間修改操作。

覆蓋加載的變量值的幾種方法

  1. 通過增加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
  1. 通過增加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
  1. 重寫特定的使用實(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;
    }
    }
  1. 覆蓋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化部署

  1. 編寫Dockerfile

    FROM frolvlad/alpine-oraclejdk8:slim
    VOLUME /tmp
    ADD earthlyfisher.jar app.jar
    ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
    
  2. 首先需要配置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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評(píng)論 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,492評(píng)論 2 7
  • 什么是Spring Spring是一個(gè)開源的Java EE開發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,771評(píng)論 1 133
  • 在這個(gè)世界上 你可以真正信任和依托的人 可能只有你自己 所以 不要背叛自己的本心 心中無愛便無恨 懷抱一顆感恩的心...
    核桃慈海閱讀 570評(píng)論 0 0

友情鏈接更多精彩內(nèi)容