Spring实战学习笔记

Spring之旅

依赖注入

我们想让骑士去营救少女

public class DemselRescuingKnight implements Knight{

    /*
     * 少女营救骑士类
     */
    private RescueDamselQuest quest;//探秘

    public DemselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }

    //走上探险
    public void embarkOnQuest() {
        quest.embark();
    }
}

这样写会造成耦合度太高,如果让骑士去杀龙或者其他探险就无能为力了,正确的做法是将骑士的任务作为参数传递给骑士,如下面的BraveKnight类

public interface Knight {
    void embarkOnQuest();
}
public class BraveKnight implements Knight{

    private Quest quest;

    public BraveKnight(Quest quest) {
        this.quest = quest;
    }

    public void embarkOnQuest() {
        quest.embark();
    }
}

接着实现具体的探险

public interface Quest {
    void embark();
}
public class SlayDragonQuest implements Quest{

    /*
     * 杀龙探险
     */
    private PrintStream stream;

    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    public void embark() {
        //开始杀龙
        stream.println("Embarking on quest to slay the dragon");
    }
}

接着用一个knight.xml让框架帮我们实现BraveKnight类,并且初始化其任务

<?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">

  <bean id="knight" class="com.knights.BraveKnight">
    <!--注入quest bean-->
    <constructor-arg ref="quest" />
  </bean>

  <!--创建SlayDragonQuest-->
  <bean id="quest" class="com.knights.SlayDragonQuest">
    <constructor-arg value="#{T(System).out}" />
  </bean>

</beans>

开始探险

public class KnightMain {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knight.xml");
        Knight knight = context.getBean(Knight.class);
        //Embarking on quest to slay the dragon
        knight.embarkOnQuest();
        context.close();
    }
}

应用切面

系统由许多不同的组件组成,每个组件除了自身核心的功能外,还承担着额外的职责,如日志,实物管理和安全。借助AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活地应用到系统中,你的核心应用甚至不知道它们的存在。
我们想让诗人在骑士探险前和探险后都赞美一下骑士

public class BraveKnight implements Knight{

    private Quest quest;
    private Minstrel minstrel;

    public BraveKnight(Quest quest, Minstrel minstrel) {
        this.quest = quest;
        this.minstrel = minstrel;
    }

    public void embarkOnQuest() {
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

但是每个骑士类都带一个诗人,让骑士去命令诗人吟诗,这样合适吗?应该让诗人自己做这些事

public class Minstrel {

    /*
     * 吟游诗人
     */
    private PrintStream stream;

    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }

    public void singBeforeQuest() {
        System.out.println("singBeforeQuest");
    }

    public void singAfterQuest() {
        System.out.println("singAfterQuest");
    }
}

更改knight.xml为如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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
       http://www.springframework.org/schema/aop/spring-aop.xsd">

  <bean id="knight" class="com.knights.BraveKnight">
    <!--注入quest bean,构造器注入-->
    <constructor-arg ref="quest" />
  </bean>

  <!--创建SlayDragonQuest-->
  <bean id="quest" class="com.knights.SlayDragonQuest">
    <constructor-arg value="#{T(System).out}" />
  </bean>

  <bean id="minstrel" class="com.knights.Minstrel">
    <constructor-arg value="#{T(System).out}"/>
  </bean>

  <aop:config>
    <aop:aspect ref="minstrel">
      <!--定义切点-->
      <aop:pointcut id="embark" expression="execution(* com.knights.BraveKnight.embarkOnQuest(..))"/>
      <!--声明前置通知-->
      <aop:before pointcut-ref="embark" method="singBeforeQuest"/>
      <!--声明后置通知-->
      <aop:after pointcut-ref="embark" method="singAfterQuest"/>
    </aop:aspect>
  </aop:config>

</beans>

再次运行KnightMain结果如下

public class KnightMain {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knight.xml");
        Knight knight = context.getBean(Knight.class);
        //singBeforeQuest
        //Embarking on quest to slay the dragon
        //singAfterQuest
        knight.embarkOnQuest();
        context.close();
    }
}

装配Bean

自动装配Bean

Spring从两个角度来实现自动化装配
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean.
自动装配(autowiring):Spring自动满足bean之间的依赖

