1. 程式人生 > 實用技巧 >3.spring(三)

3.spring(三)

使用註解實現自動裝配

  • JDK1.5開始可以支援註解,spring2.5開始支援註解
  • 使用註解須知
    • 匯入約束
    • 配置註解支援
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        //約束
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

//註解配置
<context:annotation-config/>

</beans>

使用上面一個人有兩隻寵物的例子

@Data
public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

    <bean id="cat1" class="com.cdl.pojo.Cat"></bean>
    <bean id="dog2" class="com.cdl.pojo.Dog"></bean>
    <bean id="people" class="com.cdl.pojo.People"></bean>

</beans>

@Autowired
直接寫在屬性上面即可,也可以在set方式上使用
使用@Autowired ,我們可以不用寫set方法,前提是你這個自動裝配的屬性在IOC容器中存在,且符合ByType

科普:
@Nullable
欄位標記這個,說明這個可以為null
@Autowired(required=true)
如果顯示定義了Autowired屬性為false,說明這個物件可以為null,否者不允許為空

@Data
public class People {
    @Autowired
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog222")
    private Dog dog;
    private String name;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

    <bean id="cat1" class="com.cdl.pojo.Cat"></bean>
    <bean id="dog2" class="com.cdl.pojo.Dog"></bean>
    <bean id="dog222" class="com.cdl.pojo.Dog"></bean>
    <bean id="people" class="com.cdl.pojo.People"></bean>

</beans>

如果@Autowired自動裝配環境比較複雜,自動裝配無法通過一個註解@Autowired完成的時候,使用 @Qualifier(value = xxx)去配合@Autowired使用,制定一個唯一的Bean物件注入

@Resource -- java的註解,既可以通過名字也可以通過型別裝配

  • @Resource
  • @Resource(name="cat2")

小結:

  • @Resource和@Autowired都是用來自動裝配的,都可以放在屬性的欄位上
  • @Autowired通過ByType方式實現
  • @Resource預設通過ByName,如果找不到名字,就是用ByType

Spring使用註解開發

  • 在spring4,要使用註解開發,必須保證aop的包匯入(spring-webMvc包預設匯入了aop的包)!
  • 與之前一樣,在使用註解開發之前,使用註解需匯入context約束,增加註解開發支援
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

<!--    掃描包下面的註解-->
    <context:component-scan base-package="com.cdl"/>

<!--    配置支援-->
    <context:annotation-config/>

</beans>

實體類

@Data
@Component //等價於在spring中註冊一個bean,將該類交給spring管理,相當於配置文案的<bean/>標籤
@ComponentScan("prototype") //作用域 : 單例 ,原型
public class User {

    @Value("陳丹龍") //給bean的屬性賦值,相當於bean標籤裡面的property標籤
    private String name;
}

dao層

@Repository
public class UserDao {
}

service層

@Service
public class UserService {
}

註解:

  • @compoent元件,放在類上,說明這個類被spring管理了,就是bean
    • dao層:@Repository
    • service層:@service
    • controller層:@controller
    • 這三個註解的功能與@compoent一樣的,只是在不同的層用不一樣的註解要好一點
  • @Value("cdl") 給bean的屬性賦值,相當於bean標籤裡面的property標籤
  • @ComponentScan("prototype") 作用域,值為單例或原型

小結:

  • xml與註解:
    • xml更加萬能,適用於任何場合,維護更加簡單,註解,不是自己的類使用不了,維護相對複雜
  • xml與註解最佳實踐
    • xml用來管理bean
    • 註解用來屬性的注入
    • 我們再用的過程中,只需要注意一個問題,就是在配置檔案裡,必須讓註解生效

使用java的方式配置spring(javaConfig)

  • 現在完全不需要xml配置來做了,全權交給java來做
  • JavaConfig是spring的子專案,在spring4之後,javaConfig的方式成為了一個核心功能

實體類

@Data
@Component
public class User {

    @Value("陳丹龍")
    public String name;
}

配置類

@Configuration //把這個類新增到spring容器中,並表示這是一個配置類
@ComponentScan("com.cdl")  //掃描包
@Import(Config2.class) //引入一個配置類
public class Config {

