笔记内容均来自 尚硅谷 佟刚老师的视频,例子是在学习中照着敲的
https://blog.csdn.net/u011870547/article/details/50475831
相关视频及源码
当你年老时,你是否常常想,要是年轻时多努力下该好。
以前一直有个疑惑,为什么我创建的controller中注入的service类有时候是代理类,有时候是普通javabean,当时能力不够,现在已经有了点经验就大胆跟了跟源码,看看到底咋回事。
首先看看问题现象:
a2:service是代理类,并且是jdk 动态代理
b:serivce不是代理类,而是普通类
我对service类进行了以下的测试:(前提开启事务注解<tx:annotation-driven/>)
1)service方法添加@Transactional注解或者加入其它的aop拦截配置,没有实现任何接口。 对应问题现状 a1
2)service方法添加@Transactional注解或者加入其它的aop拦截配置,实现了接口。 对应问题现状a2
3)serice方法没有添加@Transactional注解或者其它的aop拦截配置。 对应问题现状b
看来出现这种问题的原因就是spring的问题,因为这个类是它创建的,这就需要我们来看下spring创建bean的代码,由于spring太庞大了
我们只看最关键的部分,在创建bean是都会调用getBean()方法,
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
return createBean(beanName, mbd, args);
}
经过不断的流转会进入AbstractAutowireCapableBeanFactory的createBean方法
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
}
然后调用doCreateBean方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}return exposedObject;
}
然后进入核心的createBeanInstance方法,省去了不相关方法
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}
然后调用instantiateBean进行bea的实例化
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
final BeanFactory parent = this;
if (System.getSecurityManager() != null) {
beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
getInstantiationStrategy().instantiate(mbd, beanName, parent),
getAccessControlContext());
}
else {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
}
}
实例化时会调用SimpleInstantiationStrategy的instantiate方法
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
通过上面的代码分析,如果Bean定义中没有方法覆盖,就使用JDK的反射机制进行实例化,否则,使用CGLIB进行实例化。
1、通过CGLIB的方式instantiateWithMethodInjection(bd, beanName, owner)
2、或者java的反射方式BeanUtils.instantiateClass(constructorToUse)
实例化一个bean
这是时候都是一个纯洁无瑕的javabean,那每个bean的额外加工,例如为某个bean添加事务支持,添加aop配置,还有就是将springmvc的controller进行url和handler的映射,等等这些都是在spring的扩展点完成的,回到
上面的doCreateBean方法,执行完实例化bean后执行
1、populateBean(beanName, mbd, instanceWrapper);
其中的populateBean是为了给生成的bean装配属性,
2、initializeBean(beanName, exposedObject, mbd);
这不是我们这次讨论的重点,关键是initializebean方法 ,为了配置bean的拓展方法 aop 事务等
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
这个方法就是对生成的bean进行一些扩展处理,主要就是这个方法会调用我们自定义的扩展点
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
可以看到这里是获取所有的beanProcessor,调用postProcessAfterInitialization方法,我们要关注是的一个叫InfrastructureAdvisorAutoProxyCreator的扩展类。
/**
* Auto-proxy creator that considers infrastructure Advisor beans only,
* ignoring any application-defined Advisors.
*
* @author Juergen Hoeller
* @since 2.0.7
*/
@SuppressWarnings("serial")
public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {}
看下这个类的注释可以发现这个类是为配置了aop配置(包括注解和xml配置两种方式)的类,生成代理类。
核心方法是下面这个方法wrapIfNecessary方法。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
下面解析下这个函数
首先看下getAdvicesAndAdvisorsForBean这个方法:名字很明显用来获取当前bean的advisor和adices的,这些都是生成代理类时需要的信息。
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
然后调用findEligibleAdvisors,获取配置的advisor信息
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
来看下findCandidateAdvisors方法,最终调用BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans
public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
synchronized (this) {
advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the auto-proxy creator apply to them!
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
}
if (advisorNames.length == 0) {
return new LinkedList<>();
}
List<Advisor> advisors = new LinkedList<>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
if (this.beanFactory.isCurrentlyInCreation(name)) {
}
else {
try {
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
throw ex;
}
}
}
}
return advisors;
}
1)首先获取spring管理的Advisor类型的类名称。
2)通过beanFactory获取该bean对应的实体类,并装入advisors。
生成的这个advisor可是相当复杂,这里我们以事务advisor为例说明
可以看到这个advisor包含了advice(aop中的通知),pointcut(aop中的切入点),