public interface MediaPlayer {
    void play();
}
//这里用Component注解是为了后面测试时注入用的
@Component
public class CDPlayer implements MediaPlayer{

    private CompactDisc cd;

    @Autowired
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

    public void play() {
        cd.play();
    }
}
@Configuration
@ComponentScan
//ComponentScan是启用组件扫描,默认扫描与配置类相同的包,可以用basePackages属性指定包名
public class CDPlayerConfig {
}
public interface CompactDisc {
    /*
     * 光盘抽象类
     */
    void play();
}
@Component
public class SgtPeppers implements CompactDisc{

    private String title = "title";
    private String artist = "artist";

    public void play() {
        System.out.println( artist + " play " + title);
    }
}

用CDPlayerConfig初始化环境来测试

@RunWith(SpringJUnit4ClassRunner.class)
//用来初始化Spring的上下文环境
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {

    @Autowired
    private MediaPlayer player;

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        //自己测试了一下,如果为空会报错
        Assert.assertNotNull(cd);
    }

    @Test
    public void play() {
        //artist play title
        player.play();
    }
}

app-context.xml

<!--开启组件扫描-->
<context:component-scan base-package="com.makenv.autoconfig" />

用app-context.xml初始化环境来测试

@ContextConfiguration(locations = "classpath:app-context.xml")

通过Java代码装配Bean

把上面CDPlayer类和SgtPeppers类上的@Component和@Autowired去掉,不让它们自动装配,CDPlayerConfig的代码修改如下,用CDPlayerConfig类来初始化环境,测试结果符合预期

@Configuration
public class CDPlayerConfig {

    //Bean注解会告诉Spring这个方法会返回一个对象,该对象要注册为Spring应用上下文中的bean.
    //方法体中包含了最终产生bean实例的逻辑,可以用name属性为bean指定名字
    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }

    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
    }
}

通过XML装配Bean

构造注入Bean
构造注入字面量(包括装配集合)
@Autowired和@Component已经去掉

public class BlankDisc implements CompactDisc{

    private String title;
    private String artist;
    //磁道列表
    private List<String> tracks;

    public BlankDisc(String title,String artist,List<String> tracks) {
        this.title = title;
        this.artist = artist;
        this.tracks = tracks;
    }

    public void play() {
        System.out.println("Playing " + title + " by " + artist);
        for (String track : tracks) {
            System.out.println("-Track: " + track);
        }
    }
}

app.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">

    <bean id="compactDisc" class="com.makenv.xmlconfig.BlankDisc">
        <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
        <constructor-arg value="The Beatles" />
        <constructor-arg>
            <list>
                <value>Sgt. Pepper's Lonely Hearts Club Band</value>
                <value>With a Little Help from My Friends</value>
                <value>Lucy in the Sky with Diamonds</value>
            </list>
        </constructor-arg>
    </bean>

    <bean id="cdPlayer" class="com.makenv.xmlconfig.CDPlayer">
        <constructor-arg ref="compactDisc"/>
    </bean>

</beans>

测试可用

@RunWith(SpringJUnit4ClassRunner.class)
//用来初始化Spring的上下文环境
@ContextConfiguration(locations = "classpath:app.xml")
public class CDPlayerTest {

    @Autowired
    private MediaPlayer player;

    @Test
    public void play() {
        //Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles
        //-Track: Sgt. Pepper's Lonely Hearts Club Band
        //-Track: With a Little Help from My Friends
        //-Track: Lucy in the Sky with Diamonds
        player.play();
    }
}

属性注入Bean
属性注入字面量

高级装配

条件化的Bean

处理自动装配的歧义性

有一个Dessert接口,3个类实现了这个接口

public interface Dessert {
    public void showType();
}
public class Cake implements Dessert{
    public void showType() {
        System.out.println("Cake");
    }
}
public class Cookies implements Dessert{
    public void showType() {
        System.out.println("Cookies");
    }
}
public class IceCream implements Dessert{
    public void showType() {
        System.out.println("IceCream");
    }
}

当要自动装配Dessert时,不知道要转配哪个,有如下解决方案
1.标识首选的Bean
用注解:

@Component
@Primary
public class Cake implements Dessert{
    public void showType() {
        System.out.println("Cake");
    }
}