    //@bean,就相當於bean標籤,註冊一個bean
    //方法名,就相當於id的值
    //這個方法的返回值就相當於bean中的class屬性屬性
    @Bean
    public User getUser(){
        return new User();
    }
}
@Configuration
public class Config2 {
}

測試類

public class Test07 {
    public static void main(String[] args) {
        //若完全使用配置類的方式去做,這裡就需要使用AnnotationConfigApplicationContext上下文來獲取容器,通過配置類的class來載入
        ApplicationContext config = new AnnotationConfigApplicationContext(Config.class);
        User getUser = (User) config.getBean("getUser");
        System.out.println(getUser);
    }
}

這種純java的方式在springBoot中隨處可見

什麼叫spring的上下文?
應用上下文即是Spring容器抽象的一種實現;而我們常見的ApplicationContext本質上說就是一個維護Bean定義以及物件之間協作關係的高階介面。
spring的核心那就是容器,類似工廠的地方,應用程式中那麼多物件的產生銷燬,肯定需要一個地方來專門處理--容器,有一個很大家很熟悉的容器,tomcat,它是servlet的web容器,容器負責了物件整個的生命週期--------建立、裝配、銷燬,有一個專業的術語來形容spring容器----IOC容器,IOC是指我們在開發的過程中不用管物件的建立這些,都交給容器去處理,等於說把控制權交給了容器(控制反轉),這裡要說明一下,IOC不是spring專有的,還有很多通過IOC容器的框架。
光有spring容器也沒什麼用,容器說到底只是一個管理物件的空間,就像一個沒有圖紙的工廠,不知道怎麼生產產品,這就涉及到了spring應用上下文,說的簡單點就是容器的物件,是對spring容器抽象的實現,我們常見的ApplicationContext本質上來說是一種維護Bean的定義和物件之間協作關係的高階介面,

代理模式 靜態代理

  • 為什麼要學習代理模式
    • 因為這就是springAOP的底層
  • 代理模式的分類
    • 靜態代理
    • 動態代理
  • 靜態代理
    • 角色分析
      • 抽象角色:一般使用介面或抽象類來解決
      • 真實角色:被代理的角色
      • 代理角色:代理真實角色,代理真實角色後,一般會做一些附屬操作
      • 客戶:訪問代理物件的人

抽象角色

//租房
public interface Rent {
     public void rent();
}

真實角色

//房東
public class FangDong implements Rent{

    @Override
    public void rent() {
        System.out.println("房東要出租房子!");
    }
}

代理角色

//中介
public class Proxy implements Rent{

    private FangDong fangDong;

    public Proxy(FangDong fangDong) {
        this.fangDong = fangDong;
    }

    public Proxy(){
    }

    @Override
    public void rent() {
        fangDong.rent();
        seeHost();
        qianHeTong();
    }

    public void seeHost(){
        System.out.println("看房子");
    }

    public void qianHeTong(){
        System.out.println("籤合同");
    }
}

客戶

//我要租房子
public class MySelf {
    public static void main(String[] args) {
        //房東要出租房子
        FangDong fangDong = new FangDong();

        //代理,中介幫房東出租房子,但是一般會做一些附屬操作
        Proxy proxy = new Proxy(fangDong);

        //中介直接給你租房子
        proxy.rent();
    }
}

代理模式的好處:

  • 可以使更加真實角色的操作更加純粹,不用去關注公共業務
  • 公共業務就交給了代理角色,實現了業務的分工
  • 公共業務發生擴充套件的時候,方便集中管理
    缺點
  • 一個真實的角色就會產生一個代理角色,程式碼量翻倍,開發效率低

靜態代理在理解

介面

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

實現類

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加");
    }

    @Override
    public void delete() {
        System.out.println("刪除");
    }

    @Override
    public void update() {
        System.out.println("修改");
    }

    @Override
    public void select() {
        System.out.println("查詢");
    }
}

代理類

/**
 * 利用代理模式,增加一項列印日誌的功能,
 * 不需要改動原來的程式碼,即可實現功能,實現橫向開發
 * */
public class Proxy implements UserService{

    private UserServiceImpl userService;

