Introduction To Spring Framework (Presentation - 143 Slides)
Introduction To Spring Framework (Presentation - 143 Slides)
Introduction To Spring Framework (Presentation - 143 Slides)
" # $ %
&' '
( )
*+
*, '
–-
!. '
* +
* /
* ! ! ! '
* . 0 '
1
2
*& !. 2
!
* 2 2
*- 2 2 -3
!
* - 4
* , 2 +
* 3 ' 2 2
* 5 . ! 4 2
4 4
2
-
*- 2 /!
*- ! 2 2
6
* #2 !
* # # 4 !
2
7
*8
* + / 3&+&
!
*
– 9/ # # + :
: ' 9!4 8 +
–& . ! 4 7
–- ' 6$
*; '
– +< =>
–8 +
3 6
!
,
,?: 4 .
* ' 2, ?: 4 .
– 2
–, .
*
–, 2 !
4
*@ !
*@ 2
– 2
A ?
A
A 4
A
A
A
A
B 4
* 4
* 2 /
<beans>
<bean id="exampleBean" class="eg.ExampleBean"/>
<bean id="anotherExample" class="eg.ExampleBeanTwo"/>
</beans>
B 4
*C /
InputStream input = new FileInputStream("beans.xml");
BeanFactory factory = new XmlBeanFactory(input);
ExampleBean eb =
(ExampleBean)factory.getBean("exampleBean");
ExampleBeanTwo eb2 =
(ExampleBeanTwo)factory.getBean("anotherExample");
Can
Canthrow
throwNoSuchBeanDefinitionException
NoSuchBeanDefinitionException
ExampleBean eb =
(ExampleBean)factory.getBean("exampleBean", ExampleBean.class);
Can
Canthrow
throwBeanNotOfRequiredTypeException
BeanNotOfRequiredTypeException
!
*& ! 4 !
package eg;
public class ExampleBean {
private String s;
private int i;
*
–, @ 3 - 4 C8@
*,
–, , : , E !
4
* /
<property name="intProperty"><value>7</value></property>
<property name="doubleProperty"><value>0.25</value></property>
<property name="booleanProperty"><value>true</value></property>
<property name="colorProperty"><value>0,255,0</value></property>
java.awt.Colorisisinitialized
java.awt.Color initializedwith
withRGB
RGBvalues
values
4
* /
<property name="classProperty">
<value>java.lang.Object</value>
</property>
<property name="fileProperty">
<value>/home/ziba/file.txt</value>
</property>
<property name="localeProperty">
<value>pt_BR</value>
</property>
<property name="urlProperty">
<value>http://java.net</value>
</property>
<property name="stringArrayProperty">
<value>foo,bar,baz</value>
</property>
, 4
*: /
DateFormat fmt = new SimpleDateFormat("d/M/yyyy");
CustomDateEditor dateEditor = new CustomDateEditor(fmt, false);
beanFactory.registerCustomEditor(java.util.Date.class, dateEditor);
<property name="date"><value>19/2/2004</value></property>
* /
– 2 4 '
StringTrimmerEditor trimmer = new StringTrimmerEditor(true);
beanFactory.registerCustomEditor(java.lang.String.class, trimmer);
<property name="string2"><null/></property>
.' $ $3
*3 /
<property name="propertiesProperty">
<value>
foo=1
bar=2
baz=3
</value>
</property>
<property name="propertiesProperty">
<props>
<prop key="foo">1</prop>
<prop key="bar">2</prop>
<prop key="baz">3</prop>
</props>
</property>
.' $ $@
*@ /
<property name="listProperty">
<list>
<value>a list element</value>
<ref bean="otherBean"/>
<ref bean="anotherBean"/>
</list>
</property>
.' $ $
* /
<property name="setProperty">
<set>
<value>a set element</value>
<ref bean="otherBean"/>
<ref bean="anotherBean"/>
</set>
</property>
.' $ $;
*; /
<property name="mapProperty">
<map>
<entry key="yup an entry">
<value>just some string</value>
</entry>
<entry key="yup a ref">
<ref bean="otherBean"/>
</entry>
</map>
</property>
,
*, /
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(AnotherBean b1, YetAnotherBean b2, int i) {
this.beanOne = b1;
this.beanTwo = b2;
this.i = i;
}
}
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.properties
jdbc.properties
jdbc.username=sa
jdbc.password=root
3 43 , 2
* 2
InputStream input = new FileInputStream("beans.xml");
XmlBeanFactory factory = new XmlBeanFactory(input);
PropertyPlaceholderConfigurer cfg =
new PropertyPlaceholderConfigurer();
cfg.setProperties(props);
cfg.postProcessBeanFactory(factory);
DataSource ds = (DataSource)factory.getBean("dataSource");
; ' 4
* / !
package eg;
public class MySingleton {
private static MySingleton instance = new MySingleton();
private MySingleton() {}
public static MySingleton getInstance() {
return instance;
}
}
AAFactoryBean
FactoryBeandelegates
delegatesthe
the
bean creation to another class
bean creation to another class
<bean name="mySingleton"
class="...beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod">
<value>eg.MySingleton.getInstance</value>
</property>
</bean>
4 2
*F 2 2 4 2
MySingleton singleton =
(MySingleton)ctx.getBean("mySingleton");
Return
Returnthe
thebean
beancreated
createdby
bythe
thefactory
factory
FactoryBean factory =
(FactoryBean)ctx.getBean("&mySingleton");
Return
Returnthe
thefactory
factory
- ' 2
* ?3 4
*-
– 4 4 G 2
G 4
– 4 G !
4 02 # 1
*: 4
* (
* ?: ! 2
* 4- ? E -
2
- , /
( - , /)
*- 2 !
! !4
*@ 2! 2
*@ 2 /
*= /
* 6H
*-
* '
- , /
* / 4
*, ' /
*
– 4 B - , /
–, 3 B - , /
–B ( !- , /
* /
ApplicationContext ctx =
new FileSystemXmlApplicationContext("c:/beans.xml");
ExampleBean eb = (ExampleBean)ctx.getBean("exampleBean");
- , /
*- , / ! 2 4
2
String[] ctxs = new String[]{"ctx1.xml", "ctx2.xml"};
*, / 4
ApplicationContext parent =
new ClassPathXmlApplicationContext("ctx1.xml");
ApplicationContext ctx =
new FileSystemXmlApplicationContext("ctx2.xml", parent);
8
*- , /
*- , /
–8 8 0 1
A 2 4 G 2 C8@ $ $Ifile:C:/test.datJ
A ' 2 $ $IWEB-INF/test.datJ
A #C8@ $ $Iclasspath:test.datJ
interface Resource {
boolean exists();
boolean isOpen();
String getDescription();
File getFile() throws IOException;
InputStream getInputStream() throws IOException;
}
8
* # 3 4
*, ! 2 8
! 2
* /
<property name="resourceProperty">
<value>example/image.gif</value>
</property>
6H
* 2
*- , /
– ; 0 &!. KL
2 @ 1
Delegated
Delegatedtotoaa“messageSource”
“messageSource”bean
bean
6H
*- , / 2
I J!
–; ; 2
* /
– 2 2 ! M
– - @
' '
–- / - '
– # '
A, /8 2 '
A, /, '
A8 G = '
'
*@ '
public class MyListenerBean implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent e) {
// process event
}
}
* '
public class ExampleBean implements ApplicationContextAware {
ApplicationContext ctx;
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx = ctx;
}
* !
43 3 2
43 3
* / M /
public class MyPostProcessor implements BeanFactoryPostProcessor {
void postProcessBeanFactory(
ConfigurableListableBeanFactory bf) {
bf.registerCustomEditor(java.util.Date.class, dateEditor);
}
* /
public class DebugInterceptor implements MethodInterceptor {
* /
<bean id="gettersAndSettersAdvisor"
class="...aop.support.RegexpMethodPointcutAroundAdvisor">
<property name="interceptor">
<ref local="interceptorBean"/>
</property>
<property name="patterns">
<list>
<value>.*\.get.*</value>
<value>.*\.set.*</value>
</list>
</property>
</bean>
3 /4 4
*( 3 /4 44 ' !.
–5 2 ' !
– /4 !.
– + ' :4 3 /4 ,F@
A /4 2
*, -&3 / 4
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addInterceptor(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface b = (MyBusinessInterface)factory.getProxy();
3 /4 4
*C / 2 !
* ! ! /
<bean id="personTarget" class="eg.PersonImpl">
<property name="name"><value>Tony</value></property>
<property name="age"><value>51</value></property>
</bean>
PersonImplimplements
PersonImpl Personinterface
implementsPerson interface
3 /4 4
* ? '
<bean id="myAdvisor" class="eg.MyAdvisor">
<property name="someProperty"><value>Something</value></property>
</bean>
* /4
<bean id="person" class="...aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>eg.Person</value></property>
*E 3 /4 4 2
!
E - 3 /4,
* !4 !
<bean id="employee1" class="eg.Employee">...</bean>
<bean id="employee2" class="eg.Employee">...</bean>
<bean id="beanNameProxyCreator"
class="...aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames"><value>employee*</value></property>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
- ' - 3 /4,
*- 4 ' /
!
– ' '
– 2 ! !
!4 '
*C 2 4 ' 4
4! !.
* ! # ' !.
- ' - 3 /4,
* /
<bean id="debugInterceptor" class="app.DebugInterceptor"/>
<bean id="getterDebugAdvisor"
class="...aop.support.RegexpMethodPointcutAdvisor">
<constructor-arg>
<ref bean="debugInterceptor"/>
</constructor-arg>
<property name="pattern"><value>.*\.get.*</value></property>
</bean>
This
Thisadvisor
advisorapplies debugInterceptortotoall
appliesdebugInterceptor getmethods
allget methodsofofany
anyclass
class
<bean id="autoProxyCreator"
class="...aop.framework.autoproxy.AdvisorAutoProxyCreator">
<property name="proxyTargetClass"><value>true</value></property>
</bean>
- ' -&3
*; # ' /4
*
–= !
A- 2 /4 !
2
–3
A- 2
' 2 !.
; !
# '
* 2 !
M 4 ?
/** Annotated
Annotatedclass
class
* Normal comments
* @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()
*/
public class PetStoreImpl implements PetStoreFacade, OrderService {
...
}
/** Annotated
Annotatedmethod
method
* Normal comments
* @@org.springframework.transaction.interceptor.RuleBasedTransactionAttribute()
* @@org.springframework.transaction.interceptor.RollbackRuleAttribute(Exception.class)
* @@org.springframework.transaction.interceptor.NoRollbackRuleAttribute("ServletException")
*/
public void echoException(Exception ex) throws Exception {
....
}
# '
* ' 2
–C + , - !
A !
– + 8#6PQ 0+:R 6$Q1
*C
–( -&3
A- ! 24
–; ! 2
A
–D
3
* 4- , / -&3
! 2
*
-3
;
; ;
*,
SimpleMailMessage msg = new SimpleMailMessage();
msg.setFrom("me@mail.org");
msg.setTo("you@mail.org");
msg.setCc(new String[] {"he@mail.org", "she@mail.org"});
msg.setBcc(new String[] {"us@mail.org", "them@mail.org"});
msg.setSubject("my subject");
msg.setText("my text");
;
*: 2
<bean id="mailSender"
class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host"><value>smtp.mail.org</value></property>
<property name="username"><value>joe</value></property>
<property name="password"><value>abc123</value></property>
</bean>
*
MailSender sender = (MailSender) ctx.getBean("mailSender");
sender.send(msg);
* # 2
– +'
A
A
–S 0 M?? $G $ ?1
A
A + !:
A
*
public class MyTask extends TimerTask {
public void run() {
// do something
}
}
Java
Javabean
beanthat
thatwraps
wrapsaascheduled
scheduled
java.util.TimerTask
java.util.TimerTask
<bean id="myTask"
class="...scheduling.timer.ScheduledTimerTask">
<property name="timerTask">
<bean class="eg.MyTask"/>
</property>
<property name="delay"><value>60000</value></property>
<property name="period"><value>1000</value></property>
</bean>
4
*,
Creates
Createsaajava.util.Timer
java.util.Timerobject
object
<bean id="scheduler"
class="...scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list><ref bean="myTask"/></list>
</property>
</bean>
* !
+E:
+
*C +
Properties p = new Properties();
p.setProperty("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
p.setProperty("java.naming.provider.url",
"jnp://localhost:1099");
try {
jndi.bind("Something", something);
Object o = jndi.lookup("Something");
jndi.unbind("Something");
}
catch(NamingException e) {
...
}
+ &!. 4
*C ! 2
<bean id="jndiTemplate"
class="org.springframework.jndi.JndiTemplate">
<constructor-arg>
<props>
<prop key="java.naming.factory.initial">org.jnp.interfaces.NamingCont
<prop key="java.naming.provider.url">jnp://localhost:1099</prop>
</props>
</constructor-arg>
</bean> A FactoryBean delegates the
A FactoryBean delegates the
bean
beancreation
creationtotoanother
anotherclass
class
<bean id="something"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate"><ref bean="jndiTemplate"/></property>
<property name="jndiName"><value>Something</value></property>
</bean>
Object o = ctx.getBean("something");
+: ,
+: , !
*; +: ,
*
2
* 2 /
+!
* / S@ G
* ' 8 / 2
'
* /
DataSource ds = DataSourceUtils.getDataSourceFromJndi("MyDS");
JdbcTemplate jdbc = new JdbcTemplate(ds);
Returns
Returnsan ArrayList(one
anArrayList (oneentry
entryfor
foreach
eachrow)
row)ofofHashMaps
HashMaps
(one
(one entry for each column using the column nameas
entry for each column using the column name asthe
thekey)
key)
+!
*S !
final List employees = new LinkedList();
employees.add(e);
}
employeeslist
employees listwill
willbe
bepopulated
populatedwith Employeeobjects
withEmployee objects
});
+!
*
jdbc.call(new CallableStatementCreator() {
public CallableStatement createCallableStatement(Connection conn)
throws SQLException {
return conn.prepareCall("my query");
}
}, params);
+!
*
BatchPreparedStatementSetter setter =
new BatchPreparedStatementSetter() {
};
* ' 2
/
try {
// do work
}
catch (OptimisticLockingFailureException ex) {
// I'm interested in this
}
: !
*: C
– , 01 : + 01
– , 2E 401
*: ' ; :
–8 ' 4
– !
* , :
–8 4
– !
;
*F !
– !4 ' +-
– ! 4
*@
– # 2 M2 /
+: ,
–
– ! +-
* : 22
*C 2
!
– : 22
22 '
* !
–3
–: ' 0 + ,; 1
!
* ! !4 2
3 2 ;
– 0 : 2 1
– 0 1
– ! 0 1
* : 2
– # 4
*
– E 01
– 8 ! & 401
– 8 ! & 401
* # 2
– + ;
– : ;
– =! ;
– + ;
/
*: 2 + ;
<bean id="dataSource" class="...jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>MyDS</value></property>
</bean>
Data
Datasources
sourcesmust
mustbe
be
configured
configured in the appserver
in the app server
as transactional resources
as transactional resources
<bean id="transactionManager"
class="...transaction.jta.JtaTransactionManager"/>
/
*: 2 : ;
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
...
</bean>
<bean id="transactionManager"
class="...jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
/
*: 2 =! ;
<bean id="sessionFactory"
class="...orm.hibernate.LocalSessionFactoryBean">
...
</bean>
<bean id="transactionManager"
class="...orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
To
Tomake
makeHibernate
Hibernateuse
useJTA
JTAyou
youdon't
don'tneed
needHibernateTransactionManager,
HibernateTransactionManager,
just
justconfigure
configureaaJtaTransactionManager
JtaTransactionManagerand andgive
givetotosessionFactory
sessionFactorydata
data
sources obtained from JNDI
sources obtained from JNDI
*3
*,
PlatformTransactionManager transactionManager =
(PlatformTransactionManager) ctx.getBean("myTransactionManager");
TransactionTemplate transaction =
new TransactionTemplate(transactionManager);
* /
transaction.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus s) {
updateOperation1();
updateOperation2();
}
});
*- 2
– 3 ' 0 1
– @' 0 1
– 8 & 40! 1
– 0 1
- '
* 2
– . 2 2
–E
*
–- ' +-
– # !
A +: ,
A +- +& ;
: '
*E 2
* -&3
* + ,;
–5 24 ! ' 0 2 1
'
: '
* : 22 2 + ,;
– , ! 4 3&+&
– E +- 0 +: , +:& = ! 1
– = ' !
– , ! ! '
– : 2
/
- !
*: 2
* - ! 2
22
– PROPAGATION_NAME,ISOLATION_NAME,readOnly,+Except
ion1,-Exception2
– - 9O9! 2 / !
' 2 /
T 9#9 4 !
* /
– PROPAGATION_MANDATORY,ISOLATION_DEFAULT,
-CreateException,-DuplicateKeyException
: '
*: 2
<bean id="txAttributes"
class="...MatchAlwaysTransactionAttributeSource">
<property name="transactionAttribute">
<value>PROPAGATION_REQUIRED</value>
</property>
</bean> MatchAlwaysTransactionAttributeSource
MatchAlwaysTransactionAttributeSource
applies
appliesthe
thesame
sameattributes
attributestotoall
allmethods
methods
<bean id="txInterceptor"
class="...transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean="myTransactionManager"/>
</property>
<property name="transactionAttributeSource">
<ref bean="txAttributes"/>
</property>
</bean>
: '
*- ' - !
<bean id="txAttributes"
class="...interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<value>
get*=PROPAGATION_REQUIRED,readOnly
find*=PROPAGATION_REQUIRED,readOnly
load*=PROPAGATION_REQUIRED,readOnly
store*=PROPAGATION_REQUIRED
</value>
</property>
</bean> NameMatchTransactionAttributeSourceapplies
NameMatchTransactionAttributeSource applies
specific
specific attributes to methods that match to apattern
attributes to methods that match to a pattern
: '
*- /4 2 !
<bean id="autoProxyCreator"
class="...framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<value>txInterceptor</value>
</property>
<property name="beanNames">
<value>*Dao</value>
</property>
</bean>
: '
*C !
<bean id="autoproxy"
class="...aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
<bean id="txAdvisor"
class="...transaction.interceptor.TransactionAttributeSourceAdvisor"
autowire="constructor">
</bean>
<bean id="txInterceptor"
class="...transaction.interceptor.TransactionInterceptor"
autowire="byType">
</bean>
PlatformTransactionManager
<bean id="txAttributeSource"
class="...transaction.interceptor.AttributesTransactionAttributeSource"
autowire="constructor">
</bean>
<bean id="attributes"
class="...metadata.commons.CommonsAttributes">
</bean>
&8;
&8;
* &8;
– &!. #8 ;
* #
– +:&
–
– =!
=! 2
*: 2 : =!
4
<bean id="dataSource" ...> ... </bean>
HibernateTemplate hibernate =
new HibernateTemplate(sessionFactory);
*@ U
Employee e = (Employee) hibernate.load(Employee.class, "000330");
e.setFirstName("BOB");
hibernate.update(e);
=!
*S '
List employees = hibernate.find("from app.Employee");
return result;
}
});
/
* =! /
: - / 4
*C 4 +: ,
+
+
* !
2 + 4 T
' $$$
*
+
+
*( + '
–- ' @
A 2+E: / +
–- :
A8
*( 4
- @ @
* @
<bean id="myComponent"
class="...ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>myComponent</value>
</property>
<property name="businessInterface">
<value>com.mycom.MyComponent</value>
</property>
</bean> Creates
Createsaaproxy
proxy(the
(thebusiness
businessdelegate)
delegate)that
that
uses
usesaaservice
servicelocator
locatortotoaccess
accessthe
theEJB
EJB
*5 !
– 0 ! 2 + 2 2 1
- 8 @
* 8
<bean id="myComponent"
class="...SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiEnvironment">
<ref bean="myEnvironment"/>
</property>
<property name="jndiName">
<value>myComponent</value>
</property>
<property name="businessInterface">
<value>com.mycom.MyComponent</value>
</property>
</bean>
+
* -!
–@ 4
A + ' ' ! ejb/BeanFactoryPath 2
2 B; @ ! 2 4
2
– $ $? ? 4 ? 4 ? 4! $/
A: 2 ! 2 4 B - , /
*- + 4
2
– 2 ! 4
@
*
* / -!
– ' /
– 4 2 .!8 ' 01
– .!, 01
– / .!- ' 01
.!3 ' 01
* ! .!, 01
@
* /
class MySlsb extends AbstractStatelessSessionBean {
protected void onEjbCreate() throws CreateException {
...
}
* / -! 2
– ' /
– 4 2 .!8 ' 01
– .!, 01
* ! .!, 01
.!- ' 01 .!3 ' 01
* /
class MySfsb extends AbstractStatefulSessionBean {
public void ejbCreate() throws CreateException {
loadBeanFactory();
...
}
public void ejbActivate() {
...
}
public void ejbPassivate() {
...
}
*, / !4
–, /@ @ 0 ' $1
–, /@ ' 0 ' $71
*, ! 4 !2
–C 4 ! 4
( !- , /
* /
Load
Loadroot
rootapplication
applicationcontext
contextfrom
– !$/ /WEB-INF/applicationContext.xml
from
/WEB-INF/applicationContext.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>...web.context.ContextLoaderListener</listener-class>
</listener>
– '
WebApplicationContextUtils.getWebApplicationContext(ServletContext);
( ! ; D,
*
*8 2
–: ' ; D,
#!4#
A M?? $ 2 $ ? ?; D,# #!4#
? #; D,# #!4# $
8
8 /
* - 4! / ! /
*-8 / / !
'
* # 2
– 8;
– +-B#83,
–
–=
8 ' /
* ' ! /
class MyServiceImpl implements MyService {
...
}
* ' /
<bean id="myService-rmi"
class="...remoting.rmi.RmiServiceExporter">
<property name="service"><ref local="myService"/></property>
<property name="serviceInterface">
<value>app.MyService</value>
</property>
<property name="serviceName">
<value>myService</value>
</property>
</bean>
; $$$
8
* 6$6
– +;
– +; B
– ' #! '
– -&3 / + 8#6PQ '
* 6$
– &FE@
– +,-
– 8;
* 6$7)
– +
– 3
8 .
*8 , 3 2 0 ! /1
– 8,3
*D 0 ! /1
–, #'
–- ! !
* 4
–- 4 4 2
A M?? 4$ 2 $ ?
8 2
* !
– M?? $ 2 $ ?
*- ! 0+ ' B 2
$1
– M?? 4 $ $ ? ? ' # $
*; ' 0 2 # 1
– M?? $ $ ? $ $. ' $ 2 $