Java配置:

@Configuration
@ComponentScan
public class DessertConfig {

    @Bean
    @Primary
    public Dessert cake() {
        return new Cake();
    }
}

XML配置:

<bean id="cake" class="com.makenv.part1.Cake" primary="true"/>

设置多个首选Bean也会错误,因为Spring无法选择
2.限定自动装配的Bean
2.1基于默认的Bean ID作为限定符,Bean的ID为首字母变为小写的类名,类名变更则会失效

@Qualifier("cake")
@Autowired
Dessert dessert;

2.2创建自定义的限定符
在Bean声明上添加@Qualifier注解

@Component
@Qualifier("soft")
public class Cake implements Dessert{
    public void showType() {
        System.out.println("Cake");
    }
}

即可这样使用

@Qualifier("soft")
@Autowired
Dessert dessert;

Java显示配置时

@Bean
@Qualifier("soft")
public Dessert cake() {
    return new Cake();
}

2.3使用自定义的限定符注解
可以通过多个注解来确定Bean,如通过delicious和soft确定为cake,通过delicious和cold确定为icecream,自定义这些注解,就是名称不一样

/*
 * CONSTRUCTOR 用于描述构造器
 * FIELD 用于描述域
 * METHOD 用于描述方法
 * TYPE 用于描述类,接口(包括注解类型)或enum声明
 * RUNTIME 在运行时有效(即运行时保留)
 */
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
         ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}
@Component
@Delicious
@Soft
public class Cake implements Dessert
@Component
@Delicious
@Cold
public class IceCream implements Dessert
@Delicious
@Soft
@Autowired
Dessert dessert;

这样依旧能能过两个不同的注解唯一确定一个Bean

Bean的作用域

这里写图片描述

运行时值注入

面向切面的Spring

使用注解创建切面

这里写图片描述

public interface Performance {
    public void perform();
}
@Component
public class PerformanceImpl implements Performance{
    public void perform() {
        System.out.println("表演开始");
    }
}
@Aspect
public class Audience {

    /*
     * 定义观众切面
     */
    @Pointcut("execution(** com.makenv.Performance.perform(..))")
    public void performance() {}

    //上面定义了performance就可以简写,否则得写成这样
    //@Before("execution(** com.makenv.Performance.perform(..))")
    @Before("performance()")
    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
    }

    @AfterReturning("performance()")
    public void applause() {
        System.out.println("CLAP CALP!!");
    }

    @AfterThrowing("performance()")
    public void demandRefund() {
        System.out.println("Demanding a refund");
    }

}

配置切面,并且注册Bean