    public UserServiceImpl getUserService() {
        return userService;
    }

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        msg();
        userService.add();
    }

    @Override
    public void delete() {
        msg();
        userService.delete();
    }

    @Override
    public void update() {
        msg();
        userService.update();
    }

    @Override
    public void select() {
        msg();
        userService.select();
    }

    public void msg(){
        System.out.println("列印日誌");
    }
}

主方法

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        Proxy proxy = new Proxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}

這樣既可不改變原來的程式碼,改變原來的程式碼,在公司是大忌,最大優點就是不改變原來的程式碼,但是每次代理一個角色,就需要去寫一個實現類,太麻煩

動態代理詳解 底層:反射

  • 動態代理和靜態代理的角色是一樣的
  • 動態代理類是動態生成的,不是我們直接寫好的
  • 動態代理類分為兩大類
    • 基於介面的動態代理:JDK動態代理【使用這個例子學習】
    • 基於類的動態代理:cglib動態代理
    • 另外還可以通過java的位元組碼生成:Javassist

JDK動態代理

  • 需要了解兩個類:Proxy(代理類) InvocationHandler(呼叫處理程式)
    • InvocationHandler
      • 是一個介面,java.lang.reflect反射包下,他是一個由代理例項的呼叫處理程式實現介面,每一個代理例項都有一個關聯的呼叫處理程式,當在代理例項上呼叫方法時,方法呼叫將被編碼並分派到其呼叫處理程式的invoke方法
      • 其下只有一個invoke方法,處理代理例項上的方法,呼叫並返回結果
      • invoke方法的三個引數:
        • proxy 需要代理的物件
        • method 物件的方法
        • args 方法引數
    • proxy類
      • 也是反射包下(java.lang.reflect)
      • proxy提供了建立動態代理類和例項的靜態方法,他也是這些方法建立的動態類的超類(父類)

使用上面靜態代理的例子

//使用這個類自動生成代理類
public class ProxyInvocationHandler implements InvocationHandler {

    //需要代理的介面
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成代理類
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    //處理代理例項,並返回一個結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    //若要在代理類加入一個什麼方法,即直接將該方法載入處理
    public void log(String args){
        System.out.println("執行了"+args+"方法");
    }
}

代理多個類,只要實現了同一個介面

public class Client {
    public static void main(String[] args) {
        //真實角色
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceImpl02 userServiceImpl02 = new UserServiceImpl02();
        //代理角色,不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //設定要代理的物件
        pih.setTarget(userService);
        pih.setTarget(userServiceImpl02);
        //動態生成代理類
        UserService proxy = (UserService) pih.getProxy();
        proxy.add();
        proxy.select();
    }
}

作用:

  • proxy:生成動態代理例項
  • InvocationHandler:呼叫處理程式,並返回一個結果
    動態代理的優點:一個動態代理類可以代理多個類,只要這些類實現的是同一個介面

AOP

  • 什麼是AOP?(Aspect Oriented Proramming)
    • AOP,意味為著面向切面程式設計,通過預編譯和執行期動態代理,實現程式功能的統一維護的一種技術,AOP是OOP的延續,是軟體開發中的一個熱點,也是spring框架中的一個重要內容,是函數語言程式設計的一種衍生泛型,利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯的耦合度降低,提高程式的可用性,同事提高開發效率
  • AOP在spring中的作用
    • 提供宣告式事務
    • 允許使用者自定義切面
  • AOP常用術語
    • 橫切關注點:跨越應用程式的多個模組的方法和功能,即使與我們業務邏輯無關,但是也是我們需要關注的部分,就是橫切關注點,如日誌,安全,快取,事務等:
    • 切面(Aspect):橫切關注點被模組化的一個物件,即一個類
    • 通知(Advice):切面必須完成的一個工作,即類中的一個方法
    • 目標(target):被通知物件
    • 代理(proxy):向目標物件應用通知之後,插入的物件
    • 切入點(PointCut):切面通知執行的"地點"的定義
    • 連線點(jointPoint):與切入點匹配的執行點
  • Spring中通過advice定義橫切邏輯,spring支援五種型別的advice
    • 前置通知
    • 後置通知
    • 環繞通知:方法前後
    • 異常丟擲通知:方法丟擲異常
    • 引介通知:類中增加的新的方法,屬性
  • 即使用AOP在不改變原來的程式碼,增加新功能