advice是TransactionInterceptor,这个通知是用来管理spring的事务的可以看到包含事务的管理器等管理事务的属性,具体的方法见TransactionAspectSupport.invokeWithinTransaction
pointcut是TransactionAttributeSourcePointcut,
public boolean matches(Method method, @Nullable Class<?> targetClass) {
if (targetClass != null && TransactionalProxy.class.isAssignableFrom(targetClass)) {
return false;
}
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
这个是pointcut的核心方法,用来匹配某个类是否符合事务管理的aop拦截要求。
ok,回到之前的wrapIfNecessary方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
我们之前分析道getAdvicesAndAdvisorsForBean方法,可以看到如果得到的结果是DO_NOT_PROXY,就会将这个bean直接返回,
如果不是DO_NOT_PROXY,(其实DO_NOT_PROXY就是null,但是使用DO_NOT_PROXY会使得代码逻辑更加清晰),就会执行
createProxy方法,创建一个代理类,然后返回一个代理类,ok,现在我们就清楚了问题分析中的 第3)和第 1) 2) 区别,那就是
service类是否配置了相关的aop拦截配置,无论是注解还是xml形式,目前我们还不清楚第1)和 第2)的区别,就是为什么有时候
生成jdk代理,有时候生成cglib代理,这就需要继续向下看creatProxy方法了,最终会进入一个DefaultAopProxyFactory的createAopProxy
方法:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
如果目标类是接口就一定会使用jdk代理,如果目标类没有可以代理的接口就一定会使用Cglib代理。
这个问题我们现在知道了,那他有什么意义呢,换句话说,我们为什么要知道这个,即使不知道原理,我们也可以去搜搜去解决,在我看来
把他弄明白的过程学会了很多知识,而且我们如果在工作过程中遇到了需要扩展的地方,我们可以很容易的去解决。
最后欢迎大家在评论区留言,有什么想法说出来,共同进步。
来自:https://www.cnblogs.com/zcmzex/p/8822509.html
参考另一篇IOC源码理解分析:
总结:
//AbstractAutowireCapableBeanFactory创建Bean实例对象:
//AbstractAutowireCapableBeanFactory类实现了ObejctFactory接口,创建容器指定的Bean实例对象,同时还对创建的Bean实例对象进行初始化处理。
// 创建Bean实例对象 方法中 调用了 核心 三句话
protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
//调用一 :createBeanInstance:生成Bean所包含的java对象实例。
instanceWrapper = createBeanInstance(beanName, mbd, args);
//调用二:populateBean :对Bean属性的依赖注入进行处理(解析bean的属性,并对bean的属性赋值[值注入])
populateBean(beanName, mbd, instanceWrapper);
//调用三:对 bean 的 扩展功能 进行配置 比如 aop ,事务。(可以 插入到任何操作 后)
// BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 创建bean的 过程 都是 普通的 实例bean ,如果 是接口 就执行 不创建。
// 然后 bean 赋值 过程 没有变化,
// 到了 bean 拓展功能阶段,就看 有没有 代理 或者 接口问题了。比如 上面的 举例 明天写后续
1、在xml文件配置 (即spring 配置文件)
在xml文件中 配置bean, 标注 各个bean之间的依赖关系。
2、(1)在代码中用注解:
在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。
constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
byType:查找所有的set方法,将符合符合参数类型的bean注入。
下面进入正题:注解方式注册bean,注入依赖
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean
描述依赖关系主要有两种:
@Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
@Autowired:spring注解,默认也是以byName的方式去匹配与属性名相同的bean的id,如果没有找到,就通过byType的方式去查找,如果查找到多个,用@Qualifier注解限定具体使用哪个。
@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;<br>
(2)同时在spring 配置文件中 配置自动扫描的包:
<!-- 配置自动扫描的包 --> <context:component-scan base-package="com.webproject.srcdir"> </context:component-scan> <br>
在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean 注意:如果配置了<context:component-scan>那么<context:annotation-config/>标签就可以不用再xml中配置了,因为前者包含了后者。另外<context:component-scan>还提供了两个子标签 1. <context:include-filter> 2. <context:exclude-filter> 在说明这两个子标签前,先说一下<context:component-scan>有一个use-default-filters属性,改属性默认为true,这就意味着会扫描指定包下的全部的标有@Component的类,并注册成bean.也就是@Component的子注解@Service,@Reposity等。所以如果仅仅是在配置文件中这么写 <context:component-scan base-package="com.webproject.srcdir"/> Use-default-filter此时为true那么会对base-package包或者子包下的所有的进行java类进行扫描,并把匹配的java类注册成bean。 可以发现这种扫描的粒度有点太大,如果你只想扫描指定包下面的Controller,该怎么办?此时子标签<context:incluce-filter>就起到了勇武之地。如下所示 <context:component-scan base-package="com.webproject.srcdir.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> 这样就会只扫描base-package指定下的有@Controller下的java类,并注册成bean 但是因为use-dafault-filter在上面并没有指定,默认就为true,所以当把上面的配置改成如下所示的时候,就会产生与你期望相悖的结果(注意base-package包值得变化) <context:component-scan base-package="com.webproject.srcdir"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> 此时,spring不仅扫描了@Controller,还扫描了指定包所在的子包service包下注解@Service的java类 此时指定的include-filter没有起到作用,只要把use-default-filter设置成false就可以了。这样就可以避免在base-packeage配置多个包名这种不是很优雅的方法来解决这个问题了。 另外在我参与的项目中可以发现在base-package指定的包中有的子包是不含有注解了,所以不用扫描,此时可以指定<context:exclude-filter>来进行过滤,说明此包不需要被扫描。综合以上说明 Use-dafault-filters=”false”的情况下:<context:exclude-filter>指定的不扫描,<context:include-filter>指定的扫描
写在最后:虽然有这么多的注入方式,但是实际上开发的时候自己编写的类一般用注解的方式注册类,用@Autowired描述依赖进行注入,一般实现类也只有一种(jdbc or hibernate or mybatis),除非项目有大的变动,所以@Qualifier标签用的也较少;但是在使用其他组件的API的时候用的是通过xml配置文件来注册类,描述依赖,因为你不能去改人家源码嘛。
(Dependency Injection) 依赖注入:两个对象之间的依赖关系在程序运行时由外部容器动态的注入依赖行为方式称为依赖注入(DI) 。 DI 是 IOC 的一种形式。
IoC 在应用开发中是一个非常有力的概念。如 Martin Flower 所述, IoC的一种表现形式就是依赖性注射。依赖性注射用的是好莱坞原则, ” 不要找我,我会找你的。 “。换句来说,你的类不会去查找或是实例化它们所依赖的类。控制恰好是反过来的,某种容器会设置这种依赖关系。使用 IoC常常使代码更加简洁,并且为相互依赖的类提供一种很好的方法。
依赖注入的三种实现类型:接口注入、 Setter 注入和构造器注入。
1、接口注入:
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj=Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt()
}
……
}
上面的代码中, ClassA 依赖于 InterfaceB 的实现,如何获得 InterfaceB实现类的实例?传统的方法是在代码中创建 InterfaceB 实现类的实例,并将起赋予 clzB 。
而这样一来, ClassA 在编译期即依赖于 InterfaceB的实现。为了将调用者与实现者在编译期分离,于是有了上面的代码.
我们根据预先在配置文件中设定的实现类的类名 (Config.BImplementation) ,动态加载实现类,并通过InterfaceB 强制转型后为 ClassA 所用。这就是接口注入的一个最原始的雏形。
而对于一个 Type1 型 IOC 容器而言,加载接口实现并创建其实例的工作由容器完成,见如下代码。
public class ClassA {
private InterfaceB clzB;
public Object doSomething(InterfaceB b) {
clzB = b;
return clzB.doIt();
}
……
}
在运行期, InterfaceB 实例将由容器提供。
Type1 型 IOC 发展较早(有意或无意),在实际中得到了普遍应用,即使在 IOC的概念尚未确立时,这样的方法也已经频繁出现在我们的代码中。
下面的代码大家应该非常熟悉:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponseresponse)throws ServletException, IOException { …… } }
这也是一个 接口注入, HttpServletRequest 和 HttpServletResponse 实例由Servlet Container 在运行期动态注入。
参考 J2EE API文档: https://docs.oracle.com/javaee/6/api/
HttpServletRequest HttpServletRequest 都是个接口。
这两个接口是由各个 j2ee厂商提供的实现的。比如tomcat ,weblogic都有自己的j2ee实现。
可以说j2ee只是一堆规范和定义好的接口。实现是由J2EE厂商实现,实例化是由web容器实例化
java通过 动态代理 反射实列化接口,以此实现 接口注入。
2、set注入:
采用属性的set方法进行初始化,就成为set注入。
1)给普通字符类型赋值。
public class User{
privateString username;
publicString getUsername() {
returnusername;
}
publicvoid setUsername(String username) {
this.username= username;
}
}
我们只需要提供属性的set方法,然后去属性文件中去配置好让框架能够找到applicationContext.xml文件的beans标签。标签beans中添加bean标签, 指定id,class值,id值不做要求,class值为对象所在的完整路径。bean标签再添加property 标签,要求,name值与User类中对应的属性名称一致。value值就是我们要给User类中的username属性赋的值。
<bean id="userAction"class="com.test.spring.action.User" > <property name="username" value="admin"></property> </bean>
2)给对象赋值
同样提供对象的set方法
public class User{
private UserService userservice;
public UserServicegetUserservice() {
returnuser;
}
public void setUserservice(UserService userservice){
this.userservice= userservice;
}
}
配置文件中要增加UserService的bean标签声明及User对象对UserService引用。
<!--对象的声明--> <bean id="userService" class="com.test.spring.service.UserService"></bean> <bean id="userAction"class="com.test.spring.action.User" > <property name="userservice" ref="userService"></property> </bean>
这样配置,框架就会将UserService对象注入到User类中。
3)给list集合赋值
同样提供set方法
public class User{
privateList<String> username;
publicList<String> getUsername() {
returnusername;
}
publicvoid setUsername(List<String> username) {
this.username= username;
}
}
<bean id="userAction"class="com.test.spring.action.User" >
<propertyname="username">
<list>
<value>zhang,san</value>
<value>lisi</value>
<value>wangwu</value>
</list>
</property>
</bean>
4)给属性文件中的字段赋值
public class User{
privateProperties props ;
publicProperties getProps() {
returnprops;
}
publicvoid setProps(Properties props) {
this.props= props;
}
}
<bean>
<propertyname="props">
<props>
<propkey="url">jdbc:oracle:thin:@localhost:orl</prop>
<propkey="driverName">oracle.jdbc.driver.OracleDriver</prop>
<propkey="username">scott</prop>
<propkey="password">tiger</prop>
</props>
</property>
</bean>
标签中的key值是.properties属性文件中的名称
注意:
无论给什么赋值,配置文件中标签的name属性值一定是和对象中名称一致。
3、构造方法注入
1)构造方法一个参数
public class User{
privateString usercode;
publicUser(String usercode) {
this.usercode=usercode;
}
}
<bean id="userAction"class="com.test.spring.action.User">
<constructor-argvalue="admin"></constructor-arg>
</bean>
2)构造函数有两个参数时
当参数为非字符串类型时,在配置文件中需要制定类型,如果不指定类型一律按照字符串类型赋值。
当参数类型不一致时,框架是按照字符串的类型进行查找的,因此需要在配置文件中制定是参数的位置
<constructor-argvalue="admin"index="0"></constructor-arg> <constructor-argvalue="23" type="int"index="1"></constructor-arg><br>
这样制定,就是构造函数中,第一个参数为string类型,第二个参数为int类型。
参考自:https://blog.csdn.net/sinat_34093604/article/details/52485270
https://blog.csdn.net/lishuangzhe7047/article/details/20740835#
https://blog.csdn.net/a909301740/article/details/78379720
要想很好理解这三个上下文的关系,需要先熟悉spring是怎样在web容器中启动起来的。spring的启动过程其实就是其IoC容器的启动过程,对于web程序,IoC容器启动过程即是建立上下文的过程。
说完了spring上下文的初始化过程,这三个上下文的关系应该就了解了。如还是不太清楚,我就爱莫能助了,只能自行看代码去了。
===============================================================================================================
最近在做项目时牵扯到有关父子上下文的概念。
何为父子上下文呢?
父上下文:
使用listener监听器来加载配置文件,如下:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
Spring 会创建一个WebApplicationContext上下文,称为父上下文(父容器),保存在 ServletContext中,key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。
可以使用Spring提供的工具类取出上下文对象:WebApplicationContextUtils.getWebApplicationContext(ServletContext);
子上下文:
使用Spring MVC 来处理拦截相关的请求时,会配置DispatchServlet:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
每个DispatchServlet会有一个自己的上下文,称为子上下文,它也保存在 ServletContext中,key 是”org.springframework.web.servlet.FrameworkServlet.CONTEXT”+Servlet名称。当一 个Request对象产生时,会把这个子上下文对象(WebApplicationContext)保存在Request对象中,key是 DispatcherServlet.class.getName() + “.CONTEXT”。
可以使用工具类取出上下文对象:RequestContextUtils.getWebApplicationContext(request);
父上下文(父容器)和子上下文(子容器)的访问权限:
子上下文可以访问父上下文中的bean,但是父上下文不可以访问子上下文中的bean。
父上下文使用与否
方案一,传统型:
父上下文容器中保存数据源、服务层、DAO层、事务的Bean。
子上下文容器中保存Mvc相关的Action的Bean.
事务控制在服务层。
由于父上下文容器不能访问子上下文容器中内容,事务的Bean在父上下文容器中,无法访问子上下文容器中内容,就无法对子上下文容器中Action进行AOP(事务)。
当然,做为“传统型”方案,也没有必要这要做。
方案二,激进型:
Java世界的“面向接口编程”的思想是正确的,但在增删改查为主业务的系统里,Dao层接口,Dao层实现类,Service层接口,Service层实现类,Action父类,Action。再加上众多的O(vo\po\bo)和jsp页面。写一个小功能 7、8个类就写出来了。 开发者说我就是想接点私活儿,和PHP,ASP抢抢饭碗,但我又是Java程序员。最好的结果是大项目能做好,小项目能做快。所以“激进型”方案就出现了—–没有接口、没有Service层、还可以没有众多的O(vo\po\bo)。那没有Service层事务控制在哪一层?只好上升的Action层。
本文不想说这是不是正确的思想,我想说的是Spring不会限制你这样做。
由于有了父子上下文,你将无法实现这一目标。解决方案是只使用子上下文容器,不要父上下文容器 。所以数据源、服务层、DAO层、事务的Bean、Action的Bean都放在子上下文容器中。就可以实现了,事务(注解事务)就正常工作了。这样才够激进。
总结:不使用listener监听器来加载spring的配置文件,只使用DispatcherServlet来加载spring的配置,不要父子上下文,只使用一个DispatcherServlet,事情就简单了,什么麻烦事儿也没有了。
Java–大项目能做好–按传统方式做,规规矩矩的做,好扩展,好维护。
Java–小项目能做快–按激进方式做,一周时间就可以出一个版本,先上线接受市场(用户)的反馈,再改进,再反馈,时间就是生命(成本)。
来自:https://blog.csdn.net/caomiao2006/article/details/51290494
使用IDE:IDEA
就像下图的进行勾选。
如下图所示。
创建项目时,gradle中用到的依赖:
// Java EE 通过本地Jar包导入
compile fileTree(dir:'lib',include:['*.jar'])
// Spring 框架
def springVersion = '4.3.10.RELEASE'
compile("org.springframework:spring-aop:$springVersion",
"org.springframework:spring-orm:$springVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-instrument-tomcat:$springVersion",
"org.springframework:spring-instrument:$springVersion",
"org.springframework:spring-framework-bom:$springVersion",
"org.springframework:spring-expression:$springVersion",
"org.springframework:spring-core:$springVersion",
"org.springframework:spring-context-support:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-aspects:$springVersion",
"org.springframework:spring-test:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-webmvc:$springVersion",
"org.springframework:spring-webmvc-portlet:$springVersion")
// 模拟测试框架
testCompile "org.mockito:mockito-core:2.+"
// 集成测试框架
testCompile 'org.testng:testng:6.8.17'
// 单元测试框架
testCompile group: 'junit', name: 'junit', version: '4.12'
还需要的框架有:Java EE
BeanFactory类是Spring框架下最核心的接口,它提供了高级的IOC配置机制。我们可以使用下面的XmlBeanDefinitionReader和DefaultListableBeanFactory类启动IOC容器。
在com.czuaphe.test包下,创建Car类
在Resource目录编写beans.xml文件生成实例Bean
然后,在test/java目录下,使用TestNG框架测试一个BeanFactoryTest测试类
通过加载Resource下的beans.xml文件启动IOC容器,得到实例Bean。
上图我们是通过类路径得到XML文件的,在gradle下,类路径就是resources目录下
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 通过类路径得到资源,以classpath:开头
Resource resource = resolver.getResource("classpath:beans.xml");
运行结果如下图所示:
如果我们将beans.xml文件放在与Car类相同的路径下:
在test/java目录下,BeanFactoryTest类中的resouce变量只需要改变一下,就可以达到和上面一样的结果。
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("file:src/main/java/com/czuaphe/test/beans.xml");
ApplicationContext类继承了BeanFactory类,实现了更多面向应用的功能。
我们就在上例的基础上,在test/java目录下,使用TestNG框架再编写一个ApplicationContextTest类。同样可以实现像上面两种加载XML文件启动IOC容器的方法,此外,ApplicatioinContext类还可以通过类注解的方式加载Bean启动IOC容器。
我们实现通过类注解的方式,实现启动IOC容器。首先,在com.czuaphe.test包下写Bean.java文件,编写实例化Bean的配置。
在ApplicationContextTest类下,实现通过加载类注解配置文件启动IOC容器。
另外两种通过ApplicationContext类启动IOC容器,也在注释中写出了。
通过类注解启动IOC容器,运行测试的结果如下图所示:
WebApplicationContext类继承了ApplicationContext类,专门用于Web应用。启动方式也是通过Web容器启动,实现IOC容器的启动。不能简单通过Java代码实现启动。
在webapp/WEB-INF下的web.xml中定义ContextLoaderListener监听Web容器的启动,然后定义contextConfigLocation获得Spring框架的Bean配置文件。
然后在web.xml文件中,再定义一个Servlet,定义它的属性load-on-startup为1,表示在Web容器和Spring框架的IOC容器启动之后自启动,得到WebApplicationContext类的实例,就可以得到Bean实例了。
web.xml文件如下图所示
在com.czuaphe.webtest包下,编写SpringBeanServlet类,重写Servlet的init()方法,得到WebApplicationContext类的实例。
最后,对此项目配置Tomcat服务器,在启动Web容器时,就会启动Spring的IOC容器,而且会运行自定义的SpringBeanServlet,得到Bean实例。运行结果如下图所示:
编写了三种IOC容器的启动方式,在非Web应用下,使用ApplicationContext启动IOC容器就已经足够使用,在Web应用下,就必须在web.xml文件中配置WebApplicationContext的启动。让Web容器启动时IOC容器也启动。BeanFactory类启动IOC容器一般不常用。
来自:https://blog.csdn.net/cuipp0509/article/details/78544497#%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE%E6%97%B6%E9%9C%80%E8%A6%81%E7%94%A8%E5%88%B0%E7%9A%84%E6%A1%86%E6%9E%B6%E6%9C%89java%E5%92%8Cweb
什么是AOP?
基本概念
切面(aspect):横切关注点被模块化的特殊对象。
通知(advice):切面必须要完成的工作。切面中的每个方向称之为通知。通知是在切面对象中的。
目标(target):被通知的对象。
代理(proxy):向目标对象应用通知后创建的对象。
连接点(joinpoint):目标对象的程序执行的某个特定位置。如某个方法调用前,调用后的位置。包括两个信息:1.目标程序的哪个方法?2.方法执行
前还是执行后?
切点(pointcut):每个类会有多个连接点,AOP通过切点定位到特定的边接点。
类比,连接点相当于数所成方圆 中的记录,而切点相当于查询条件。一个切点匹配多个连接点。
2.目标对象(要切入的对象)
首先为了不违反开闭原则和更好的可扩展性,目标对象实际上是实现了已定义好的某个接口
接口:
package com.itnba.test;
public interface IHuman {
public void chifan();
public void shuijiao();
}
实现接口的两个类:
package com.itnba.test;
import org.springframework.stereotype.Component;
public class Chinese implements IHuman {
@Override
public void chifan() {
// TODO 自动生成的方法存根
System.out.println("中国人吃饭");
}
@Override
public void shuijiao() {
// TODO 自动生成的方法存根
System.out.println("中国人睡觉");
}
}
package com.itnba.test;
import org.springframework.stereotype.Component;
public class American implements IHuman {
@Override
public void chifan() {
// TODO 自动生成的方法存根
System.out.println("美国人吃饭");
}
@Override
public void shuijiao() {
// TODO 自动生成的方法存根
System.out.println("美国人睡觉");
}
}
3.定义一个切面类
package com.itnba.test;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
public class Qiemian {
public void chifanqian(){
System.out.println("洗手");
}
public void chifanhou(){
System.out.println("漱口");
}
public void shuijiaoqian(){
System.out.println("洗澡");
}
}
4.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"
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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"
default-autowire="byName"
>
<!-- 首相要实例化目标对象类和切面类 -->
<bean id="chinese" class="com.itnba.test.Chinese"></bean>
<bean id="american" class="com.itnba.test.American"></bean>
<bean id="qiemian" class="com.itnba.test.Qiemian"></bean>
<aop:config>
<!-- 要切入的对象 -->
<aop:pointcut expression="execution(* com.itnba.test.*.chifan(..))" id="chifan"/>
<aop:pointcut expression="execution(* com.itnba.test.*.shijiao(..))" id="shijiao"/>
<!-- 切入点 -->
<aop:aspect id="ha" ref="qiemian"><!-- 切面类 -->
<!-- <aop:之前before、之后after... method="切面类中的方法" pointcut-ref="上面的切入的对象"/> -->
<aop:before method="chifanqian()" pointcut-ref="chifan"/><!-- 之前通知 -->
<aop:after method="chifanhou()()" pointcut-ref="chifan"/><!-- 之后通知 -->
<aop:before method="shuijiaoqian()" pointcut-ref="shijiao"/>
</aop:aspect>
</aop:config>
</beans>
5.运行
package com.itnba.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//要用接口接收
IHuman c= (IHuman)context.getBean("chinese");
c.chifan();
c.shuijiao();
System.out.println("*********************************");
//要用接口接收
IHuman a= (IHuman) context.getBean("american");
a.chifan();
a.shuijiao();
}
}
结果:(主要是第4步的xml配置中 ,引用 shuijiao 错打成了 shijiao)
使用注解的方法:
1.导入 spring包和aspectj的两个jar包
2.配置文件中加入aop命名空间
<?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"
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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd" >
<!-- 自动扫描包下的类,并将其实例化。多个包之间用,隔开 -->
<context:component-scan base-package="com.itnba.test"></context:component-scan>
<!-- 配置文件中启动AspectJ的注解功能 ,默认是false,要将其改为true -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
3.目标对象(要切入的对象)
首先为了不违反开闭原则和更好的可扩展性,目标对象实际上是实现了已定义好的某个接口
接口:
package com.itnba.test;
public interface IHuman {
public void chifan();
public void shuijiao();
}
实现接口的两个类:
package com.itnba.test;
import org.springframework.stereotype.Component;
@Component
public class Chinese implements IHuman {
@Override
public void chifan() {
// TODO 自动生成的方法存根
System.out.println("中国人吃饭");
}
@Override
public void shuijiao() {
// TODO 自动生成的方法存根
System.out.println("中国人睡觉");
}
}
package com.itnba.test;
import org.springframework.stereotype.Component;
@Component
public class American implements IHuman {
@Override
public void chifan() {
// TODO 自动生成的方法存根
System.out.println("美国人吃饭");
}
@Override
public void shuijiao() {
// TODO 自动生成的方法存根
System.out.println("美国人睡觉");
}
}
4.定义一个切面类,直接在里面注解
package com.itnba.test;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect //声明该类是切面类
@Component//配置文件中启动自动扫描功能
public class Qiemian {
@Before("execution(* com.itnba.test.*.chifan(..))")//在那个方法的之前通知 *代表全部..代表所有形参,不管有多少
public void chifanqian(){
System.out.println("洗手");
}
@After("execution(* com.itnba.test.*.chifan(..))")//在那个方法之后通知
public void chifanhou(){
System.out.println("漱口");
}
@Before("execution(* com.itnba.test.*.shuijiao(..))")
public void shuijiaoqian(){
System.out.println("洗澡");
}
}
5.写好后用main函数来运行一下
package com.itnba.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Chinese c= context.getBean(Chinese.class);
c.chifan();
c.shuijiao();
System.out.println("*********************************");
American a= (American) context.getBean("american");
a.chifan();
a.shuijiao();
}
}
结果:
这样,Spring的面向切面编程就简单介绍完了。
通知分类:
前置通知(@Before) — 在目标方法执行前执行的通知。
后置通知(@After) — 在目标方法执行后执行的通知。无论是否发生异常。后置通知中,无法读取目标方法返回的结果。
返回通知(@AfterReturnning) –在目标方法执行成功后执行的通知。在返回通知中可以访问目标返回结果的。
@AfterReturnning(value=”execution(* com.itnba..*(..))”,returning=”result”)
public void afterRun(Object result){
System.out.println(result);
}
异常通知(@AfterThrowing) — 在目标方法执行出错后执行的通知。
@AfterThrowing(value=”execution(* com.itnba..*(..))”,throwing=”ex”)
public void afterThrowing(Exception ex){
System.out.println(ex.getMessage());
}
环绕通知(@Around) — 需要切面方法携带ProceedingJoinPoion参数,类似于一个完整的动态代理,环绕通知必须要有一个返回值,是目标方法的返
回值。
@Around(“execution(* com.itnba..*(..))”)
public object aroundAdvice( ProceedingJoinPoint pjp){
object obj = null;
try{
//做前置通知
obj = pjp.proceed();
//做返回通知
}
catch(Exception ex){
//做异常通知
}
//做后置通知
return obj;
}
添加日志:
切面方法可以加入参数(JoinPoint) joinPost.getSignature().getXXX()获得相关方法信息
切面方法中可以加入Object参数,用来获得目标方法的返回值(只对返回通知起作用)
来自:https://www.cnblogs.com/hq233/p/6637488.html
Spring AOP
1.1 面向切面编程基础
通常,系统由很多组件组成,每个组件负责一部分功能,然而,这些组件也经常带有一些除了 核心功能之外的附带功能 。系统服务如日志、事务管理和安全经常融入到一些其他功能模块中。这些系统服务通常叫做交叉业务,这是因为它们总是分布在系统的很多组件中。通过将这些业 务分布在多个组件中,给我们的代码引入了双重复杂性。
(1) 实现系统级业务的代码在多个组件中复制。这意味着如果你要改变这些业务逻辑,你就必须到各个模块去修改。就算把这些业务抽象成一个独立模块,其它模块只是调用它的一个方法,但是这个方法调用也还是分布在很多地方。
(2) 组件会因为那些与自己核心业务无关的代码变得杂乱。一个向地址录中添加条目的方法应该只关心如何添加地址,而不是关心它是不是安全或支持事务的。
此时,我们该怎么办呢?这正是AOP用得着的地方。AOP帮助我们将这些服务模块化,并把它们声明式地应用在需要它们的地方,使得这些组件更加专注于自身业务,完全不知道其它涉及到的系统服务。
这里的概念切面,就是我们要实现的交叉功能,是应用系统模块化的一个方面或领域。切面的 最常见例子就是日志记录。日志记录在系统中到处需要用到,利用继承来重用日志模块是不合适的,这样,就可以创建一个日志记录切面,并且使用AOP在系统中 应用。下图展示了切面应用方式