@Configuration
//启用AspectJ自动代理,不然即便使用了AspectJ注解,但它并不会被视为切面
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {

    @Bean
    public Audience audience() {
        return new Audience();
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
//用来初始化Spring的上下文环境
@ContextConfiguration(classes = ConcertConfig.class)
public class PerformTest {

    @Autowired
    Performance performance;

    @Test
    public void play() {
        //Silencing cell phones
        //表演开始
        //CLAP CALP!!
        performance.perform();
    }
}

可以将上面的观众切面的方法写成环绕通知,执行效果一样

@Aspect
public class Audience2 {

    @Pointcut("execution(** com.makenv.Performance.perform(..))")
    public void performance() {}

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint jp) {
        try {
            System.out.println("Silencing cell phones2");
            jp.proceed();
            System.out.println("CLAP CALP!!2");
        } catch (Throwable e) {
            System.out.println("Demanding a refund2");
        }
    }
}

处理通知中的参数

public interface CompactDisc {
    void playTrack(int index);
}
public class BlankDisc implements CompactDisc {

    private String title;
    private String artist;
    //磁道列表
    private List<String> tracks;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public void setTracks(List<String> tracks) {
        this.tracks = tracks;
    }

    public void playTrack(int index) {
        String str = tracks.get(index);
        System.out.println(str);
    }
}
@Aspect
public class TrackCounter {

    /*
     * 轨道计数类
     */
    private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();

    @Pointcut("execution(* com.makenv.part2.CompactDisc.playTrack(int)) && args(trackNumber)")
    public void trackPlayed(int trackNumber) {}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber) {
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber) {
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {

    @Bean
    public CompactDisc blackDisc() {
        BlankDisc cd = new BlankDisc();
        cd.setTitle("title");
        cd.setArtist("artist");
        List<String> tracks = new ArrayList<String>();
        tracks.add("A");
        tracks.add("B");
        tracks.add("C");
        cd.setTracks(tracks);
        return cd;
    }

    @Bean
    public TrackCounter trackCounter() {
        return new TrackCounter();
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TrackCounterConfig.class)
public class TrackCounterTest {

    @Autowired
    private CompactDisc cd;

    @Autowired
    private TrackCounter counter;

    //测试CompactDisc不为空
    @Test
    public void test() {
        Assert.assertNotNull(cd);
    }

    @Test
    public void testTrackCounter() {

        cd.playTrack(1);
        cd.playTrack(2);
        cd.playTrack(2);
        //BCC12(一行显示一个)
        System.out.println(counter.getPlayCount(1));
        System.out.println(counter.getPlayCount(2));
        //测试条为绿色,测试通过
        Assert.assertEquals(1,counter.getPlayCount(1));
        Assert.assertEquals(2,counter.getPlayCount(2));
    }
}

在XML中声明切面

这里写图片描述

public interface Performance {
    public void perform();
}
public class PerformanceImpl implements Performance{
    public void perform() {
        System.out.println("表演开始");
    }
}
public class Audience {

    public void silenceCellPhones() {
        System.out.println("Silencing cell phones");
    }

    public void applause() {
        System.out.println("CLAP CALP!!");
    }

    public void demandRefund() {
        System.out.println("Demanding a refund");
    }

}

config.xml的形式如下

<bean id="audience" class="com.makenv.part3.Audience"/>
<bean class="com.makenv.part3.PerformanceImpl"/>
<aop:config>
    <aop:aspect ref="audience">
        <aop:pointcut id="performance" expression="execution(** com.makenv.part3.Performance.perform(..))"/>
        <!--如果不提前定义pointcut,每次都得这样写-->
        <aop:before method="silenceCellPhones" pointcut="execution(** com.makenv.part3.Performance.perform(..)))"/>
        <!--以下是简写形式-->
        <!--<aop:before method="silenceCellPhones" pointcut-ref="performance"/>-->
        <aop:after-returning method="applause" pointcut-ref="performance"/>
        <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
    </aop:aspect>
</aop:config>

测试程序

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:config.xml")
public class PerformTest {

    @Autowired
    Performance performance;

    @Test
    public void play() {
        //Silencing cell phones
        //表演开始
        //CLAP CALP!!
        performance.perform();
    }
}

可以将上面的形式声明为环绕通知

public class Audience2 {

    public void watchPerformance(ProceedingJoinPoint jp) {
        try {
            System.out.println("Silencing cell phones3");
            jp.proceed();
            System.out.println("CLAP CALP!!3");
        } catch (Throwable e) {
            System.out.println("Demanding a refund3");
        }
    }

}

config2.xml

    <bean id="audience" class="com.makenv.part3.Audience2"/>
    <bean class="com.makenv.part3.PerformanceImpl"/>
    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="performance" expression="execution(** com.makenv.part3.Performance.perform(..))"/>
            <aop:around method="watchPerformance" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>

将初始环境换为config2.xml,测试正确
为通知传递参数

public interface CompactDisc {
    void playTrack(int index);
}
public class BlankDisc implements CompactDisc {

    private String title;
    private String artist;
    //磁道列表
    private List<String> tracks;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public void setTracks(List<String> tracks) {
        this.tracks = tracks;
    }

    public void playTrack(int index) {
        String str = tracks.get(index);
        System.out.println(str);
    }
}
public class TrackCounter {

    private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();

    public void countTrack(int trackNumber) {
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber, currentCount + 1);
    }

    public int getPlayCount(int trackNumber) {
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}

app.xml

<bean id="trackCounter" class="com.makenv.part4.TrackCounter"/>
<bean id="cd" class="com.makenv.part4.BlankDisc">
    <property name="title" value="title"/>
    <property name="artist" value="artist"/>
    <property name="tracks">
        <list>
            <value>A</value>
            <value>B</value>
            <value>C</value>
        </list>
    </property>
</bean>
<aop:config>
    <aop:aspect ref="trackCounter">
        <aop:pointcut id="trackPlayed" expression="execution( * com.makenv.part4.CompactDisc.playTrack(int)) and args(trackNumber)"/>
        <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
    </aop:aspect>
</aop:config>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:app.xml")
public class TrackCounterTest {

    @Autowired
    private CompactDisc cd;

    @Autowired
    private TrackCounter counter;

    @Test
    public void testTrackCounter() {

        cd.playTrack(1);
        cd.playTrack(1);
        cd.playTrack(2);
        //BBC21(一行显示一个)
        System.out.println(counter.getPlayCount(1));
        System.out.println(counter.getPlayCount(2));
        //测试条为绿色,测试通过
        Assert.assertEquals(2,counter.getPlayCount(1));
        Assert.assertEquals(1,counter.getPlayCount(2));
    }
}

构建Spring Web应用程序

请求方法可以出现的参数类型(部分)请求方法中可以返回的类型 (部分)
javax.servlet.ServletRequestModelAndView
javax.servlet.http.HttpServletRequestModel
javax.servlet.ServletResponseView
javax.servlet.http.HttpServletResopnse代表逻辑视图名的String
javax.servlet.http.HttpSessionvoid

配置静态资源处理的原因:如果将DispatcherServlet请求映射配置为”/”,则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。配置静态资源处理后,Spring框架能够捕获所有URL的请求,同时又将静态资源的请求转由Web容器处理,这样就可以将DispatcherServlet的请求映射配置为”/”

编写基本控制器

@Controller()
@RequestMapping("/index")
public class HomeController {

    @RequestMapping(value = "/index1", method = RequestMethod.GET)
    public ModelAndView home1() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("home1");
        //重定向到home2页面
        //modelAndView.setViewName("redirect:/index/index2");
        //转发到home2页面
        //modelAndView.setViewName("forward:/index/index2");
        return modelAndView;
    }

    @RequestMapping(value = "/index2", method = RequestMethod.GET)
    public String home2() {
        //重定向到home1页面
        //return "redirect:/index/index1";
        //转发到home1页面
        //return "forward:/index/index1";
        return "home2";
    }

}

接受请求的输入

SpringMVC允许多种方式将客户端中的数据传送到控制器的处理方法中,包括

  1. 查询参数
  2. 表单参数
  3. 路径变量
@Controller
@RequestMapping("/spittles")
public class SpittleController {

    @Autowired
    SpittleServiceImpl spittleService;

    @RequestMapping(method = RequestMethod.GET)
    public String selectSpittles(Model model) {
        model.addAttribute(spittleService.selectSpittles());
        return "spittles";
    }

    //访问如下形式的url,spittles/show?spittleId=2
    //RequestParam没有提供value时默认与参数名相同
    @RequestMapping(value = "/show", method = RequestMethod.GET)
    public String showSpittle(@RequestParam int spittleId, Model model) {
        Spittle spittle = new Spittle();
        spittle = spittleService.selectOne(spittleId);
        model.addAttribute(spittleService.selectOne(spittleId));
        return "spittle";
    }

    //没有提供参数时,指定默认值
    @RequestMapping(value = "/show2", method = RequestMethod.GET)
    public String showSpittle2(@RequestParam(value = "spittleId", defaultValue = "1") int spId, Model model) {
        model.addAttribute(spittleService.selectOne(spId));
        return "spittle";
    }

    //使用占位符时,RequestMapping的value值需要和PathVariable的value值一样
    //访问如下形式的url,/spittles/1,PathVariable没有提供value时默认与参数名相同
    @RequestMapping(value = "/{spittleId}", method = RequestMethod.GET)
    public String showSpittle3(@PathVariable("spittleId") int spittleId, Model model) {
        model.addAttribute(spittleService.selectOne(spittleId));
        return "spittle";
    }

}

检验表单

这里写图片描述

渲染Web视图

spring表单绑定标签库
这里写图片描述

参考博客

Spring MVC静态资源处理
[1]http://www.cnblogs.com/fangqi/archive/2012/10/28/2743108.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java识堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值