<!--使用AOP,需要匯入一個織入包-->
  <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>

方式一:使用sping的api介面

public interface UserService {
    void add();
    void select();
    void delete();
    void update();
}
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加");
    }

    @Override
    public void select() {
        System.out.println("查詢");
    }

    @Override
    public void delete() {
        System.out.println("刪除");
    }

    @Override
    public void update() {
        System.out.println("修改");
    }
}
public class Log implements MethodBeforeAdvice {

    /**
     * method:要執行的目標引數的方法
     * args:引數
     * target:目標物件
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "執行了" + method.getName() + "方法");
    }
}
public class AfterLog implements AfterReturningAdvice {

    //Object o:方法執行後的返回值
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println(method.getName()+"方法的返回結果為"+ o);
    }
}
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    註冊bean-->
    <bean id="userService" class="com.cdl.service.UserServiceImpl"></bean>
    <bean id="log" class="com.cdl.service.Log"></bean>
    <bean id="afterLog" class="com.cdl.service.AfterLog"></bean>


<!-- 方式一:使用原生spring-api介面   -->

<!--    配置AOP,需要匯入約束-->
    <aop:config>
<!--        配置切入點,expression表示式,execution需要執行的位置,目標物件UserServiceImpl下的所有方法及方法的引數,
可以配置多個切入點-->
        <aop:pointcut id="pointCut" expression="execution(* com.cdl.service.UserServiceImpl.*(..))"/>
<!--        執行環繞增強-->
<!--        意思就是把LOG這個方法切入到哪個方法上面-->
        <aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
    </aop:config>
</beans>
public class Test09 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        //動態代理的是介面
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

方式二:使用自定義類實現aop[主要是切面定義]

public class DiyPointCut {
    public void before(){
        System.out.println("前置通知");
    }

    public void after(){
        System.out.println("後置通知");
    }
}
<!--    自定義類-->
    <bean id="diyPointCut" class="com.cdl.diy.DiyPointCut">    </bean>
        <aop:config>
<!--            自定義切面,ref是需要引用的類-->
            <aop:aspect ref="diyPointCut">
<!--                切入點-->
                <aop:pointcut id="pointCut" expression="execution(* com.cdl.service.UserServiceImpl.*(..))"/>
<!--                通知-->
                <aop:before method="before" pointcut-ref="pointCut"></aop:before>
                <aop:after method="after" pointcut-ref="pointCut"></aop:after>

            </aop:aspect>
        </aop:config>

方式三:使用註解實現

/**
 * 方式三:使用註解方式實現AOP
 * @Aspect:表示將該類配置為切面
 */
@Aspect
public class AnnotationPointCut {

    @Before("execution(* com.cdl.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("------方法執行前--------");
    }

    @After("execution(* com.cdl.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("------方法執行後--------");
    }

    //在環繞增強中,我們可以傳入一個引數,代表我們要獲取資料切入的點
    @Around("execution(* com.cdl.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint pt) throws Throwable {
        System.out.println("------環繞前--------");
        Object proceed = pt.proceed();
        System.out.println("------環繞後--------");
    }
}
<!--    方式三:註解-->
    <bean id="annotationPoint" class="com.cdl.diy.AnnotationPointCut"></bean>
<!--開啟註解支援-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

宣告式事務

  • 宣告式事務

在宣告式事務中,要配置一個切面,其中就用到了Propagation,其中,Propagation表示打算對這些方法怎麼使用事務,是用還是不用,Propagation有七種屬性

  • REOUIRED:支援當前事務,若當前沒有事務,就新建一個事務
  • SUPPORTS:支援當前事務,若當前沒有事務,就以非事務的方式執行
  • MANDATORY:支援當前事務,若當前沒有事務,就丟擲異常,
  • REQUIRES_NEW:新建事務,若當前存在事務,就把當前事務掛起
  • NOT_SUPPORTED:以非事務的方式執行操作,若當前存在事務,把當前事務掛起
  • NEVER:以非事務的方式執行,若當前存在事務,則丟擲異常
  • NESTED:支援當前事務,若當前存在事務,則執行一個巢狀事務,若當前沒有事務,就新建一個事務
  • 程式設計式事務