图表 1 应用切面
其中,通知Advice是切面的实际实现。连接点Joinpoint是应用程序执行过程 中插入切面的地点,这个地点可以是方法调用,异常抛出,甚至可以是要修改的字段,切面代码在这些地方插入到你的应用流程中,添加新的行为。切入点 Pointcut定义了Advice应该应用在那些连接点,通常通过指定类名和方法名,或者匹配类名和方法名式样的正则表达式来指定切入点。
1.2 AOP在Spring中的实现
基于AOP,业界 存在各种各样的AOP实现,比如,JBoss AOP、Spring AOP、ASP ectJ、 Aspect Werkz等。各自实现的功能也不一样。AOP实现的强弱在很大程度上取决于连接点模型。目前,Spring只支持方法级的连接点。这和一些其他AOP框 架不一样,如AspectJ和JBoss,它们还提供了属性接入点,这样可以防止你创建特别细致的通知,如对更新对象属性值进行拦截。然而,由于 Spring关注于提供一个实现J2EE 服务的框架,所以方法拦截可以满足大部分要求,而且Spring的观点是属性拦截破坏了封装,让Advice触发在属性值改变而不是方法调用上无疑是破坏了这个概念。
Spring的AOP框架的关键点如下:
(1)Spring实现了AOP联盟接口。在Spring AOP中,存在如下几种通知(Advice)类型
Before通知:在目标方法被调用前调用,涉及接口org.springFramework .aop.MethodBeforeAdvice;
After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
(2)用Java 编写Spring通知,并在Spring的配置文件中,定义在什么地方应用通知的切入点。
(3)Spring的运行时通知对象。代理Bean只有在第一次被应用系统需要的时候才 被创建。如果你使用的是ApplicationContext,代理对象在BeanFactory载入所有Bean的时候被创建。Spring有两种代理 创建方式。如果目标对象实现了一个或多个接口暴露的方法,Spring将使用JDK 的 java.lang.reflect.Proxy类创建代理。这个类让Spring动态产生一个新的类,它实现所需的接口,织入了通知,并且代理对目标对 象的所有请求。如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个子类的时候,Spring将通知织入,并且将 对目标对象的调用委托给这个子类。此时,需要将Spring发行包lib/cglib目录下的JAR文件发布到应用系统中。
1.3 Spring AOP的优势
借助于Spring AOP,Spring IoC能够很方便的使用到非常健壮、灵活的企业级服务,是因为Spring AOP能够提供如下几方面的优势:
(1)允许开发者使用声明式企业服务,比如事务服务、安全性服务;EJB 开发者都知道,EJB组件能够使用J2EE容器提供的声明式服务,但是这些服务要借助于EJB容器,而Spring AOP却不需要EJB容器,借助于Spring的事务抽象框架就可以这些服务。
(2)开发者可以开发满足业务需求的自定义切面;
(3)开发Spring AOP Advice很方便。因为这些AOP Advice仅是POJO类,借助于Spring提供的ProxyFactoryBean,能够快速的搭建Spring AOP Advice。
来自:https://www.cnblogs.com/ITtangtang/p/3978349.html
IoC 与 DI
首先想说说 IoC ( Inversion of Control ,控制倒转)。这是 spring 的核心,贯穿始终。所谓 IoC ,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和 对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的 mm ,然后打听她们的兴趣爱 好、 qq 号、电话号、 ip 号、 iq 号 ……… ,想办法认识她们,投其所好送其所要,然后嘿嘿 …… 这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传 统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己 new 一个,或者从 JNDI 中查询一个),使用完之后还要将对象销毁(比 如Connection 等),对象始终会和其他的接口或类藕合起来。
那么 IoC 是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚 介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我 们的要求,提供一个 mm ,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控 制,而是有婚介这样一个类似容器的机构来控制。 Spring所倡导的开发方式就是如此,所有的类都会在 spring 容器中登记,告诉 spring 你是个什 么东西,你需要什么东西,然后 spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 spring 。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象 都被 spring 控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。
IoC 的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过 DI ( Dependency Injection ,依赖注入)来实现的。比如对象 A 需要操作数据库,以前我们总是要在 A 中自己编写代码来获得一个 Connection 对象,有了 spring 我们就只需要告诉 spring , A 中需要一个 Connection ,至于这个 Connection 怎么构造,何时构造, A 不需要知道。在系统 运行时,spring 会在适当的时候制造一个 Connection ,然后像打针一样,注射到 A 当中,这样就完成了对各个对象之间关系的控制。 A 需要依赖 Connection 才能正常运行,而这个 Connection 是由 spring 注入到 A 中的,依赖注入的名字就这么来的。那么 DI 是如何实现的呢? Java 1.3 之后一个重要特征是反射( reflection ),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性, spring 就是通过反射来实现注入的。关于反射的相关资料请查阅 java doc 。
IoC 是一个很大的概念,可以用不同的方式来实现。主要的实现形式有两种 :
依赖查找:容器提供回调接口和上下文环境给组件。 EJB 和 Apache Avalon 都是使用这种方式。
依赖注入:组件不做定位查询,只是提供普通的 Java 方法让容器去决定依赖关系。容器全权负责组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造子传递给需要的对象。通过 JavaBean 属性注射依赖关系的做法称为设值方法注入( Setter Injection );将依赖关系作为构造子参数传入的做法称为构造子注入( Constructor Injection )。
附图说明:
到这里,大家应该对 IoC 与 DI 都有了初步的认识了。其实就 Spring 来说,就是 JavaBean 由 Spring 来管理组装,表面上看就少了几个 new 字,其实就是为了降低耦合度,这也是我们做软件的目标之一。
至于 Spring 是怎样实现 IoC 的, 《 expert one-on-one J2EE Development without EJB 中文版》第七章“ Spring 框架介绍”很详细的列举了多种方法。说实在,一下子看这么多,我真有点糊涂了。我还是自己写个 Demo 熟悉一下大名鼎鼎的 Spring 吧。
首先得下载 Spring 。 Spring 网上有两种 Spring 包一种是 spring-framework-1.2.6-with-dependencies.zip ,另一种是spring-framework-1.2.6.zip 。当然最好是下载 spring-framework-1.2.6-with-dependencies.zip 形式的,因为里面包括了更多的东东。 spring-framework-1.2.6-with-dependencies.zip 的下载地址是:http://prdownloads.sourceforge.net/springframework/spring-framework-1.2.6-with-dependencies.zip 。
下载下来,解压后,你会发现里面有很多 jar 文件。因为刚刚接触 Spring ,因此我只需要 spring-core.jar ( spring-framework-1.2.6\dist ),将其导入 eclipse 的构建路径中。另外, log 日志是需要的,这也是为了养成良好的编程习惯。将 log4j-1.2.9.jar ( spring-framework-1.2.6\lib\log4j )和 commons-logging.jar ( spring-framework-1.2.6\lib\jakarta-commons )导入到构建路径中。
准备就绪,开始写 Demo 了。
我的工程的结构是:

<!– [if !supportLists]–>1、 <!– [endif]–>log4j.properties 代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>log4j.rootLogger = Debug, stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern =% c{ 1 } – % m % n
如何使用 Log4j ,请看我的另一篇转贴的文章: 如何使用 Log4J 。
<!– [if !supportLists]–>2、 <!– [endif]–>HelloBean 的代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
public class HelloBean {
private String helloworld = ” Hello!World! ” ;
public String getHelloworld() {
return helloworld;
}
public void setHelloworld(String helloworld) {
this .helloworld = helloworld;
}
}
这是一个简单的 JavaBean ,有个 String 类型的 helloworld 属性,初始值是 “Hello!World!” 。
<!– [if !supportLists]–>3、 <!– [endif]–>Bean.xml 代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–><? xml version = ” 1.0 ” encoding = ” GBK ” ?>
<! DOCTYPE beans PUBLIC ” -//SPRING/DTD BEAN/EN ”
” http://www.springframework.org/dtd/spring-beans.dtd ” >
< beans >
< bean id = ” helloBean ” class = ” com.HelloBean ” >
< property name = ” helloworld ” >
< value > Hello ! Rick </ value >
</ property >
</ bean >
</ beans >
Spirng重点之一就是配置文件,上面是个相当简单的配置文件,我想大家都应该看得懂。最后就是写应用程序了。
4、 <!– [endif]–>Test 的代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
import org.springframework.beans.factory. * ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Test {
public static void main(String[] args) {
// 实例化JavaBean,主要是为了比较new对象和依赖注入两者的区别
HelloBean hellobean = new HelloBean();
System.out.println(hellobean.getHelloworld());
// 通过Spring访问JavaBean组件
Resource resource = new ClassPathResource( ” com/bean.xml ” );
BeanFactory factory = new XmlBeanFactory(resource);
hellobean = (HelloBean)factory.getBean( ” helloBean ” );
System.out.println(hellobean.getHelloworld());
}
}
这个Demo很好的阐述了Spring的Ioc,其实就Spring而言,就是通过配置文件,让Spring如同一个管家一样来管理所有的Bean类。
Spring的依赖注入相对复杂一点,主要是明白调用别的Bean,不是通过实例化对象来调用,而是告诉Spring,我需要什么Bean,然后Spring再向你的Bean里面注入你所需要的Bean对象。
接下来说说代码实现,我只是在刚刚的例子上再添加一点东东。
首先要增加一个HelloImp的接口,这是问什么呢,那你得问Spring,它定的规矩:JavaBean的实现要有两个部分,一个接口,一个默认实现。你不照做就不行。
HelloImp代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
public interface HelloImp {
public void getName();
}
实现HelloImp的Hello代码:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
public class Hello implements HelloImp {
public void getName(){
System.out.println( ” Jack ” );
}
}
接着就是在 HelloBean 中调用 Hello 了。 Spring 的不同之处也在这体现出来。
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
public class HelloBean {
private String helloworld = ” Hello!World! ” ;
private HelloImp hello; // 注意这个私有对象是借口
public String getHelloworld() {
return helloworld;
}
public void setHelloworld(String helloworld) {
this .helloworld = helloworld;
}
public void setHello(HelloImp hello) {
this .hello = hello;
}
public void get(){
this .hello.getName();
}
}
注意字体加粗的地方。
配置文件也需要增加一点东西:
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–><? xml version = ” 1.0 ” encoding = ” GBK ” ?>
<! DOCTYPE beans PUBLIC ” -//SPRING/DTD BEAN/EN ”
” http://www.springframework.org/dtd/spring-beans.dtd ” >
< beans >
<! —注意引用的类是具体的类Hello –>
< bean id = ” myHello ” class = ” com.Hello ” >
</ bean >
< bean id = ” helloBean ” class = ” com.HelloBean ” >
< property name = ” helloworld ” >
< value > Hello ! Rick </ value >
</ property >
< property name = ” hello ” >
< ref bean = ” myHello ” ></ ref >
</ property >
</ bean >
</ beans >
注意字体加粗的部分。
最后在 Test 中添加一句 hellobean.get(); 就可以看到结果了。
<!– <br><br>Code highlighting produced by Actipro CodeHighlighter (freeware)<br>http://www.CodeHighlighter.com/<br><br>–>package com;
import org.springframework.beans.factory. * ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Test {
public static void main(String[] args) {
HelloBean hellobean = new HelloBean();
System.out.println(hellobean.getHelloworld());
Resource resource = new ClassPathResource( ” com/bean.xml ” );
BeanFactory factory = new XmlBeanFactory(resource);
hellobean = (HelloBean)factory.getBean( ” helloBean ” );
System.out.println(hellobean.getHelloworld());
hellobean.get();
}
}
到这,Spring的IoC和DI总算有了一定的认识,也算是敲开了Spring的大门了。
来自:https://blog.csdn.net/qq_27298687/article/details/52958368
Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
在这篇由三部分组成的 Spring 系列 的第 1 部分中,我将介绍 Spring 框架。我先从框架底层模型的角度描述该框架的功能,然后将讨论两个最有趣的模块:Spring 面向方面编程(AOP)和控制反转 (IOC) 容器。接着将使用几个示例演示 IOC 容器在典型应用程序用例场景中的应用情况。这些示例还将成为本系列后面部分进行的展开式讨论的基础,在本文的后面部分,将介绍 Spring 框架通过 Spring AOP 实现 AOP 构造的方式。
请参阅 下载,下载 Spring 框架和 Apache Ant,运行本系列的示例应用程序需要它们。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如图 1 所示。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
控制反转模式(也称作依赖性介入)的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器 (在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。
在典型的 IOC 场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起,决定什么时间调用方法。下表列出了 IOC 的一个实现模式。
Spring 框架的 IOC 容器采用类型 2 和类型3 实现。
面向方面的编程,即 AOP,是一种编程技术,它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。AOP 的核心构造是方面,它将那些影响多个类的行为封装到可重用的模块中。
AOP 和 IOC 是补充性的技术,它们都运用模块化方式解决企业应用程序开发中的复杂问题。在典型的面向对象开发方式中,可能要将日志记录语句放在所有方法和 Java 类中才能实现日志功能。在 AOP 方式中,可以反过来将日志服务模块化,并以声明的方式将它们应用到需要日志的组件上。当然,优势就是 Java 类不需要知道日志服务的存在,也不需要考虑相关的代码。所以,用 Spring AOP 编写的应用程序代码是松散耦合的。
AOP 的功能完全集成到了 Spring 事务管理、日志和其他各种特性的上下文中。
Spring 设计的核心是 org.springframework.beans 包,它的设计目标是与 JavaBean 组件一起使用。这个包通常不是由用户直接使用,而是由服务器将其用作其他多数功能的底层中介。下一个最高级抽象是 BeanFactory 接口,它是工厂设计模式的实现,允许通过名称创建和检索对象。BeanFactory 也可以管理对象之间的关系。
BeanFactory 支持两个对象模型。
bean 工厂的概念是 Spring 作为 IOC 容器的基础。IOC 将处理事情的责任从应用程序代码转移到框架。正如我将在下一个示例中演示的那样,Spring 框架使用 JavaBean 属性和配置数据来指出必须设置的依赖关系。
因为 org.springframework.beans.factory.BeanFactory 是一个简单接口,所以可以针对各种底层存储方法实现。最常用的 BeanFactory 定义是 XmlBeanFactory,它根据 XML 文件中的定义装入 bean,如清单 1 所示。
BeanFactory factory = new XMLBeanFactory(new FileInputSteam("mybean.xml"));
在 XML 文件中定义的 Bean 是被消极加载的,这意味在需要 bean 之前,bean 本身不会被初始化。要从 BeanFactory 检索 bean,只需调用 getBean() 方法,传入将要检索的 bean 的名称即可,如清单 2 所示。
MyBean mybean = (MyBean) factory.getBean("mybean");
每个 bean 的定义都可以是 POJO (用类名和 JavaBean 初始化属性定义) 或 FactoryBean。FactoryBean 接口为使用 Spring 框架构建的应用程序添加了一个间接的级别。
理解控制反转最简单的方式就是看它的实际应用。在对由三部分组成的 Spring 系列的第 1 部分进行总结时,我使用了一个示例,演示了如何通过 Spring IOC 容器注入应用程序的依赖关系(而不是将它们构建进来)。
我用开启在线信用帐户的用例作为起点。对于该实现,开启信用帐户要求用户与以下服务进行交互:
对于这个示例,我假设服务已经存在,理想的情况是用松散耦合的方式把它们集成在一起。以下清单显示了三个服务的应用程序接口。
public interface CreditRatingInterface {
public boolean getUserCreditHistoryInformation(ICustomer iCustomer);
}
清单 3 所示的信用级别评定接口提供了信用历史信息。它需要一个包含客户信息的 Customer 对象。该接口的实现是由 CreditRating 类提供的。
public interface CreditLinkingInterface {
public String getUrl();
public void setUrl(String url);
public void linkCreditBankAccount() throws Exception ;
}
信用链接接口将信用历史信息与银行信息(如果需要的话)连接在一起,并插入用户的信用卡信息。信用链接接口是一个远程服务,它的查询是通过 getUrl() 方法进行的。URL 由 Spring 框架的 bean 配置机制设置,我稍后会讨论它。该接口的实现是由 CreditLinking 类提供的。
public interface EmailInterface {
public void sendEmail(ICustomer iCustomer);
public String getFromEmail();
public void setFromEmail(String fromEmail) ;
public String getPassword();
public void setPassword(String password) ;
public String getSmtpHost() ;
public void setSmtpHost(String smtpHost);
public String getUserId() ;
public void setUserId(String userId);
}
EmailInterface 负责向客户发送关于客户信用卡状态的电子邮件。邮件配置参数(例如 SMPT 主机、用户名、口令)由前面提到的 bean 配置机制设置。Email 类提供了该接口的实现。
这些接口就位之后,接下来要考虑的就是如何用松散耦合方式将它们集成在一起。在 清单 6 中可以看到信用卡帐户用例的实现。
注意,所有的 setter 方法都是由 Spring 的配置 bean 实现的。所有的依赖关系 (也就是三个接口)都可以由 Spring 框架用这些 bean 注入。createCreditCardAccount() 方法会用服务去执行其余实现。在 清单 7 中可以看到 Spring 的配置文件。我用箭头突出了这些定义。
要运行示例应用程序,首先必须 下载 Spring 框架 及其所有依赖文件。接下来,将框架释放到(比如说)磁盘 c:\,这会创建 C:\spring-framework-1.2-rc2 (适用于当前发行版本) 这样的文件夹。在继续后面的操作之前,还必须下载和释放 Apache Ant。
接下来,将源代码释放到文件夹,例如 c:\ 盘,然后创建 SpringProject。将 Spring 库(即 C:\spring-framework-1.2-rc2\dist 下的 spring.jar 和 C:\spring-framework-1.2-rc2\lib\jakarta-commons 下的 commons-logging.jar)复制到 SpringProject\lib 文件夹中。完成这些工作之后,就有了必需的构建依赖关系集。
打开命令提示符,将当前目录切换到 SpringProject,在命令提示符中输入以下命令:build。
这会构建并运行 CreateCreditAccountClient 类,类的运行将创建 Customer 类对象并填充它,还会调用 CreateCreditCardAccount 类创建并链接信用卡帐户。CreateCreditAccountClient 还会通过 ClassPathXmlApplicationContext 装入 Spring 配置文件。装入 bean 之后,就可以通过 getBean() 方法访问它们了,如清单 8 所示。
ClassPathXmlApplicationContext appContext =
new ClassPathXmlApplicationContext(new String[] {
"springexample-creditaccount.xml"
});
CreateCreditCardAccountInterface creditCardAccount =
(CreateCreditCardAccountInterface)
appContext.getBean("createCreditCard");
在这篇由三部分组成的 Spring 系列 的第一篇文章中,我介绍了 Spring 框架的基础。我从讨论组成 Spring 分层架构的 7 个模块开始,然后深入介绍了其中两个模块:Spring AOP 和 IOC 容器。
由于学习的最佳方法是实践,所以我用一个工作示例介绍了 IOC 模式 (像 Spring 的 IOC 容器实现的那样)如何用松散耦合的方式将分散的系统集成在一起。在这个示例中可以看到,将依赖关系或服务注入工作中的信用卡帐户应用程序,要比从头开始构建它们容易得多。
请继续关注这一系列的下一篇文章,我将在这里学习的知识基础上,介绍 Spring AOP 模块如何在企业应用程序中提供持久支持,并让您开始了解 Spring MVC 模块和相关插件。
来自:https://www.ibm.com/developerworks/cn/java/wa-spring1/